Rev. 2.73

세 번째 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

첫 번째 WebGL 레슨에 오신것을 환영합니다. 이 학습은 게임 개발자를 위한 3D 그래픽 학습 수단으로 인기가 높은 NeHe OpenGL 두 번째 튜토리얼을 바탕으로 하고 있습니다. 고작 삼각형과 사각형이라니, 어찌보면 흥미가 떨어질만한 주제일지도 모르겠지만, 이 단원은 WebGL을 배우는 기초의 중요한 도입부입니다. 이 레슨을 이해할 수 있다면 나머지 레슨 역시 쉽게 배울 것입니다.

자, 여기에 삼각형과 사각형 그림이 있습니다.
static.png
WebGL을 지원하는 브라우저를 사용중이라면 여기를 클릭하여 실재 WebGL 버전을 확인할 수 있습니다. 지원하는 브라우저에 대한 정보는 레슨 0을 참고하세요.

지금부터 작동방법에 대하여 알아봅시다.

시작에 앞서...

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

저는 이 레슨을 스스로 학습하면서 작성했습니다. 그래서 분명히 효율적이지 못합니다. 효율적이지 못한 부분을 알아차렸다면 당신의 자산으로 삼아 이용하세요. 그리고 만약 뭔가 실수를 발견했다면 댓글을 통해 알려주세요. 즉시 수정 반영하도록 하겠습니다.

여기에 사용된 예제의 소스코드를 얻는 방법은 두가지가 있습니다. 실제 예제가 작동하는 곳에서 "소스 보기"를 선택하거나, GitHub를 사용할 수 있다면 저장소에서 동일한 예제를 다운로드할 수 있습니다.(앞으로 학습할 예제들도 포함되어 있습니다.) 추천하는 방법은 두 번째로, 저장소에서 모두 가져온 다음 즐겨쓰는 텍스트 편집기에서 열어두고 학습하는 것입니다. 이 단원은 OpenGL에 대한 지식이 있다해도 도입부 부터 두개의 쉐이더를 정의해 버리기 때문에 어리둥절 할 수도 있습니다. 쉐이더는 일반적으로 고도의 기술로 여겨지고 있지만, 좌절하지 마세요. 사실 생각보다 훨씬 쉽습니다.

다른 많은 프로그램과 마찬가지로 이 WebGL 예제는 저수준 함수를 정의하는 것으로부터 시작됩니다. 그리고 아래쪽에있는 코드에서 고수준 함수를 호출합니다. 그래서 이를 설명하기 위해 프로그램에 대한 설명은 아래에서 위로 진행합니다. 만약 지금 소스 코드를 보고 있다면 제일 아래로 건너뛰세요.

HTML 마크업

다음과 같은 HTML코드가 보입니다.

<body onload="webGLStart();">
  <a href="http://learningwebgl.com/blog/?p=28">&lt;&lt; Back to Lesson 1</a><br />

  <canvas id="lesson01-canvas" style="border: none;" width="500" height="500"></canvas>

  <br/>
  <a href="http://learningwebgl.com/blog/?p=28">&lt;&lt; Back to Lesson 1</a><br />
</body>

이것은 페이지의 body에 속한 부분입니다. 다른 모든 것은 자바스크립트 속에 있습니다. (만약 "소스 보기"로 코드를 보고있다면, 사이트 접속 분석에 이용하는 추가 코드가 보입니다만 무시해도 됩니다.)

물론 <body>안에는 더욱 다양한 HTML태그를 추가하여 WebGL의 이미지를 볼 수 있습니다. 그러나 이 간단한 데모는 블로그로 이동하는 링크와 WebGL로 만든 3D 그래픽을 표시하는 <canvas>태그가 있을 뿐입니다.

Canvas 요소는 HTML5에서 새롭게 추가되었습다. 이것은 웹페이지에 2D와 WebGL을 통한 3D를 자바스크립트를 사용하여 렌더링하는 새로운 수단입니다. 아래 코드는 <canvas>요소의 레이아웃을 조정하는 간단한 속성만을 사용합니다. webGLStart라는 자바스크립트 함수에 선언되어 있으며 페이지가 로드될 때 한 번만 실행하도록 구성되었습니다.

