Rev. 2.73

네 번째 WebGL 레슨에 오신 것을 환영합니다. 이번에는 실제 3D 개체를 화면에 그려봅니다. 이 학습은 "NeHe OpenGL의 다섯 번째 튜토리얼"을 기반으로 하고 있습니다.

다음 동영상은 이번 학습에서 만들어 볼 WebGL 컨텐츠입니다. 물론, 브라우저에서 실행됩니다.

여기를 클릭하여 WebGL에서 작동하는 것을 확인해 보세요. 만약 WebGL이 갖춰지지 않은 환경이라면 레슨 0을 참고하세요.

이것이 어떻게 만들어졌는지 자세히 살펴봅시다.

시작에 앞서...

이 레슨은 충분한 자바스크립트 프로그래밍 지식이 있지만, 3D 그래픽 구현 경험이 없는 사람들을 대상으로 하고 있습니다. 그리고 가능한 한 빨리 자신만의 3D를 구사하여 결과물을 만들수 있도록 하는 것에 목적을 둡니다. 레슨에 사용된 예제에서 실재 구현된 코드를 분석하여 어떤 일이 일어나는지 알아보고 그것을 이해하여 스스로 응용할 수 있도록 하는 것입니다. 만약 이전의 레슨들(첫 번째, 두 번째, 세 번째)을 학습하지 않았다면 먼저 학습하고 돌아오세요. 이전 레슨과의 차이점만을 소개하고 있거든요.

이전과 동일하게 학습내용 중에는 버그 혹은 오류가 있을 수도 있습니다. 뭔가 잘못된 것을 발견하면 댓글로 알려주세요. 가능한 빨리 고치도록 하겠습니다.

여기에 사용된 예제의 소스코드를 얻는 방법은 두가지가 있습니다. 실제 예제가 작동하는 곳에서 "소스 보기"를 선택하거나, GitHub를 사용할 수 있다면 저장소에서 동일한 예제를 다운로드할 수 있습니다.(앞으로 학습할 예제들도 포함되어 있습니다.) 추천하는 방법은 두 번째로, 저장소에서 모두 가져온 다음 즐겨쓰는 텍스트 편집기에서 열어두고 학습하는 것입니다.

변수 및 인자 추가/변경하기 - animate, drawScene

이 레슨과 이전 레슨의 차이는 animate, initBuffers 그리고 drawScene 이 3개의 함수에서만 이루어집니다. animate 함수가 위치한 곳으로 스크롤하면 그 첫 번째 아주 작은 변화를 발견할 수 있습니다. 두 객체의 현재 회전 상태를 기억하기 위한 변수인 rTri과 rSquare의 이름이 다음과 같이 변경되었습니다.

   rPyramid += (90 * elapsed) / 1000.0; 
   rCube -= (75 * elapsed) / 1000.0;

이것이 이 함수 변경의 전부입니다. 자, 다음으로 drawScene함수를 살펴 보도록합니다. 함수 직전에 다음과 같이 새로운 변수를 추가했습니다.

   var rPyramid = 0;
   var rCube = 0;

다음은 함수의 시작 부분에서, 피라미드의 위치를​​ 바꾸는 코드를 작성합니다. 여기서 한 번 그릴때 마다, 피라미드의 좌표는 Y축을 중심으로 회전합니다. 이것은 이전 단원에서 삼각형에 적용했던 것과 같습니다.

    mvRotate(rPyramid, [0, 1, 0]);

...이제 그려낼 수 있습니다. 이전 단원과의 차이는 컬러풀한(색조가 많은) 삼각형을 그대로 이용하여 화려한 피라미드를 나타내기 위해서는 더 많은 꼭지점들과 색상들을 처리하는 과정을 필요로하며, initBuffers함수에서 핸들하게 됩니다.(initBuffers함수는 조금 나중 봅시다). 즉, 이것의 의미하는 것은 지금까지 사용된 버퍼의 이름이 변경될 뿐 나머지 코드들은 동일하게 사용할 수 있습니다.

    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);

예, 매우 간단합니다. 이제부터 큐브(Cube, 입방체)를 구성하는 코드를 살펴봅시다. 우선 회전시킵니다. 이번에는 X축 뿐만아니라 Z축(보는 사람의 관점)도 회전시킵니다.

    mvRotate(rCube, [1, 1, 1]);

