목차

WebGLFundamentals.org

Fix, Fork, Contribute

WebGL 2D 평행 이동

3D로 넘어가기 전에 잠시 2D를 사용해봅시다. 끝까지 함께 해주세요. 어떤 분들에게는 이 글이 굉장히 당연하겠지만 몇 가지 요점을 써보려고 합니다.

이 글은 WebGL 기초로 시작한 WebGL 관련 시리즈에서 이어집니다. 아직 읽지 않았다면 적어도 첫 번째 글을 먼저 읽고 다시 여기로 돌아오는 게 좋습니다.

평행 이동은 기본적으로 무언가를 "움직이는 것"을 의미하는 멋진 수학적 명칭인데요. 문장을 영어에서 일본어로 옮기는 것도 맞지만 이 경우에는 기하학적 이동을 말합니다. 첫 번째 포스트에서 끝낸 샘플 코드를 사용하면 setRectangle에 전달되는 값을 변경하는 것만으로 쉽게 사각형을 이동할 수 있었죠? 다음은 이전 샘플에 기반한 샘플입니다.

먼저 사각형의 translation, width, height, color 변수를 만듭니다.

  var translation = [0, 0];
  var width = 100;
  var height = 30;
  var color = [Math.random(), Math.random(), Math.random(), 1];

그런 다음 전부 다시 그리는 함수를 만들어봅시다. translation을 업데이트한 후에 이 함수를 호출할 수 있습니다.

  // 장면 그리기
  function drawScene() {
    webglUtils.resizeCanvasToDisplaySize(gl.canvas);

    // 클립 공간에서 픽셀로 변환하는 방법을 WebGL에 지시
    gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);

    // 캔버스 지우기
    gl.clear(gl.COLOR_BUFFER_BIT);

    // 프로그램(셰이더 쌍) 사용
    gl.useProgram(program);

    // 위치 속성 활성화
    gl.enableVertexAttribArray(positionLocation);

    // 위치 버퍼 할당
    gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);

    // 사각형 설정
    setRectangle(gl, translation[0], translation[1], width, height);

    // positionBuffer(ARRAY_BUFFER)에서 데이터 가져오는 방법을 속성에 지시
    var size = 2;          // 반복마다 2개의 컴포넌트
    var type = gl.FLOAT;   // 데이터는 32비트 부동 소수점
    var normalize = false; // 데이터 정규화 안 함
    var stride = 0;        // 0 = 다음 위치를 가져오기 위해 반복마다 size * sizeof(type) 만큼 앞으로 이동
    var offset = 0;        // 버퍼의 처음부터 시작
    gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);

    // 해상도 설정
    gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);

    // 색상 설정
    gl.uniform4fv(colorLocation, color);

    // 사각형 그리기
    var primitiveType = gl.TRIANGLES;
    var offset = 0;
    var count = 6;
    gl.drawArrays(primitiveType, offset, count);
  }

아래 예제에 translation[0]translation[1]을 업데이트하고 변경할 때마다 drawScene을 호출하는 슬라이더 두 개를 첨부했습니다. 사각형을 움직이려면 슬라이더를 드래그해보세요.

지금까지는 그럭저럭 잘 됐습니다. 하지만 더 복잡한 모양으로 똑같이 하고 싶다고 상상해보세요.

이처럼 삼각형 6개로 구성된 'F'를 그리고 싶다고 가정해봅시다.

우선 현재 코드에 따라 setRectangle을 이런 식으로 변경해야 합니다.

// 문자 'F'를 정의하는 값들로 버퍼 채우기
function setGeometry(gl, x, y) {
  var width = 100;
  var height = 150;
  var thickness = 30;
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
      // 왼쪽 열
      x, y,
      x + thickness, y,
      x, y + height,
      x, y + height,
      x + thickness, y,
      x + thickness, y + height,

      // 상단 가로 획
      x + thickness, y,
      x + width, y,
      x + thickness, y + thickness,
      x + thickness, y + thickness,
      x + width, y,
      x + width, y + thickness,

      // 중간 가로 획
      x + thickness, y + thickness * 2,
      x + width * 2 / 3, y + thickness * 2,
      x + thickness, y + thickness * 3,
      x + thickness, y + thickness * 3,
      x + width * 2 / 3, y + thickness * 2,
      x + width * 2 / 3, y + thickness * 3,
    ]),
    gl.STATIC_DRAW
  );
}