WebGL 초기화 - webGLStart

이제 스크롤바를 조금 위로 올려 webGLStart() 함수를 보도록 합니다.

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

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

    drawScene();
  }

이러한 함수 호출을 보통 "초기화 한다."라고 합니다. 이 함수는 WebGL 3D 렌더링의 대상이 되는 canvas 요소를 전달하고 앞서 언급한 쉐이더를 초기화 합니다. 또한 initBuffers 함수를 호출하여 특정 버퍼(Buffer)의 초기화도 실행합니다. 여기에서의 버퍼는 지금부터 그려야 할 삼각형과 사각형에 대한 정보를 유지하는 데 사용됩니다. 다음은 initGL에 의하여 만들어진 엔진인 gl객체에서 배경색상을 검게하는 메서드와 depth test를 위한 값을 기입하여 WebGL을 사용할 기본 설정을 구성합니다. 마지막에 호출한 drawScene 함수는 setInterval을 이용하여 매 0.015초 간격으로 화면을 갱신하게 됩니다. 이 함수는 버퍼를 사용하여 개체를 그립니다.

WebGL이 어떻게 작동하는지 이해하기 위해서는 initGL과 initShaders 함수가 매우 중요한 역할을 하지만 이들 함수는 나중에 자세히 다루도록 하고 initBuffers와 drawScene을 먼저 살펴보도록 합니다.

  var triangleVertexPositionBuffer;
  var squareVertexPositionBuffer;

우선, 버퍼를 저장하기 위한 두개의 전역 변수를 정의합니다.(실제 WebGL을 구현할 때 마다 전역 변수를 사용해야 하는 것은 아닙니다. 이 레슨에서는 코드를 단순화하기 위해 사용했습니다.)

버퍼설정 - initBuffers

  function initBuffers() {
    triangleVertexPositionBuffer = gl.createBuffer();

삼각형의 버텍스(Vertex, 꼭지점 혹은 정점) 좌표를 기록하기 위해 미리 만들어 두었던 전역 변수에 버퍼를 생성하여 할당합니다. 여기에서 꼭지점은 그리려고 하는 도형이 가상 공간에서 위치하게 될 좌표정보입니다. 삼각형은 3개의 꼭지점을 갖는 것입니다. 버퍼는 사실 그래픽 카드의 메모리입니다. 꼭지점 좌표들을 초기화하는 동안 그래픽 카드는 버퍼에 담긴 정보로 다시 그려내기 때문에 프로그램을 매우 효율적으로 작성할 수 있습니다. 물론 이번 레슨에는 단 3개의 버텍스만을 보내고 있기 때문에 그래픽 카드로 전송하는 비용은 거의 들지 않습니다만, 거대한 모델을 취급하게 되는 상황에서 이 방법은 엄청난 장점이 있습니다.

   gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);

이 라인은 WebGL에게 행동중인 버퍼에 대한 작업은 모두 인수로 지정된 버퍼에 의해 실행하도록 알려주는 것입니다. 이 작업에서 대상의 버퍼를 지정하는 것이 아니라, 미리 "current array buffer"를 지정하고 거기에 대한 작업을 수행한다는 개념은 항상 따라다닙니다. 이상하지요? 이렇게 하는 것은 성능상의 이유가 숨어있습니다.

    var vertices = [
         0.0,  1.0,  0.0,
        -1.0, -1.0,  0.0,
         1.0, -1.0,  0.0
    ];

위 코드는 자바스크립트 배열로 버텍스 좌표를 정의한 것입니다. 이 좌표를 자세히 보면 중심이 (0,0,0)인 이등변 삼각형인 것을 유추할 수 있습니다.

  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);

이번에는 앞서 작성한 자바스크립트 배열을 바탕으로 Float32Array 객체를 만들고 "Current Buffer" 값을 설정하여 WebGL에게 알려줍니다. 물론 "Current Buffer"는 triangleVertexPositionBuffer입니다. Float32Array 대해서는 향후 레슨에서 다루도록 하겠습니다. 일단 지금 알아 두어야 할 것은, 이 방법으로 자바스크립트 배열을 WebGL에서 사용할 수 있는 버퍼의 형태인 Float32Array로 변경하여 넘겨줄 수 있다는 것입니다.

    triangleVertexPositionBuffer.itemSize = 3;
    triangleVertexPositionBuffer.numItems = 3;