이제, 큐브를 그립니다. 여기서 코드가 약간 복잡해지긴 하겠지만 아래와 같은 세가지 방법이 있습니다.

  • "트라이앵글 스트립(triangle strip)"을 사용하여 연결된 삼각형을 그리는 방법. 만약 큐브의 모든 면이 같은 색인 경우 이것이 가장 간단한 방법입니다. 이 방법은, 지금까지 사용했던 정점 좌표를 그대로 사용할 수 있습니다. 다음에 위치할 한 면을 그리기 위해서는 새로운 2개의 좌표만 추가하면 될 뿐입니다. 매우 효율적이죠. 하지만 모든 면이 다른 색상을 가지게 하고 싶기 때문에 이 방법은 부적절합니다. 왜냐하면, 큐브의 정점은 다른 세개의 삼각형이 공유하고 있으며, 색상을 변경하려면 매우 까다로운 방법을 사용할 필요가 있기 때문입니다. 설명하는 것 조차 어렵기에 이쯤에서 그만합시다.
  • 여섯개의 사각형 조각을 그리는 방법으로 속임수를 씁니다. 여섯개 사각형의 좌표를 부수적으로 관리하여 개별적으로 그리는 방식으로 색상을 입힐 수 있습니다. 첫 번째 레슨에서는 이 방법을 사용했습니다. 그리고 잘 작동했습니다. 하지만 그다지 좋은 WebGL 학습이 되지 않습니다. 나중에 추가적인 계산 비용이 매우 크게 소진될 것이 우려되었기에 그만두었었죠. 만약 계산 비용을 최소화하려고 했다면 drawArrays함수의 호출 횟수를 줄여야 합니다.
  • 마지막 옵션으로는 두개의 삼각형으로 조합된 여섯개의 사각형으로 큐브를 지정합니다. 그러나 특수한 명령을 WebGL에 사용하여 대량으로 렌더링하는 방법입니다. 이것은 트라이앵글 스트립을 사용하는 방법과 거의 유사하지만, 한점 한점 추가 해 나가는 것이 아니라, 12개의 삼각형을 한번에 정의해 버리는 점이 다릅니다. 또한 간단하게 각 면의 색상을 지정할 수 있으며, 코드도 비교적 깨끗하게 작성할 수 있습니다. 여기서 이를 위한 새로운 기능인 drawElements함수를 소개합니다. 결국, 이 방법이 선택된 것이죠. :-)

우선, 큐브의 정점 좌표와 각 면의 색상이 들어간 버퍼 배열을 만듭니다. 여기서 버퍼 배열은 피라미드를 만들때 했던것과 같이 initBuffers함수에 정의하고 있습니다.

    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);

다음 과정은 앞서 정의한 정점으로 삼각형을 그려냅니다. 여기서 작은 문제가 발생합니다. 큐브의 정면을 나타낸다고 생각해 봅시다. 4개의 정점에 대한 위치를 가지며, 각각의 정점에는 색상이 지정됩니다. 그러나 이는 곧 삼각형 2개를 이용해서 그리는 것이기 때문에 총 6개의 정점을 가진다는 의미입니다. 하지만 앞서 정의한 정점 배열은 4개의 위치정보만이 들어있을 뿐입니다.

그렇다면 어떻게 해야할까요? 해결방법을 조금 알기쉽게 말하자면 "배열 버퍼의 처음부터 3번째 까지의 정점으로 1개의 삼각형을 그린다. 그런 다음 1번째와 3번째와 4번째 정점으로 삼각형을 그리는" 것입니다. 이렇게 함으로써 하나의 면을 그릴 수 있습니다. 나머지 면도 마찬가지로 그려갑니다. 이것이 핵심입니다.

이렇게 사용하는 버퍼를 "element array buffer"라 칭하며, drawElements함수에서 호출됩니다. 지금까지 사용했던 정점 배열과 같이, "element array buffer"도 initBuffers에서 초기화합니다. 이 새로운 배열은 정점에 대한 좌표와 색상 정보를 포함합니다. 나중에 그 속을 들여다 봅시다.

이것을 사용하기 위해서는 큐브의 정점 배열을 element array buffer로 등록해 줍니다.(WebGL은 서로 다른 current array와 element array을 저장할 수 있으므로, 명시적으로 어떤 것이 gl.bindbuffer에서 호출될 것인지를 결정해야 합니다.) 그리고 모델-뷰 행렬(model-view matrix)과 투영 행렬(projection matrix)들을 그래픽 카드로 보내는 코드를 drawElements에 작성합니다.

    gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, cubeVertexIndexBuffer); // added
    setMatrixUniforms();
    gl.drawElements(gl.TRIANGLES, cubeVertexIndexBuffer.numItems, gl.UNSIGNED_SHORT, 0); // added

버퍼의 입체화 - initBuffers

이제 drawScene함수가 준비되었습니다. 남아있는 initBuffers함수가 가진 코드는 매우 간단합니다. 여기에는 새로운 종류의 개체를 처리하기 위해 또다른 새로운 이름을 가진 버퍼를 정의합니다. 물론, 큐브의 정점 배열도 여기에 추가합니다.

  var pyramidVertexPositionBuffer;
  var pyramidVertexColorBuffer;
  var cubeVertexPositionBuffer;
  var cubeVertexColorBuffer;
  var cubeVertexIndexBuffer;

피라미드의 정점 배열 좌표를 입력합니다. 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;

...마찬가지로 피라미드의 버텍스에 색상 버퍼를 변경합니다.

    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;

...그리고 큐브의 버텍스 위치 배열입니다. 여기에는 정점 좌표가 들어 있습니다.

    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;

