Witam. Widzę, że udało Ci się przetrwać ciężkie chwile z rysowaniem, kolorowaniem i obracaniem obiektów. Teraz przyszła pora na nadanie temu wszystkiemu wymiaru. Tak, tak trzeciego wymiaru.
Zmiany w stosunku do poprzedniej lekcji koncetrują się wokół funkcji animate, initBuffers, drawScene
. W animate
zmiany są nie wielkie, wręcz kosmetyczne. Zmienne pamiętające aktualny stan obrotu mają teraz nową nazwę i odwrócony został kierunek obrotu jednej z figur. Wygląda to tak:
rPyramid += (90 * elapsed) / 1000.0; rCube -= (75 * elapsed) / 1000.0;
W tej funkcji to wszystko. Deklaracja zmiennych w niej występujących znajduje się zaraz przed funkcją drawScene
.
Tak samo jak obracaliśmy trójkątem wokół osi Y tak i teraz będziemy robić to samo:
mat4.rotate(mvMatrix, degToRad(rPyramid), [0, 1, 0]);Jedyna różnica w rysowaniu piramidy w stosunku do rysowania trójkąta to większa liczba kolorów, wierzchołków. Oznacza to że oprócz nazw buforów, kod jest identyczny. Do kodu buforów przejdziemy poźniej.
gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, pyramidVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, pyramidVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0); setMatrixUniforms(); gl.drawArrays(gl.TRIANGLES, 0, pyramidVertexPositionBuffer.numItems);
Jeśli chodzi o piramidę to tyle. Przejdźmy teraz do kostki. Najpierw powinnismy ustalić w jaki sposób będą ściany sześcianu. Każda ściana będzie złożona z dwóch niezależnych trójkątów. Na dwa trójkąty potrzebne jest zdefiniowanie 6 wierzchołków. Jak zatem to zrobić mając miejsce w buforze tylko na 4? Będziemy robić coś w stylu: "Narysuj pierwszy trójkąt używając pierwszych 3 wierzchołków w buforze, a następnie narysuj drugi trójkąt wykorzystując wierzchołek pierwszy, trzeci i czwarty."
Pierwszym krokiem jest utworzenie buforów.
gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, cubeVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer); gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, cubeVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
Nowością jaką będziemy używać jest element array buffer
. Podobnie jak do tej pory zostanie on wypełniony w initBuffers
odpowiednimi wartościami i będzie zawierać listę wierzchołków wraz z indeksem do tablicy pozycji i kolorów. Zaraz się temu przyjrzymy.
Aby użyć element buffer array
robimy to co zwykle. Czyli wybieramy go jako aktualny, i wywułujemy funkcję drawElements
. W funckcji rysowania sceny to już wszystko.
Pozostała część kodu mieści się w initBuffers
i jest w miarę oczywista. Najpierw deklaracja buforów z nowymi nazwami:
var pyramidVertexPositionBuffer; var pyramidVertexColorBuffer; var cubeVertexPositionBuffer; var cubeVertexColorBuffer; var cubeVertexIndexBuffer;
Definicja wierzchołków piramidy z odpowiednią wartością numItems
:
pyramidVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexPositionBuffer); var vertices = [ // Front face 0.0, 1.0, 0.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, // Right face 0.0, 1.0, 0.0, 1.0, -1.0, 1.0, 1.0, -1.0, -1.0, // Back face 0.0, 1.0, 0.0, 1.0, -1.0, -1.0, -1.0, -1.0, -1.0, // Left face 0.0, 1.0, 0.0, -1.0, -1.0, -1.0, -1.0, -1.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); pyramidVertexPositionBuffer.itemSize = 3; pyramidVertexPositionBuffer.numItems = 12;
Podobnie określenie kolorów:
pyramidVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, pyramidVertexColorBuffer); var colors = [ // Front face 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Right face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, // Back face 1.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, // Left face 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0 ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW); pyramidVertexColorBuffer.itemSize = 4; pyramidVertexColorBuffer.numItems = 12;
... pozycje wierzchołków ścian kostki:
cubeVertexPositionBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexPositionBuffer); vertices = [ // Front face -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, // Back face -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, -1.0, // Top face -1.0, 1.0, -1.0, -1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, -1.0, // Bottom face -1.0, -1.0, -1.0, 1.0, -1.0, -1.0, 1.0, -1.0, 1.0, -1.0, -1.0, 1.0, // Right face 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 1.0, 1.0, -1.0, 1.0, // Left face -1.0, -1.0, -1.0, -1.0, -1.0, 1.0, -1.0, 1.0, 1.0, -1.0, 1.0, -1.0, ]; gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW); cubeVertexPositionBuffer.itemSize = 3; cubeVertexPositionBuffer.numItems = 24;
Przy definicji kolorów ścian wykorzystujemy pętle, żęby nie pisać cztery razy tego samego dla każdego wierzchołka ściany.
cubeVertexColorBuffer = gl.createBuffer(); gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer); colors = [ [1.0, 0.0, 0.0, 1.0], // Front face [1.0, 1.0, 0.0, 1.0], // Back face [0.0, 1.0, 0.0, 1.0], // Top face [1.0, 0.5, 0.5, 1.0], // Bottom face [1.0, 0.0, 1.0, 1.0], // Right face [0.0, 0.0, 1.0, 1.0], // Left face ]; var unpackedColors = []; for (var i in colors) { var color = colors[i]; for (var j=0; j < 4; j++) { unpackedColors = unpackedColors.concat(color); } } gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(unpackedColors), gl.STATIC_DRAW); cubeVertexColorBuffer.itemSize = 4; cubeVertexColorBuffer.numItems = 24;
No i wreszcie definiujemy element array buffer.
cubeVertexIndexBuffer = gl.createBuffer(); gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); var cubeVertexIndices = [ 0, 1, 2, 0, 2, 3, // Front face 4, 5, 6, 4, 6, 7, // Back face 8, 9, 10, 8, 10, 11, // Top face 12, 13, 14, 12, 14, 15, // Bottom face 16, 17, 18, 16, 18, 19, // Right face 20, 21, 22, 20, 22, 23 // Left face ] gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(cubeVertexIndices), gl.STATIC_DRAW); cubeVertexIndexBuffer.itemSize = 1; cubeVertexIndexBuffer.numItems = 36;
Pamiętaj, każda liczba w tym buforze jest indeksem do wierzchołka pozycji i koloru. Tak więc pierwsza linia w połączeniu z instrukcją rysowania trójkątów oznacza, żę otrzymamy trójkąt z wierzchołków 0,1 i 2, a potem następny używając 0,2 i 3. Ponieważ oba trójkąty są tego samego koloru i są przygległe to w efekcie otrzymamy kwadrat.
Teraz już wiesz jak tworzyć obiekty 3D w WebGL'u. W następnej części zaczniemy