버퍼의 마지막 작업은 두개의 새로운 속성을 추가하는 것입니다. 이것은 WebGL에 내장된 기능을 수행하지는 않습니다만 나중에 아주 유용하게 사용됩니다. 자바스크립트의 좋은 점은(나쁜 점이라고 하기도 하지만) 각 객체별 속성을 설정하기 위하여, 명시적으로 선언하는 것을 필요로하지 않다는 것입니다. 원래 버퍼객체는 itemSize와 numItems속성을 가지고 있지 않지만, 이제는 위와 같은 두 속성들을 가지게 되었습니다. 이 버퍼 속성으로 9개의 값으로 부터 분리된 3개의 버텍스 좌표정보(numItems)가 있고 각각은 3개의 숫자(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;
  }

이 모든 것들은 분명해야 합니다. 사각형은 4개의 버텍스를 가지므로 삼각형 보다 큰 numItems값을 가지고 있습니다.

좋아요, 이것으로 두개의 도형이 가진 버텍스 정보를 그래픽 카드에 보낼 수있게 되었습니다. 지금부터 만들어진 버퍼를 이용하여 실제로 이 도형들을 그리기 위한 함수인 drawScene를 차근차근 살펴보도록 하겠습니다.

장면 그리기 - drawScene

  function drawScene() {
    gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);

첫 번째 단계는 viewport 함수를 사용하여 WebGL에 canvas의 크기를 알려주는 것입니다. 왜 이 작업이 중요 한지에 대해서는 한참 이후의 단원에서 다루게 됩니다. 지금 최소한 기억해야 할 것은 실재로 도형을 그리기 전에 canvas의 크기를 정하는 함수를 호출하는 것입니다. 다음 코드는 다음 장면이 그려질 것을 대비하여 canvas의 내용을 지우는 것입니다.

    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

... 그리고

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

여기에 장면을 어떻게 보일지에 대한 설정을 위해 퍼스펙티브(perspective, "원근법")를 설정합니다. 기본적으로 WebGL은 가까운 것과 먼 것을 같은 크기로 그립니다.(3D 용어, orthographic projection - "직각 투영"이라고 합니다.) 그래서 먼 것이 작게 보이도록하는 WebGL의 퍼스펙티브에 대한 설정이 약간 필요합니다. 여기서는 장면(수직) 시야각으로 45도, canvas의 너비와 높이 비율 시점에 더 가까이 가면 숨기는 거리로 0.1, 더 이상 멀어지면 숨기라는 거리로 100을 설정합니다.

위에서 볼 수 있듯이, perspective 함수는 mat4라는 함수 모듈을 이용하여 호출합니다. 이 함수 모듈은 매우 유용하지만 WebGL에 기본으로 포함되어 있지 않기 때문에 유틸리티 함수를 이용하여 정의하고 있습니다. 이 함수의 작동에 대한 자세한 설명은 나중에 다루겠지만, 세세한 것을 몰라도 손쉽게 사용할 수 있습니다.

퍼스펙티브 설정이 끝났으니 이제 출력하는 코드를 만들 수 있습니다.

    mat4.identity(mvMatrix);

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

현재 위치와 회전은 매트릭스(Matrix, 행렬)에 유지되고 있습니다. 당신이 과거 학교에서 배운대로 매트릭스 이동(어디에서 어디로 이동하는지), 회전 혹은 다른 기하학적인 변형을 나타낼 수 있습니다. 더 자세한 내용으로 접근할 수 없지만, 4x4 행렬(3x3가 아닌) 한개로 3D 공간 사이의 어떤 변형을 나타낼 수 있습니다. 처음에는 identity matrix(단위 행렬)부터 시작합니다 - identity matrix는 아무런 변화가 없는 행렬입니다. 여기에 첫 번째 전이를 나타내는 매트릭스를 겁니다. 그리고 두 번째 변형을 나타내는 매트릭스를 곱하면 됩니다. 합성된 매트릭스 한개로 전체의 변형을 나타냅니다. 이 현재 이동과 회전을 나타내는데 사용되는 매트릭스를 model-view matrix라고 합니다. 아마도 이미 깨닫고 있다고 생각 합니다만, mat4.identity 함수는 앞으로 model-view matrix에 이동과 회전을 곱할 준비를 위해 identity matrix를 설정합니다. 즉, 3D로 표현하기 위해 움직일 수 있는 위치에서 원점으로 돌아 왔다고 말할 수 있습니다.