큐브의 정점 색상 배열은 조금 복잡합니다. 정점 색상 목록을 만들기 위해 반복문(roop, 루프)을 사용하고 있으며, 각 색상을 4번씩 정의할 필요는 없습니다.

    cubeVertexColorBuffer = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, cubeVertexColorBuffer);
    var 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;

마지막으로, element array buffer를 하나 더 정의합니다.(gl.bindBuffer와 gl.bufferData의 첫 인수는 다르다는 것에 다시 한번 주의하세요.)

    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;

기억하세요. 이것은 "어떤 정점을 나타내는데, 정점의 위치 배열과 색상 배열 중의 특정 인덱스를 사용 해라"라는 배열입니다. 첫 번째는 drawScene에서 명령이 발생할 때에 0번째와 1번째와 2번째 정점으로 삼각형을 사용하고, 0번째와 2번째와 3번째 정점으로 삼각형을 사용한다는 뜻이 됩니다. 이 때 2개의 삼각형은 같은 색으로 이어져 있기 때문에 결과적으로 정점 0과 1과 2와 3을 사용하여 사각형을 사용한다는 것입니다. 이것을 나머지 모든 면에서 반복하면 완료됩니다.

이제 WebGL 장면에서 어떻게 3D개체를 사용하고 만드는가, 그리고 어떻게 element array buffer와 drawElement 로 정점을 다시 사용하는가에 대해서 배운 것입니다. 만약 질문이나 내용의 정정이 필요하다면 댓글로 남겨주세요. 다음 레슨에서는 텍스쳐 매핑(texture mapping)에 대하여 학습합니다.

이 문서의 원본은 WebGL Lesson 4 – some real 3D objects입니다.

Comments

세 번째 WebGL 레슨에 오신 것을 환영합니다. 레슨 2에 이어 이번 레슨에서는 객체가 회전 운동을 할 수 있도록 합니다. 이번 학습은 NeHe OpenGL의 네 번째 튜토리얼을 바탕으로 합니다.

다음 동영상은 이번 레슨에서 얻어지는 결과물입니다.

WebGL을 지원하는 브라우저를 사용중이라면 여기를 클릭하여 실재로 작동하는 WebGL 버전을 확인할 수 있습니다. 만약 지원하지 않는 브라우저를 사용중이라면 레슨 0을 참고하여 설치하세요.

지금부터 자세한 내용을 설명하도록 합니다.

시작에 앞서...

이 레슨은 충분한 자바스크립트 프로그래밍 지식이 있지만, 3D 그래픽 구현 경험이 없는 사람들을 대상으로 하고 있습니다. 그리고 가능한 한 빨리 자신만의 3D를 구사하여 결과물을 만들수 있도록 하는 것에 목적을 둡니다. 레슨에 사용된 예제에서 실재 구현된 코드를 분석하여 어떤 일이 일어나는지 알아보고 그것을 이해하여 스스로 응용할 수 있도록 하는 것입니다. 만약 당신이 첫 번째 그리고 두 번쩨 레슨을 학습하지 않았다면 먼저 학습하는 것이 좋습니다.

이전과 동일하게 학습내용 중에는 버그 혹은 오류가 있을 수도 있습니다. 뭔가 잘못된 것을 발견하면 댓글로 알려주세요. 가능한 빨리 고치도록 하겠습니다.

여기에 사용된 예제의 소스코드를 얻는 방법은 두가지가 있습니다. 실제 예제가 작동하는 곳에서 "소스 보기"를 선택하거나, GitHub를 사용할 수 있다면 저장소에서 동일한 예제를 다운로드할 수 있습니다.(앞으로 학습할 예제들도 포함되어 있습니다.) 추천하는 방법은 두 번째로, 저장소에서 모두 가져온 다음 즐겨쓰는 텍스트 편집기에서 열어두고 학습하는 것입니다.

시작하기 전에 확실히 집고 넘어가야 하는 것이 있습니다. WebGL에서 3D장면 연출을 위해 애니메이션을 부여하는 것은 아주 간단합니다. 다른 장면을 연속해서 그릴려낼 뿐이니까요. 이 사실을 알고 난 후 저는 조금 허무했습니다. 당연하다고 말할 수 있을지도 모르지만, 제가 혼란스러워하는 이유는 이 보다 한층 더 높은 수준의 추상화를 사용하는 것이라고 생각했기 때문입니다. 예를 들자면, X지점에 그려진 사각형이 있습니다. 사각형을 이동하기 위하여 3D시스템에 Y지점까지 이동하라고 명령하면 자동으로 애니메이션을 제공해 줄 것이라고 생각했습니다. 그러나 실제로는 그렇지 않았고, X지점에 있는 사각형을 Y지점으로 이동하는 동작을 그려낸 후 Z지점까지 이동하는 동작을 매 프레임마다 계속해서 3D시스템에 주문하여 애니메이션을 구현해야 했습니다.

초기화 함수 - webGLStart

