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