주의 깊은 어떤 독자는 매트릭스 이야기의 시작 부분에 "WebGL"가 아니라 "OpenGL"이라고 알려주었습니다. 이것은 mat4.perspective 함수처럼 매트릭스 처리를 위한 WebGL 내장 함수가 아니기 때문입니다. 대신, 스스로 구현 하거나, 이미 존재하는 서드-파티 매트릭스 라이브러리를 사용해야 합니다. - Brandon Jones씨의 glMatrix와 같은 - 유틸리티 함수의 기능에 대한 자세한 내용은 나중에 설명 하겠지만, 다행히도 자세한 내용을 잘 몰라도 사용하는데에 지장이 없다는 것입니다.

자, 이제 canvas 왼쪽 삼각형을 그리는 부분의 코드를 살펴봅시다.

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

mat4.identity 함수에 의해 3D 공간의 중심으로 이동했기 때문에, 좌측 1.5(X 축을 따라 마이너스 방향) 안쪽에는 7(보고있는 사람으로부터 멀어질 Z축을 따라 마이너스 방향) (mat4.translate 예상 했을지도 모르지만, 이것은 지정한 매개 변수에 따라 이동을 위한 매트릭스를 model-view matrix에 거는 낮은 수준의 이동을 위한 기능입니다.)

다음 단계에서는 실제로 렌더링을 시작합니다.

    gl.bindBuffer(gl.ARRAY_BUFFER, triangleVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, triangleVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

앞서 선언한 버퍼들 중 하나를 사용하기 위해 gl.bindBuffer를 호출해서 current buffer를 할당하는 코드를 호출합니다. 여기에 triangleVertexPositionBuffer을 지정하여 WebGL에게 버텍스 좌표로 사용하는 값​​을 전달합니다. 나중에 어떻게 작동하는지 조금 더 자세히 설명 하겠지만 지금은 버퍼에 있는 각 요소가 3개의 숫자로 이루어진 것을 미리 담아 두었던 itemSize 속성을 사용하여 넘겨주고 있다는 사실을 발견할 수 있습니다.

다음은

    setMatrixUniforms();

이것은 WebGL에 현재의 model-view matrix(또한 projection matrix도 있지만 나중에 알아봅니다)를 사용하도록 지시해야 합니다. 그리고 이 함수는 WebGL 내장 함수로 존재하지 않기 때문에 직접 준비해야 합니다. 이 mvMatrix(model-vew matrix) 변수를 변경하여 여러 곳으로 움직이는 것이 가능합니다. 그러나 이것은 전부 자바스크립트에 의해서만 이루어지기 때문에 setMatrixUniforms을 호출하여 매트릭스를 그래픽 카드로 넘겨주는 일을 하게됩니다.

이것이 완료 되면, WebGL은 버텍스의 위치를 가진 숫자 배열과 매트릭스를 알아차리게 되고, 다음과정에서 이 값들을 어떻게 사용하는지 알려줍니다.

    gl.drawArrays(gl.TRIANGLES, 0, triangleVertexPositionBuffer.numItems);

이 코드를 다른 말로한다면 "앞서 전달된 버텍스 배열로 삼각형을 그리세요. 버텍스는 배열의 0번 요소, 그리고 numItems에 위치한 요소로 사용하세요"

여기까지 끝나면 WebGL은 삼각형을 그릴 것입니다. 다음은 사각형을 그려봅시다.

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

model-view matrix를 오른쪽으로 3만큼 이동한 곳에서 시작합니다. 기억하세요. 지금은 이미 좌측 1.5, 스크린에서 7.0 안쪽에 위치하기 때문에 이 결과 오른쪽 1.5, 뒤쪽이 7만큼 떨어진 위치에 있습니다.

다음은

    gl.bindBuffer(gl.ARRAY_BUFFER, squareVertexPositionBuffer);
    gl.vertexAttribPointer(shaderProgram.vertexPositionAttribute, squareVertexPositionBuffer.itemSize, gl.FLOAT, false, 0, 0);

WebGL에게 사각형의 버텍스 버퍼를 알려줍니다.

   setMatrixUniforms();

model-view matrix와 projection matrix를 다시 보냅니다.(마지막에서 mvTranslate 결과가 반영되도록) 이제 드디어 마지막 과정입니다.

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

그리기를 실행하세요. 여기서 triangle strip이 무엇인지 궁금하시죠? 그래요, 이것은 삼각형의 연속들입니다. 처음 3개의 버텍스를 지정하여 삼각형을 그리고 다음 마지막으로 이용한 2개의 버텍스에 새로운 1개의 버텍스를 추가하고 다음의 삼각형을 그리고 또 하나를 추가하는 일을 계속합니다. 이것은 사각형을 지정하는 빠르고 간편한 길이지만 그리 좋은 방식은 아닙니다만, 더 복잡한 형상을 여러 삼각형으로 표현하는 매우 편리한 방법이기도 합니다.

어쨌든 여기까지 끝나면 drawScene 함수는 끝입니다.

  }

