initGL
, getShader
oraz initShaders
, webGLStart
, która to wszystko zebrała w jedność i wykonała,initBuffers
oraz drawScene
. Aby wiedzieć jak te zmiany zachodzą konieczne jest zrozumienie jak są renderowane obiekty w WebGL'u. Przedstawia to poniższy rysunek.
Diagram przedstawia w uproszczony sposób jak dane przekazywane do JavaScriptu zmieniaja się w piksele w WebGL'u.
Na najwyższym poziomie wygląda to tak: za każym razem gdy wywołujesz funkcję jak na przykład drawArrays
, WebGL przetwarza dane które zostały mu wcześniej przekazane w postaci atrybutów oraz uniform variables(których używaliśmy przy macierzach projekci i modelu) i przekazuje to do vertex shadera.
Robi to wywołując shadera wierzchołków po razie dla każdego z nich, za każdym razem z atrybutami odpowiednimi dla każdego wierzchołka. Ponadto przekazuje uniform varibales. Jak ich nazwa wskazuje nie zmieniają się od wywołania do wywołania. Shader zastosowuje do nich obie znane nam macierze i wyniki umieszcza w zmiennych zwanych varying variables. Są one interfejsem pomiędzy vertex a fragment shaderem. Takich zmiennych może być wiele jednak jedną jedyną jaka jest obowiązkowa to glPosition
, która zawiera koordynaty wierzchołka nad którym shader właśnie przestał pracować.
Następnie WebGL dokounje rasteryzacji, czyli zmienia obraz 3D w 2D. Tak naprawdę to modyfikuje varying variables i następnie wywołuje dla każdego piksela z obrazu fragment shadera. Decyduje on które piksele mają zostać wypełnione a które nie i jakim kolorem.
Ostatecznie dane są jeszcze trochę przetwarzane przez WebGL'a i przekazywane do frame buffer'a, któy już przechowuje to co jest wyświetlane.
Celem tej lekcji jest pokazanie jak z JavaScript przekazać informacje o kolorze do frament shadera nie mając do niego bezpośredniego dostępu.
Sposobem na dokonanie tego jest wykorzystanie faktu, że vertex shader może przekazać wiecej varying variables. Nie tylko glPosition
. Czyli przekazujemy kolor do vertex shadera, który przekaże te informacje prosto do fragment shadera.
Spójrzmy wreszcie na kod. Na dobry początek przyjrzyjmy się vertex shaderowi:
attribute vec3 aVertexPosition; attribute vec4 aVertexColor; uniform mat4 uMVMatrix; uniform mat4 uPMatrix; varying vec4 vColor; void main(void) { gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0); vColor = aVertexColor; }
Łatwo zauważyć, że doszły nam dwa atrybuty - rózne dla każdego z wierzchołków oraz zmienna wyjściowa vColor
. Pozycja jest dokładnie tak samo liczona jak wcześniej. Kolor natomiast jest przekazywany z wejścia prosto do wyjścia.
Fragment shader teraz nie definiuje sztywno koloru jak wcześniej tylko pobiera z wejścia zmienną:
precision mediump float; varying vec4 vColor; void main(void) { gl_FragColor = vColor; }
Kolejna mała zmiana zachodzi w inicjalizacji shaderów. Teraz pobieramy referencji do dwóch atrybutów a nie jednego. Dodatkowe linie są zaznaczone
var shaderProgram; function initShaders() { var fragmentShader = getShader(gl, "shader-fs"); var vertexShader = getShader(gl, "shader-vs"); shaderProgram = gl.createProgram(); gl.attachShader(shaderProgram, vertexShader); gl.attachShader(shaderProgram, fragmentShader); gl.linkProgram(shaderProgram); if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) { alert("Could not initialise shaders"); } gl.useProgram(shaderProgram); shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition"); gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute); shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor"); gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute); shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix"); shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix"); }
Przeanalizujmy teraz jakie zmiany miały miejsce w funkcji initBuffers
. Zmiany zostały podświetlone.
var triangleVertexPositionBuffer; var triangleVertexColorBuffer; var squareVertexPositionBuffer; var squareVertexColorBuffer; function initBuffers() { triangleVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); var vertices = [ 0.0, 1.0, 0.0, -1.0, -1.0, 0.0, 1.0, -1.0, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); triangleVertexPositionBuffer.itemSize = 3; triangleVertexPositionBuffer.numItems = 3; triangleVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer); var colors = [ 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); triangleVertexColorBuffer.itemSize = 4; triangleVertexColorBuffer.numItems = 3; squareVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); vertices = [ 1.0, 1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, -1.0, 0.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); squareVertexPositionBuffer.itemSize = 3; squareVertexPositionBuffer.numItems = 4; squareVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer); colors = []; for (var i=0; i < 4; i++) { colors = colors.concat([0.5, 0.5, 1.0, 1.0]); } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); squareVertexColorBuffer.itemSize = 4; squareVertexColorBuffer.numItems = 4; }
Na dobry początek definiujemy dwie globalne zmienne, które będą pełniły rolę buforów koloru. Następnie po stworzeniu bufora pozycji zabieramy się do wypełnienia bufora kolorów odpowiednimi informacjami. Robimy to w bardzo podobny sposób. Tutaj zamiast definiować współrzędne wierzchołka określamy jaki przy nim będzie kolor w kolejności Czerwony, Zielony, Niebieski, Aplha. Wartośc "1.0" jest maksymalną możliwą. Parametr Aplha mówi o przezroczystości. Analogicznie definiujemy kolory dla kwadratu.
Ostatnie zmiany zachodzą w funkcji drawScene
. Jest to zrobienie użytku z nowych danych.
function drawScene() { gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight); gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix); mat4.identity(mvMatrix); mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]); gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); setMatrixUniforms(); gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems); mat4.translate(mvMatrix, [3.0, 0.0, 0.0]); gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, squareVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); setMatrixUniforms(); gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems); }
To by było na tyle. Następnym razem będziemy ruszać naszymi kolorowymi figurami.