어쨋든, 이것이 뜻하는 바는 drawScene이라 불리우는 함수를 반복적으로 호출할 수 밖에 없다는 것입니다. 애니메이션하기 위해서는 뭔가 약간씩 달라지도록 그리는 처리를 직접해야 하는 것입니다. 자, 이제 예제 코드의 하단으로 이동하여 초기화 함수인 webGLStart의 변화를 살펴봅시다.

function webGLStart() {
    var canvas = document.getElementById("lesson03-canvas");
    initGL(canvas);
    initShaders();
    initTexture();

    gl.clearColor(0.0, 0.0, 0.0, 1.0);
    gl.enable(gl.DEPTH_TEST);

    tick(); // changed
  }

유일한 변화는 함수의 마지막에 drawScene 대신에 tick이라는 함수를 호출한 것입니다. 이 함수는 화면 갱신을 위해 정해진 시간에 의해 정기적으로 뭔가를 그려내는 일을 합니다. 예를 들면, 81도로 회전한 삼각형을 82도로 회전하는 과정을 수행합니다. 위로 조금 이동하면 다음과 같은 함수를 발견할 수 있습니다.

재귀함수 - tick

function tick() {
    requestAnimFrame(tick);

이 라인은 다음에 그려질 화면을 호출하는 과정입니다. requestAnimFrame 함수는 구글에서 제공하는 유틸리티 라이브러리인데 페이지의 상단에 위치한 <script> 태그에서 webgl-utils.js파일을 로드하여 사용할 수 있습니다. 이 라이브러리는 브라우저마다 독립적으로 구현된 기능(WebGL 장면을 다시 그려내는 시기를 브라우저에게 묻는 기능)을 하나로 통합하여 사용할 수 있게 합니다. 파이어폭스는 mozRequestAnimationFrame을 호출하고 크롬과 사파리는 webkitRequestAnimationFrame을 호출해야 합니다. 나중에는 requestAnimationFrame으로 통합될 것으로 예상되지만, 그 이전에는 이 라이브러리의 도움을 받기로 합니다.

requestAnimFrame은 drawScene 함수가 정기적으로 호출되는 효과를 가집니다. 자바스크립트 내장함수인 setInterval을 사용할 수도 있었지만(예전에는 실제로도 많이 사용되었음), 사용자가 WebGL로 구현된 페이지를 보고있지 않아도(혹은 다른 탭을 조회 중) 지속적으로 작동하기 때문에 브라우저의 전반적인 성능이 떨어지는 문제가 있어 이와 같은 자체 함수를 제공하기 시작한 것입니다. 즉, 방문객이 WebGL로 구현된 화면을 조회하고 있을 때에만 발동하는 것입니다. tick 함수의 나머지를 보면,

    drawScene();
    animate();
  }

tick함수는 자기자신을 다음번에 다시 호출해 줄 것을 requestAnimFrame에 예약해 두고 drawScene과 animate 두개의 함수를 실행합니다.

장면 그리기 - drawScene

drawScene함수는 index.html 소스의 2/3정도로 스크롤하면 발견할 수 있습니다. 먼저 눈에 들어온는 것은 함수 앞에 새롭게 정의된 두개의 전역 변수입니다.

  var rTri = 0;
  var rSquare = 0;

이들은 각 삼각형과 사각형의 회전상태를 보존할 것입니다. 모두 0도에서 시작하여 매 시간마다 조금씩 회전수치가 증가합니다.(여담 입니다만, 이런식의 전역 변수를 사용하는 것은 실제로 좋지 않은 방법입니다. 레슨 9에서 더 우아한 구조를 보여드리겠습니다.)

drawScene 함수의 변경사항은 삼각형을 그리는 부분입니다. 그 부분의 코드를 모두 올립니다, 새롭게 추가되거나 변경된 부분은 주석으로 표시했습니다.

    mat4.perspective(45, gl.viewportWidth / gl.viewportHeight, 0.1, 100.0, pMatrix);

    mat4.identity(mvMatrix);

    mat4.translate(mvMatrix, [-1.5, 0.0, -7.0]);

    mvPushMatrix(); // added
    mvRotate(rTri, [0, 1, 0]); // added

    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);

    mvPopMatrix();  // added

여기서 무슨 일이 발생했는지 설명하기 위하여 레슨 1로 돌아가 봅시다. 다음과 같이 말했습니다.

OpenGL은 장면을 그릴 때 대상 물체마다 현재(Current)의 위치와 회전값을 전달합니다. - 예를 들어, "20 만큼 전진하고 32도 만큼 회전하여 로봇을 그려"라는 식으로 말이죠. 실제로 복잡한 부분은 "요만큼 움직였고 이만큼 회전하여 그렸다"라는 지침이 있습니다. 이것은 "로봇을 그려라"라는 코드 한개의 함수로 캡슐화 할 수 있으므로 매우 편리합니다. 그리고 함수 호출 전에 이동 및 회전값을 변경함으로써 마음대로 로봇을 이동시킬 수 있습니다.