실험 - Go crazy!

만약 당신이 여기까지 도달했다면 이제 직접 실험할 준비가 확실히 갖추어진 것입니다. GitHub 또는 "소스 보기"를 통해 얻어낸 파일을 로컬에 복사하세요. 만약 후자의 방법을 선택한다면 index.htmlglMatrix-0.9.4.min.js가 필요합니다. 직접 브라우저에서 실행하여 제대로 작동하는지 확인하세요. 그리고 어떤 버텍스의 좌표를 변경해 보세요. 지금 장면은 완전히 평평하지만 얻을 사각형의 Z값을 2 또는 3으로 변경해 보세요. 위치가 뒤바뀌기 때문에 커지기도합니다. 한개 또는 두개의 버텍스를 변경하여 외관에 발생하는 왜곡을 살펴보세요. Go crazy, and don’t mind me. I’ll wait.

...

자~ 다시 돌아오셨나요? 이제부터는 지금까지 가지고 놀았던 프로그램을 가능하게 지원했던 함수들을 살펴보도록 합니다. 전에 말했듯이, 만약 세부 사항을 모르고 initBuffers와 같은 함수를 그냥 복사해서 쓰는 것에 만족한다면 어쩌면 자세한 부분을 이해하지 않아도 당신은 재미있는 WebGL 페이지를 만들 수 있을지 모릅니다.(비록 흑백이라도 - 색상은 다음 레슨) 하지만 자세한 내용들에는 이해하기 어려운 것이 없어요. 어떻게 작동하는지에 대하여 이해하는 것은 분명히 더 나은 WebGL 프로그램을 개발할 수 있게 되는 것임을 명심하세요.

초기화 함수 - initGL

저와 계속 함께 하시겠어요? 감사합니다. 이제, 가장 지루한 함수를 먼저 끝내 버립시다. 처음에는 webGLStart에서 호출되는 initGL 함수입니다. 이 코드는 웹페이지 상단에 있습니다.

  var gl;
  function initGL(canvas) {
    try {
      gl = canvas.getContext("experimental-webgl");
      gl.viewportWidth = canvas.width;
      gl.viewportHeight = canvas.height;
    } catch(e) {
    }
    if (!gl) {
      alert("Could not initialise WebGL, sorry :-(");
    }
  }

이것은 아주 간단합니다. 당신도 눈치챘을지 모르지만, initBuffers와 drawScene으로 불리는 함수는 자주 gl이라는 개체를 참조했었습니다. 이것은 WebGL의 중심이되는 무언가의 단순한 참조입니다. 이 함수는 그 무언가를 가져옵니다. 그 무언가는 WebGL 컨텍스트라는 것으로, standard context name을 사용하여 canvas로 부터 해당 컨텍스트를 얻을 수 있습니다. 컨텍스트가 생기면 자바스크립트의 객체에 어떤 property를 추가할 수 있는 멋진 기능을 이용하여 canvas 너비와 높이를 저장 해 둡니다. 이 값은 drawScene 함수의 시작 부분 뷰포트와 퍼스펙티브를 설정하는 데 사용할 수 있습니다. 예 그렇습니다. gl은 canvas요소를 통하여 WebGL을 사용하기 위한 컨텍스트입니다.

