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

Got something to add? You can just leave a comment.

Your Reaction Time!

avatar

captcha