그렇습니다. model-view matrix에는 현재의 상태값이 보존되어 있습니다. 다음 코드를 봅시다.

    mat4.rotate(mvMatrix, degToRad(rTri), [0, 1, 0]);

아주 명백하지요. model-view matrix에 보존되어 있는 현재 회전수치를 수직 축 주위의 rTri에 표시된 각도만큼 회전하도록 변경합니다.(축이 두 번째 인수에 배열로 지정됩니다.) 이것은 삼각형을 그릴 때, rTri도 만큼 회전하고 있다는 것입니다. mat4.rotate에 의해 앵글의 각도가 변경됩니다. 여기에 사용된 degToRad 함수는 각도로 변환해 주는 간단한 일을 처리합니다.

그렇다면, mvPushMatrix과 mvPopMatrix는 무엇일까요? 이름에서 알 수 있을지도 모르지만, 이것들도 model-view matrix와 관련이 있습니다. 로봇을 그리는 비유로 돌아가 보면, A지점으로 이동하는 로봇을 그리려고 합니다. 최고 수준의 추상화로써 A지점으로 이동하는 구간의 일부분(옵셋) 만큼 이동하고 주전자를 그립니다. 로봇을 그리는 코드는 model-view matrix에서 변경됩니다. 이것은 로봇의 몸체에 적용되어 시작되며, 아래에는 다리, 위에는 머리, 그리고 팔이 붙어있습니다. 여기서 문제는 이처럼 A지점에서 옵셋만큼 이동 시키려고했을 때 발생합니다. A지점이 아니라 마지막으로 그린​​ 것에 대해 옵셋만큼 이동하기 때문에 로봇의 팔에 들려있는 주전자는 뜬 상태로 렌더링됩니다. 이것은 의도대로 되지 않아요.(?)

요구되는 분명한 것은 로봇을 그리기 전에 model-view matrix를 보존할 방법과 다시 되돌릴 방법 이 되겠군요. 예 그렇습니다, mvPushMatrix과 mvPopMatrix에서 이것을 수행합니다. mvPushMatrix는 매트릭스를 스택(Stack)에 쌓습니다. mvPopMatrix는 스택에서 매트릭스를 꺼내 현재의 매트릭스를 바꿉니다. 스택을 이용한다는 것은 여러 단계의 중첩 그리기 코드의 비트를 이용할 수 있다는 것입니다. 각각 그리기 코드 안에서 model-view matrix를 조작하여 그려낸 후 복구합니다. 따라서 회전하는 삼각형의 그리기가 완료되면 mvPopMatrix을 통해 model-view matrix를 리턴합니다.(?) 다음 코드를 봅시다.

    mat4.translate(mvMatrix, [3.0, 0.0, 0.0]);

...이 회전하지 않는 기준 좌표계에서의 장면을 가로질러 (model-view matrix를) 이동하도록 합니다. (만약 아직도 이 의미를 잘 모르겠다면, 코드를 복사하여 push/pop을 삭제한 후 실행하여 무슨 일이 일어나는가를 관찰하는 것이 좋습니다. 직접 보면 금세 알아차릴 수 있을 것입니다.)

이 세가지 변화는 삼각형이 사각형에 영향을 주지 않고 중심에서 수직축을 중심으로 회전합니다. 또한 사각형의 수평축을 중심으로 주위를 회​​전하는 유사한 세가지를 처리해 줍니다.

    mvPushMatrix(); // added
    mat4.rotate(mvMatrix, degToRad(rSquare), [1, 0, 0]); // added

    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);

    mvPopMatrix(); // added
  }

...그리고 이것으로 drawScene 코드의 모든 변경은 끝났습니다.

살아 움직이도록 - animate

장면을 애니메이션하기 위해 필요한 또 다른 것은 각각의 렌더링에서 조금씩 다른 장면을 그릴 수 있도록 rTri과 rSquare의 값이 시간의 흐름에 따라 변경되도록 하는 것입니다. 이 과정은 drawScene과 마찬가지로 주기적으로 호출되는 animate라는 새롭게 추가된 함수에서 수행합니다. 다음 코드를 보시죠.

  var lastTime = 0;
  function animate() {
    var timeNow = new Date().getTime();
    if (lastTime != 0) {
      var elapsed = timeNow - lastTime;

      rTri += (90 * elapsed) / 1000.0;
      rSquare += (75 * elapsed) / 1000.0;
    }
    lastTime = timeNow;
  }

장면을 애니메이션하는 간단한 방법은 animate 함수가 호출될 때 마다 일정한 값을 단순히 더하는 것입니다.(이 레슨의 기반이 되는 원래 OpenGL 레슨도 그렇게하고 있습니다) 그러나 여기에는 좀 더 좋은 방법이라고 생각되는 방법을 선택했습니다. 개체의 회전량은 이전 함수 호출에서 얼마나 경과했는지에 따라 결정됩니다. 좀더 정확히 말하면, 삼각형은 1초에 90도 사각형은 1초 동안 75도 회전합니다. 이 방법의 좋은 점은, 컴퓨터의 연산 처리능력에 상관없이 같은 속도로 움직이는 장면을 볼 수 있게 되는 것입니다. 느린 컴퓨터를 사용하는 사람은 단순히 움직임이 끊겨 보이게 될 뿐이죠. 이 것은 이번 레슨과 같은 간단한 데모에서는 문제없이 사용할 수 있지만, 게임과 같은 경우에는 분명히 문제가 됩니다.