지원 함수 - mat4.create

initGL()을 호출한 후 webGLStart 함수는 initShaders()를 호출합니다. 물론 이 함수는 쉐이더를 초기화 합니다. 그 코드는 나중에 보기로 하고, 먼저 model-view matrix를 취급하는 유틸리티 함수들을 살펴봅시다. 아래가 바로 그 코드입니다.

  var mvMatrix = mat4.create();
  var pMatrix = mat4.create();

model-view matrix를 유지하기 위한 mvMatrix라는 변수를 정의합니다. mat4.create에 의해 만들어진 이 매트릭스에는 모두 0으로 비어있는 행렬입니다. 당신은 기억할 것입니다. drawScene 함수를 분석하면서 glMatrix에 포함된 mat4.perspective 함수를 이용하여 변수를 할당하여 시점을 설정한 일을 말예요. mat4.perspective 함수는 WebGL 내장 함수가 없기 때문에 사용했었죠. model-view matrix가 물체의 이동 및 회전을 캡슐화 할 수 있는 것과 마찬가지로 멀리 갔다거나, 물체가 거리에 비례하여 가까이 있을 때 보다 작아짐을 나타내기에 아주 적합한 매트릭스입니다. 그리고 mat4.perspective는 당신이 짐작대로 projection matrix를 수행합니다. 인수로 지정된 투시 법을 적용하는 데 필요한 특수 매트릭스를 반환하는 함수입니다.

그렇습니다. 이제 앞에서 설명한 model-view matrix와 projection matrix를 자바스크립트에서 WebGL에 보내기 위해 setMatrixUniforms 함수에서 파생된 모든것을 보고 왔습니다. 그 무서운 쉐이더에 관한 것도 말이죠. 그들은 내부적으로 관련되어 있습니다. 그래서 살짝 그들의 배경을 살펴보도록 하겠습니다.

쉐이더(Shader)란 무엇인가, 생각해 보신적 있나요? 음, 3D 그래픽 역사의 어느 시점에서 그들은 이렇게 생각하고 있었을 것입니다. - 화면에 쓴 부분에 어떤 그늘을 클릭하거나, 무슨 색깔인지 여부를 시스템에 전달하는 코드 질량. 그러나 시간이 흘러 지속적으로 기능이 향상되어 지금은 그리기 전에 하고 싶은 것은 무엇이든 가능한 코드 덩어리 정도로 인식되는 것 같습니다. 그리고 이것은 사실 매우 유용합니다. (a)그래픽 카드에서 작동하기 때문에 매우 빠르게 동작 가능. (b)그들이 할 수 있는 변환 프로세스는 이러한 간단한 예제에서 정말 유용하게 다룰 수 있음.

이러한 간단한 WebGL 예제(적어도 OpenGL 튜토리얼의 "intermediate"입니다)에도 쉐이더를 도입하는 이유는 WebGL의 구조를 이용하기 위해서입니다. 다행히 쉐이더는 그래픽 카드에서 작동하기 때문에 우리의 model-view matrix와 projection matrix에 정의되어 있는 모든 지점과 버텍스를 상대적으로 느린 자바스크립트로 처리하지 않아도 장면을 그릴수 있기 때문입니다. 이것은 믿을 수 없을 정도로 편리하며, 이로인한 추가적인 오버헤드가 발생하더라도 그에 상응하는 가치를 얻을 수 있습니다.

초기화 함수 - initShaders

다시 초기화 부분입니다. webGLStart 함수는 initShaders 함수를 읽고 있었어요. 한 라인씩 살펴보도록 합시다.

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

위와 같이 getShader 함수를 사용하여 "fragment shader"와 "vertex shader" 두 가지를 검색하여 WebGL의 "program"이라는 녀석이 다 붙어 있습니다. Program은 WebGL 시스템 옆에 배치되는 코드의 일부입니다. 이것은 그래픽 카드에서 작동하는 무언가를 지정하는 방법을 간파할 수 있습니다. 기대 했을지도 모르지만 program은 여러 쉐이더와 연결됩니다. 각 쉐이더는 program중인 코드 조각을 알 수 있습니다. 각각의 program은 한개의 fragment shader와 한개의 vertex shader를 유지할 수 있습니다. 그것은 조금 나중에 봅시다.

    shaderProgram.vertexPositionAttribute = gl.getAttribLocation(shaderProgram, "aVertexPosition");
    gl.enableVertexAttribArray(shaderProgram.vertexPositionAttribute);