확장하기에 좋지 않겠다는 걸 눈치채셨을 겁니다. 수백 수천 개의 선으로 이루어진 아주 복잡한 지오메트리를 그리려면 꽤 복잡한 코드를 작성해야 합니다. 또한 자바스크립트는 그릴 때마다 모든 점들을 업데이트해야 하는데요.

더 간단한 방법이 있습니다. 지오메트리를 업로드하고 셰이더에서 평행 이동을 수행하면 됩니다.

여기 새로운 셰이더 입니다.

<script id="vertex-shader-2d" type="x-shader/x-vertex">
attribute vec2 a_position;

uniform vec2 u_resolution;
+uniform vec2 u_translation;

void main() {
*  // 평행 이동 추가
*  vec2 position = a_position + u_translation;

  // 사각형을 픽셀에서 0.0에서 1.0사이로 변환
*  vec2 zeroToOne = position / u_resolution;
  ...

그리고 코드를 조금 재구성할 겁니다. 우선 지오메트리는 한 번만 설정하면 됩니다.

// 문자 'F'를 정의하는 값들로 버퍼 채우기
function setGeometry(gl) {
  gl.bufferData(
    gl.ARRAY_BUFFER,
    new Float32Array([
      // 왼쪽 열
      0, 0,
      30, 0,
      0, 150,
      0, 150,
      30, 0,
      30, 150,

      // 상단 가로 획
      30, 0,
      100, 0,
      30, 30,
      30, 30,
      100, 0,
      100, 30,

      // 중간 가로 획
      30, 60,
      67, 60,
      30, 90,
      30, 90,
      67, 60,
      67, 90,
    ]),
    gl.STATIC_DRAW
  );
}

그런 다음 평행 이동하여 그리기 전에 u_translation을 업데이트하면 됩니다.

  ...

+  var translationLocation = gl.getUniformLocation(program, "u_translation");

  ...

  // 위치를 넣을 버퍼 생성
  var positionBuffer = gl.createBuffer();
  // ARRAY_BUFFER에 바인딩 (ARRAY_BUFFER = positionBuffer)
  gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
+  // 버퍼에 지오메트리 데이터 넣기
+  setGeometry(gl);

  ...

  // 장면 그리기
  function drawScene() {

    ...

+    // 평행 이동 설정
+    gl.uniform2fv(translationLocation, translation);

    // 사각형 그리기
    var primitiveType = gl.TRIANGLES;
    var offset = 0;
*    var count = 18;
    gl.drawArrays(primitiveType, offset, count);
  }

setGeometry가 한 번만 호출된다는 점에 주목하세요. 더 이상 drawScene 안에 있지 않습니다.

그리고 여기 해당 예제입니다. 다시 한 번 translation을 업데이트하기 위해 슬라이더를 드래그해보세요.

이제 그릴 때 WebGL이 거의 모든 걸 수행하고 있습니다. translation을 설정하고 그려달라고 요청하는 게 우리가 하는 전부죠. 심지어 지오메트리에 수만 개의 점들이 있더라도 주요 코드는 그대로 유지됩니다.

원한다면 모든 점들을 업데이트하는 복잡한 자바스크립트 버전과 비교해 볼 수 있습니다.

너무 뻔한 예제가 아니었기를 바랍니다. 앞으로 이걸 수행하는 훨씬 더 좋은 방법을 다룰테니 계속 읽어주세요. 다음 글에서는 회전에 대해 알아보겠습니다.

이슈/버그는? Github에 이슈를 만들어주세요.
코드 블록은 <pre><code>여기에 코드 입력</code></pre>를 사용해주세요
comments powered by Disqus