나머지 추가한 코드들

이것으로, 주기적으로 호출되는 animate 함수와 drawScene 함수에 대하여 알아 보았습니다. 이제 추가적인 코드인 mvPushMatrix와 mvPopMatrix를 살펴봅시다.

  var mvMatrix = mat4.create();
  var mvMatrixStack = []; // added
  var pMatrix = mat4.create();

  // added
  function mvPushMatrix() {
    var copy = mat4.create();
    mat4.set(mvMatrix, copy);
    mvMatrixStack.push(copy);
  }

  function mvPopMatrix() {
    if (mvMatrixStack.length == 0) {
      throw "Invalid popMatrix!";
    }
    mvMatrix = mvMatrixStack.pop();
  }

놀랄만한 것은 아무것도 없네요. 매트릭스를 유지하기 위한 스택를 가지고 적절하게 push와 pop이 되도록 정의하고 있습니다. 마지막으로 압서 언급한 degToRad 함수를 살펴 보도록 하겠습니다. 학교에서 가르쳐주는 수학을 기억한다면 그리 놀랄일은 없습니다.

    function degToRad(degrees) {
        return degrees * Math.PI / 180;
    }

이것으로 끝입니다. 더 이상의 변경사항은 없습니다. 이제 당신은 WebGL을 이용하여 간단한 애니메이션을 장면에 담는 방법을 알게되었습니다. 의견이나 수정할 곳이 있으면 아래에 댓글로 남겨주세요. 그리고, 다음 레슨에서는 2D개체를 3D월드에 존재하는 진짜 3D개체로 만듭니다. 다음 레슨으로 이동하시려면 여기를 클릭하세요.

이 문서의 원본은 WebGL Lesson 3 – a bit of movement입니다.

Comments

두 번째 WebGL 레슨에 오신 것을 환영합니다. 레슨 1에서 만든 삼각형과 사각형에 색상을 추가하는 방법에 대하여 알아보겠습니다. 이번 학습 역시 NeHe OpenGL 세 번째 튜토리얼을 바탕으로 하고 있습니다.

다음은 학습할 예제를 WebGL 지원 브라우저에서 실행하여 얻어낸 결과물의 그림입니다.
static.png
WebGL을 지원하는 브라우저를 사용중이라면 여기를 클릭하여 실재로 작동하는 WebGL 버전을 확인할 수 있습니다. 만약 지원하지 않는 브라우저를 사용중이라면 레슨 0을 참고하여 설치하세요.

자, 이제 어떻게 작동하는지 자세히 살펴 보도록합시다.

시작에 앞서...

이 레슨은 충분한 자바스크립트 프로그래밍 지식이 있지만, 3D 그래픽 구현 경험이 없는 사람들을 대상으로 하고 있습니다. 그리고 가능한 한 빨리 자신만의 3D를 구사하여 결과물을 만들수 있도록 하는 것에 목적을 둡니다. 레슨에 사용된 예제에서 실재 구현된 코드를 분석하여 어떤 일이 일어나는지 알아보고 그것을 이해하여 스스로 응용할 수 있도록 하는 것입니다. 만약 당신이 첫 번째 레슨을 학습하지 않았다면 먼저 읽어 두어야 합니다. - 이 단원에서는 단지 이전 코드에서 새롭게 추가된 코드에 대해서만 설명합니다.

이전과 동일하게 학습내용 중에는 버그 혹은 오류가 있을 수도 있습니다. 뭔가 잘못된 것을 발견하면 댓글로 알려주세요. 최대한 빨리 고치도록 하겠습니다.

여기에 사용된 예제의 소스코드를 얻는 방법은 두가지가 있습니다. 실제 예제가 작동하는 곳에서 "소스 보기"를 선택하거나, GitHub를 사용할 수 있다면 저장소에서 동일한 예제를 다운로드할 수 있습니다.(앞으로 학습할 예제들도 포함되어 있습니다.) 추천하는 방법은 두 번째로, 저장소에서 모두 가져온 다음 즐겨쓰는 텍스트 편집기에서 열어두고 학습하는 것입니다.

학습 순서

대부분의 코드는 첫 번째 예제와 매우 비슷합니다. 그리고 이번에는 위에서부터 아래로 진행하도록 하겠습니다.

  • "x-shader/x-vertex"와 "x-shader/x-gragment"타입을 가진 <script>태그를 사용하여 vertex shader와 fragment shader를 정의하기
  • WebGL 문맥을 inigGL에서 초기화 하기
  • 쉐이더를 getShader과 initShaders을 통해 WebGL program object에 로드하기
  • model-view matrix인 mvMatrix와 투명 매트릭스인 pMatrix를 정의하고 setMatrixUniforms를 추가하여 쉐이더를 볼 수 있게 하기
  • initBuffers를 이용하여 장면에 있는 객체에 대한 정보를 포함하는 buffer 로드하기
  • drawScene 함수에서 장면 그리기
  • webGLStart 함수를 정의하고 초기화 하기
  • 끝으로, 최소한으로 출력될 HTML 준비하기