program이 설치되고 shader가 연결되면 "attribute"에 대한 참조를 검색합니다. 그것은 program의 vertexPositionAttribute라는 새로운 필드에 저장됩니다. 다시 말하지만 자바스크립트의 어떠한 객체든지 다양한 새로운 필드를 추가할 수 있다는 특징이 있습니다. 이를 이용하여 program에 기본적으로 존재하지 않는 vertexPositionAttribute라는 필드를 추가합니다. 필요한 정보를 모아 두는 것이 편리하기 때문에 "attribute"를 program에 해당 할당해 버립니다.

그런데, vertexPosionAttribute은 무엇을 위해 있는 것일까요? 기억 할지 모르지만, drawScene 함수에서 사용되었습니다. 만약 삼각형의 버텍스 좌표를 적절한 버퍼에서 설정하는 코드로 돌아오면, 버퍼와 애트리뷰트를 연계하고 있는 것을 볼 수 있을 것입니다. 분명히 나중에 그 의미를 알 수 있습니다. 지금은 gl.enableVertexAttribArray 함수를 사용하여 WebGL에 배열에서 애트리뷰트 값을 제공하려는 것을 전하고 있다는 부분에만 주목합시다.

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

initShader의 마지막 부분은 program에서 두개의 값을 보유하고 있습니다. uniform variable라고 하는 것의 location입니다. 나중에 빨리 설명하기 때문에 당분간은 애트리뷰트와 비슷한 것이라고 생각합시다. 값을 편리하게 처리할 수 있도록 program 객체에 저장해 둡니다.

지원 함수 - getShader

이제 getShader 함수를 살펴보도록 합니다.

  function getShader(gl, id) {
      var shaderScript = document.getElementById(id);
      if (!shaderScript) {
          return null;
      }

      var str = "";
      var k = shaderScript.firstChild;
      while (k) {
          if (k.nodeType == 3)
              str += k.textContent;
          k = k.nextSibling;
      }

      var shader;
      if (shaderScript.type == "x-shader/x-fragment") {
          shader = gl.createShader(gl.FRAGMENT_SHADER);
      } else if (shaderScript.type == "x-shader/x-vertex") {
          shader = gl.createShader(gl.VERTEX_SHADER);
      } else {
          return null;
      }

      gl.shaderSource(shader, str);
      gl.compileShader(shader);

      if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
          alert(gl.getShaderInfoLog(shader));
          return null;
      }

      return shader;
  }

이 함수도 보기보다 훨씬 쉽습니다. 여기서 하고 있는 일은 인수로 전달된 ID와 일치하는 요소를 HTML에서 찾아 내용물을 꺼내 fragment가 vertex 중 하나인 shader가 가지는 type(type의 차이점은 미래의 학습)에 따라 작성하여 WebGL이 그래픽 카드에서 작동하게 컴파일을 걸어놓고 있는 것입니다. 다음 코드에 오류가 없는지 확인했습니다. 물론 이 쉐이더를 자바스크립트 코드에 문자열로 통합할 수 있었습니다. 그 경우에는 HTML에서 불러올 부분은 필요하지 않습니다. 그러나 이번 방법은 쉐이더가 페이지의 스크립트가 되므로 자바스크립트처럼 쉽게 읽을 수 있습니다.

하드웨어 언어 - Shader

그것을 근거로 쉐이더 코드를 살펴봅시다.

<script id="shader-fs" type="x-shader/x-fragment">
  #ifdef GL_ES
  precision highp float;
  #endif

  void main(void) {
    gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
  }
</script>

<script id="shader-vs" type="x-shader/x-vertex">
  attribute vec3 aVertexPosition;

  uniform mat4 uMVMatrix;
  uniform mat4 uPMatrix;

  void main(void) {
    gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
  }
</script>

