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

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

Your Reaction Time!

avatar

captcha