첫 레슨과 비교하여 코드가 변동되는 부분은 쉐이더, initBuffers, 그리고 drawScene함수입니다. 그리고 WebGL 렌더링 파이프라인에 대하여 알아보기 위해 아래와 같은 다이어그램을 이용하여 처리과정을 설명합니다.

WebGL 렌더링 파이프라인

simple-rendering-pipeline.png

이 다이어그램은 매우 단순화된 형태로 보여줍니다. drawScene 자바스크립트 함수에 전달된 데이터가 화면의 WebGL canvas에 표시되는 픽셀로 설정되어 있습니다. 이것은 단지 학습에 필요한 부분만을 담고 있습니다. 향후 레슨에서 더욱 자세한 다이어그램을 보게 될 것입니다.

가장 높은 수준의 프로세스는 다음과 같이 설명할 수 있습니다. drawArrays와 같은 함수를 호출할 때 마다 WebGL은 애트리뷰트의 형태로 주어진(레슨 1에서 버텍스로 사용된 버퍼와 같은) 데이터를 uniform 변수로 (projection matrix와 model-view matrix를 위해 사용) 처리하고 vertex shader에 전달합니다.

이것은 각각의 vertex shader에 한개 버텍스에 대해 적절하게 설정된 애트리뷰트를 함께 호출합니다. uniform 변수도 전달되지만, 이름이 암시 하듯이 uniform 변수는 각 호출에 변화하지 않습니다. vertex shader는 이 데이터로 작업을 수행합니다 - 레슨 1에서는 projection matrix와 model-view 현재 상태에 따라 외관에 버텍스이 이동하도록 적용되었습니다. 그리고 결과를 varying 변수에 담았습니다. vertex shader는 여러 varying 변수를 출력할 수 있습니다. gl_Position의 내용은 필수이며, vertex shader 처리를 마친 후 버텍스 좌표가 들어 있습니다.

vertex shader의 처리가 끝나면 WebGL은 varying 변수로 이루어진 3차원 이미지를 2차원 이미지로 변환하는 마술을 부립니다. 그리고 이미지의 픽셀마다 fragment shader를 입힙니다.(일부 3차원 그래픽 시스템에서는 fragment shader를 pixel shader라고 말하는 것은 바로 이 때문입니다) 물론 이것은 fragment shader 픽셀에 버텍스가 없어도 처리할수 있음을 의미합니다. 즉, 픽셀 사이의 버텍스를 감싸는 것입니다. 이 경우, 버텍스 사이의 좌표지점을 채우는데, 이것을 선형 보간(linear interpolation)이라고 합니다. 이 과정에서 버텍스 사이의 공간을 채워넣습니다. 이리하여 포인트와 버텍스로 삼각형을 만들 수 있습니다. fragment shader의 목적은 이러한 보정 포인트 각각의 색상을 반환하는 데 있으며, gl_FragColor라는 varying 변수에 저장하여 수행합니다.

fragment shader의 처리가 끝나면, 그 결과를 WebGL에서 약간 가공한 후(이것 역시 다음 학습에서 알아보겠습니다) 프레임 버퍼에 기록합니다. 그리고 이것이 마지막으로 화면에 표시되는 것입니다.

버텍스의 색상 가져오기

바라건대, 이번 레슨에서 말하고자 하는 가장 중요한 트릭은 자바스크립트 코드로부터 fragment shader에 있는 버텍스의 색상을 얻는 방법입니다. fragment shader에는 직접 액세스 할 수 없으니까요.

우리는 위치뿐만 아니라 복수의 varying 변수 vertex shader에서 전달, fragment shader에서 꺼낼 수있다는 사실을 이용하는 방법입니다. 즉 색을 vertex shader로 전달하고 그것을 그대로 varying 변수에 저장하여 ragment shader에서 가져옵니다.

편리하게도, 이렇게하면 손쉽게 그라디언트 색상을 얻을수 있습니다. vertex shader로 설정되는 모든 varying 변수는 버텍스 간의 조각이 생성되는 동안 선형 보간됩니다. 위치뿐만 아니라, 버텍스 사이 색상 선형 보간과 더 매끄러운 그라디언트 생상을 얻을 수 있습니다. 그것은 위의 삼각형 이미지에서 보는 것과 같습니다.

이제 코드를 봅시다.

  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;
  }

이것이 의미하는 것은 aVertexPosition과 aVertexColor라는 두 애트리뷰트가 있습니다. - 버텍스마다 다른 입력 - uMVMatrix와 uPMatrix라는 두 non-varying uniform 변수가 있습니다. 그리고 vColor라는 varying 변수로 출력되는 것입니다.