가장 명심해야 할 것은 이들은 자바스크립트로 작성되어 있지 않다는 것입니다. 사실, 쉐이더 코드는 C언어의 영향을 강하게 받은(물론 자바스크립트도) 특별한 쉐이더 언어로 작성되어 있습니다. 첫 번째 스크립트는 fragment shader입니다. 하지만 그리 복잡해 보이지는 않군요. 이 코드에는 그래픽 카드에 float의 정밀도를 지정하는데 반드시 포함하지 않으면 안되는 마스크의 일부분이 있습니다. 아래는 렌더링되는 모든 것이 간단하게 흰색으로 렌더링하도록 지정합니다.(색상에 관한 사항은 다음 레슨에서 설명합니다) 다음 쉐이더는 더욱 재미있습니다. 이것은 vertex shader입니다. 기억 하겠지만, vertex shader는 vertex를 사용하여 매우 여러가지 일을 꾸밀수 있게 하며, 이 역시 그래픽 카드에서 실행되는 코드입니다.

여기에 관련한 uMVMatrix과 uPMatrix라는 두개의 uniform 변수가 있습니다. uniform 변수는 쉐이더 외부에서 액세스할 수 있기 때문에 유용합니다. - 특히, program이 있는 곳과 관계가 있습니다. initShaders에서 location을 꺼내는데 다음으로 보이는 model-view와 projection matrix를 설정하는 부분입니다. 당신은 쉐이더 프로그램을 (객체 지향의) object와 uniform 변수 field 정도로 간주해 버릴지도 모르겠네요.

이제 모든 버텍스에 대해 쉐이더가 호출됩니다. 그리고 버텍스는 drawScene 내부 vertexPositionAttribute으로 애트리뷰트와 버퍼를 연관지 쉐이더에 VertexPosition로 전달됩니다. vertex shader의 main 함수 내부의 작은 코드는 버텍스에 model-view matrix와 projection matrix를 곱한 결과를 최종 버텍스 좌표로 출력하는 것입니다.

그래서, webGLStart는 initShaders를 부르고 그 안에 getShader를 사용하여 fragment와 vertex shader를 페이지 내의 스크립트에서 검색하여 컴파일하고 WebGL로 전달하여 3D 화면을 그릴 때 사용되는 것입니다.

지원 함수 - setMatrixUniforms

이제 끝으로 설명할 녀석은 setMatrixUniforms 함수입니다. 이것은 지금까지의 내용을 알고 있다면 매우 간단하게 이해할 수 있습니다.

  function setMatrixUniforms() {
    gl.uniformMatrix4fv(shaderProgram.pMatrixUniform, false, new Float32Array(pMatrix.flatten()));
    gl.uniformMatrix4fv(shaderProgram.mvMatrixUniform, false, new Float32Array(mvMatrix.flatten()));
  }

여기에서는 initShader 함수에서 얻은 projection matrix와 model-view matrix를 나타내는 uniform 변수에 대한 참조를 사용하여 WebGL에 자바스크립트 형식의 매트릭스를 전달합니다.

휴~ 처음 학습치고는 꽤 양이 많았네요. 이 레슨은 여러분과 제가 앞으로 더욱 재미있는 것을 만드는데 필요한 것들입니다. 기본적으로 필요한 항목을 모두 이해했습니다. 색상, 운동, 실재 3차원 입체 WebGL 모델들에 대하여 말예요. 그리고 더 많은 것을 공부하기 위해 레슨 2로 이어집니다.

짬짬히 작성 하다보니 많이 늦어졌네요. 정확하게 모두 이해하지는 못했지만 몇몇 그래픽 용어를 숙지하고 대략적인 작동 원리를 이해하는 중요한 학습이였습니다. 그리고 보시다시피 이 문서는 다소 어눌합니다. 확실히 이해하고 작성한 것이 아니기 때문에 저도 무슨 말을 하려고 하는지 모르는 부분이 상당수 있었습니다. 그리고 원문을 보면 이양반 말하는 스타일이 그리 논리적이지는 못한 느낌을 자주 받았는데, 일단 그런 부분들은 대략적으로 직역했지만 계속 학습하면서 조금씩 다듬어 가도록 하겠습니다.

Comments