Tutorial 4

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

Wynik końcowy