쉐이더 본체에서 gl_Position(이것은 모든 vertex shader에서 varyin 변수를 암시적으로 정의)을 계산하는 법을 레슨 1에서 학습한 것과 동일한 방법으로 계산합니다. 그리고 색상과 관련된 모든 입력 애트리뷰트에서 출력할 varying 변수에 그대로 전달합니다.

이것은 각각의 버텍스에 대하여 작업을 수행하면, 조각을 보관하고 생성하여 fragment shader로 전달됩니다.

  #ifdef GL_ES
  precision highp float;
  #endif

  varying vec4 vColor;

  void main(void) {
    gl_FragColor = vColor;
  }

색상 반환하기

다음은 부동 소수점 정밀 사용구 뒤에 선형 보간 결과 부드럽게 혼합된 색상을 포함하여 입력 varying 변수인 vColor을 받고 즉시 이 부분의 색상으로 반환합니다.

  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);

    // new code start
    shaderProgram.vertexColorAttribute = gl.getAttribLocation(shaderProgram, "aVertexColor");
    gl.enableVertexAttribArray(shaderProgram.vertexColorAttribute);
    // new code end

    shaderProgram.pMatrixUniform = gl.getUniformLocation(shaderProgram, "uPMatrix");
    shaderProgram.mvMatrixUniform = gl.getUniformLocation(shaderProgram, "uMVMatrix");
  }

애트리뷰트의 위치를 검색하는 이 코드는 이전 단원에서는 어느정도 진행했지만, 이제는 그 의미가 명백해 졌습니다. vertex shader로 전달하고 애트리뷰트에 대한 참조를 가져오기 위한 수단입니다. 레슨 1에서는 각각의 버텍스 좌표의 애트리뷰트만을 얻는 방법을 다루었습니다. 이번에는 색상의 애트리뷰트를 가져옵니다.

색상 버퍼 정의하기

이전 단원에서 남아있는 변경 사항은 initBuffers에서 버텍스 좌표와 색상의 버퍼를 모두 제공해야 한다는 점과 drawScene에서 이들을 모두 WebGL에 전달 할 필요가 있다는 점입니다.

처음 initBuffers을 보면 삼각형과 사각형의 색상 버퍼를 저장하는 전역 변수를 새롭게 정의합니다.

  var triangleVertexPositionBuffer;
  var triangleVertexColorBuffer; // new code
  var squareVertexPositionBuffer;
  var squareVertexColorBuffer; // new code

그리고 삼각형의 버텍스 좌표의 버퍼를 만든 직후, 버텍스의 색상을 지정합니다.

  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;

    // new code start
    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;

색상을 위해 준비한 값은 하나의 목록에 저장된 각각의 버텍스에 하나의 값을 설정할 수 있습니다. 그런데 색상 버퍼와 버텍스 좌표의 버퍼배열 사이에 흥미로운 차이가 있습니다. 각 버텍스 좌표는 세 숫자, 즉 X, Y, Z 좌표로 지정되는 반면, 각 색상은 네 가지 요소, 즉 빨강, 녹색, 파랑, 알파에 의해 지정됩니다. 알파에 대하여 생소한 사람을 위해 보충하자면, 알파는 투명도를 나타내는 것으로 값은(0 투명 1 불투명) 이고, 나중 학습에서 유용하게 사용됩니다. 이 속성당 요소수의 차이가 발생하여 버퍼에 연결된 itemSize 값을 변경할 필요가 있습니다.

다음은, 사각형에도 상응하는 코드를 수행합니다. 이번에는 같은 색상을 각 꼭지점에 사용하기 때문에 루프 버퍼값을 생성합니다.

    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;

    // new code start
    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;

개체의 데이터를 4개의 버퍼 모임으로 모두 제공하기 때문에, 다음과 같은 변경은 drawScene에서 새 데이터를 사용하는 것입니다. 새로운 코드는 또한 주석으로 강조되어 쉽게 파악할 수 있습니다.

  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);

    // new code start
    gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexColorBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, triangleVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
    // new code end

    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);

    // new code start
    gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexColorBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexColorAttribute, squareVertexColorBuffer.itemSize, gl.FLOAT, false, 0, 0);
    // new code end

    setMatrixUniforms();
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, squareVertexPositionBuffer.numItems);
  }

이것이 WebGL 장면에 색상을 추가하는 데 필요한 모든 것입니다. 그리고 지금은 쉐이더의 기초를 통하여 어떻게 데이터가 쉐이더 간에 전달되는가에 대하여 공부했습니다.

이번 수업은 여기까지입니다. 처음 학습에 비해 쉽게 진행했을 것으로 간주됩니다. 만약 질문, 의견, 수정이 있으면 아래에 댓글을 남겨주세요. 다음 레슨에서는 지금까지 만든 삼각형과 사각형을 이용하여 회전하는 애니메이션을 만들어 보도록합니다.

이 문서의 원본은 WebGL Lesson 2 – Adding colour입니다.

Comments