WebGL 변환

Gregg Tavares
Gregg Tavares

WebGL 2D 번역

3D로 넘어가기 전에 2D를 좀 더 오래 사용해 보겠습니다. 잠시만 기다려 주세요. 이 글은 일부 사람들에게는 매우 명확해 보일 수 있지만, 몇 가지 도움말을 통해 요점을 축적할 것입니다.

이 도움말은 WebGL Fundamentals로 시작하는 시리즈 중 일부입니다. 아직 읽지 않았다면 적어도 첫 장을 읽은 후 여기로 돌아오는 것이 좋습니다. Translation은 기본적으로 무언가를 "이동하다"는 것을 의미하는 멋진 수학 이름입니다. 영어에서 일본어로 문장을 옮기는 것도 나쁘지 않을 것 같지만, 여기서는 기하학적 이동에 관해 이야기해 보죠. 첫 번째 게시물에서 작성한 샘플 코드를 사용하면 setRectangle에 전달된 값을 올바르게 변경하기만 해도 직사각형을 쉽게 변환할 수 있을까요? 다음은 이전 샘플을 기반으로 하는 샘플입니다.

  // First lets make some variables 
  // to hold the translation of the rectangle
  var translation = [0, 0];
  // then let's make a function to
  // re-draw everything. We can call this
  // function after we update the translation.
  // Draw the scene.
  function drawScene() {
     // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);
    // Setup a rectangle
    setRectangle(gl, translation[0], translation[1], width, height);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 6);
  }

지금까지 잘 하고 계십니다. 하지만 이제 더 복잡한 모양으로 동일한 작업을 하고 싶다고 상상해 보세요. 다음과 같이 삼각형 6개로 구성된 'F'를 그리고 싶다고 가정해 보겠습니다.

F 문자

다음은 setRectangle을 이와 같은 것으로 변경해야 하는 현재 코드입니다.

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl, x, y) {
  var width = 100;
  var height = 150;
  var thickness = 30;
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          x, y,
          x + thickness, y,
          x, y + height,
          x, y + height,
          x + thickness, y,
          x + thickness, y + height,

          // top rung
          x + thickness, y,
          x + width, y,
          x + thickness, y + thickness,
          x + thickness, y + thickness,
          x + width, y,
          x + width, y + thickness,

          // middle rung
          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);
}

확장성이 떨어질 것이라고 예상할 수도 있습니다. 수백 또는 수천 개의 선으로 매우 복잡한 도형을 그리려면 꽤 복잡한 코드를 작성해야 합니다. 게다가 JavaScript를 그릴 때마다 모든 지점을 업데이트해야 합니다. 더 간단한 방법이 있습니다. 도형을 업로드하고 셰이더에서 변환을 수행하기만 하면 됩니다. 새로운 셰이더입니다.

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

uniform vec2 u_resolution;
uniform vec2 u_translation;

void main() {
   // Add in the translation.
   vec2 position = a_position + u_translation;

   // convert the rectangle from pixels to 0.0 to 1.0
   vec2 zeroToOne = position / u_resolution;
   ...

코드를 약간 재구성해 보겠습니다. 이 경우 도형을 한 번만 설정하면 됩니다.

// Fill the buffer with the values that define a letter 'F'.
function setGeometry(gl) {
  gl.bufferData(
      gl.ARRAY_BUFFER,
      new Float32Array([
          // left column
          0, 0,
          30, 0,
          0, 150,
          0, 150,
          30, 0,
          30, 150,

          // top rung
          30, 0,
          100, 0,
          30, 30,
          30, 30,
          100, 0,
          100, 30,

          // middle rung
          30, 60,
          67, 60,
          30, 90,
          30, 90,
          67, 60,
          67, 90]),
      gl.STATIC_DRAW);
}

그런 다음 원하는 변환으로 그리기 전에 u_translation를 업데이트하면 됩니다.

  ...
  var translationLocation = gl.getUniformLocation(
             program, "u_translation");
  ...
  // Set Geometry.
  setGeometry(gl);
  ..
  // Draw scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

setGeometry는 한 번만 호출됩니다. 더 이상 drawScene 내부에 있지 않습니다.

이제 WebGL을 그릴 때 거의 모든 작업이 실행됩니다. 번역을 설정하고 그림을 그릴 것을 요청하기만 하면 됩니다. 도형에 수만 개의 점이 있더라도 기본 코드는 동일하게 유지됩니다.

WebGL 2D 회전

지금 바로 인정하겠습니다. 어떻게 설명해야 할지 모르겠지만, 도대체 뭘 시도해 봐야 할지 모르겠네요.

먼저 '단위 원'이라는 것을 소개하겠습니다. 중학교 수학을 기억한다면 (나 때문에 잠들지 마세요!) 원에는 반지름이 있습니다. 원의 반지름은 원의 중심에서 가장자리까지의 거리입니다. 단위 원은 반지름이 1.0인 원입니다.

기본적인 3학년 수학에서 1을 곱하면 그것은 동일하게 유지됩니다. 따라서 123 * 1 = 123입니다. 꽤 기본적인 것 같죠? 단위 원, 반지름이 1.0인 원도 1의 형태입니다. 회전하는 1입니다. 단위 원으로 무언가를 곱할 수 있습니다. 1을 곱하는 것과 같은 방식으로 말이죠. 마법과 사물이 회전하는 것만 빼면 말이죠. 단위 원의 한 점에서 X 및 Y 값을 가져와서 이전 샘플의 도형에 곱합니다. 다음은 셰이더의 업데이트입니다.

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

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;

void main() {
  // Rotate the position
  vec2 rotatedPosition = vec2(
     a_position.x * u_rotation.y + a_position.y * u_rotation.x,
     a_position.y * u_rotation.y - a_position.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;

그리고 이 두 값을 전달할 수 있도록 JavaScript를 업데이트합니다.

  ...
  var rotationLocation = gl.getUniformLocation(program, "u_rotation");
  ...
  var rotation = [0, 1];
  ..
  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

왜 효과가 있나요? 자, 계산식을 봅시다.

rotatedX = a_position.x * u_rotation.y + a_position.y * u_rotation.x;
rotatedY = a_position.y * u_rotation.y - a_position.x * u_rotation.x;

직사각형을 회전하고 싶다고 가정해 보겠습니다. 회전하기 전 오른쪽 상단 모서리는 3.0, 9.0으로 설정되어 있습니다. 단위 원에서 12시 방향에서 시계 방향으로 30도 떨어진 지점을 선택해 보겠습니다.

30도 회전

원의 위치는 0.50과 0.87입니다.

3.0 * 0.87 + 9.0 * 0.50 = 7.1
9.0 * 0.87 - 3.0 * 0.50 = 6.3

바로 이 위치에 있어야 합니다.

회전 그리기

시계 방향으로 60도 회전할 때도

60도 회전

원의 위치는 0.87, 0.50입니다.

3.0 * 0.50 + 9.0 * 0.87 = 9.3
9.0 * 0.50 - 3.0 * 0.87 = 1.9

이 지점을 오른쪽 방향으로 시계 방향으로 회전하면 X값이 커지고 Y는 작아지는 것을 알 수 있습니다. 90도를 계속 넘기면 X는 다시 작아지기 시작하고 Y는 점점 커지기 시작합니다. 이 패턴은 회전을 제공합니다. 단위 원 위의 점에는 다른 이름이 있습니다. 사인과 코사인이라고 부릅니다. 따라서 어떤 각도에서든 이와 같이 사인과 코사인을 조회할 수 있습니다.

function printSineAndCosineForAnAngle(angleInDegrees) {
  var angleInRadians = angleInDegrees * Math.PI / 180;
  var s = Math.sin(angleInRadians);
  var c = Math.cos(angleInRadians);
  console.log("s = " + s + " c = " + c);
}

코드를 복사하여 자바스크립트 콘솔에 붙여넣고 printSineAndCosignForAngle(30)를 입력하면 s = 0.49 c= 0.87가 출력됩니다 (참고: 숫자를 반올림했습니다). 모두 결합한 경우 도형을 원하는 각도로 회전할 수 있습니다. 회전하려는 각도의 사인 및 코사인 값으로 회전을 설정하기만 하면 됩니다.

  ...
  var angleInRadians = angleInDegrees * Math.PI / 180;
  rotation[0] = Math.sin(angleInRadians);
  rotation[1] = Math.cos(angleInRadians);

설명이 도움이 되었기를 바랍니다. 다음은 더 간단한 방법입니다. 확장하기

라디안이란 무엇인가요?

라디안은 원, 회전, 각도와 함께 사용되는 측정 단위입니다. 인치, 야드, 미터 등으로 거리를 측정할 수 있는 것처럼 각도를 도 또는 라디안으로 측정할 수 있습니다.

미터법 측정값을 사용하는 계산이 야드파운드법 측정값을 사용하는 수학보다 쉽다는 것을 알고 계실 것입니다. 인치에서 피트로 이동하려면 12로 나눕니다. 인치에서 야드로 이동하려면 36으로 나눕니다. 나는 당신에 대해 모르지만 내 머릿속에서 36으로 나눌 수는 없습니다. 메트릭을 사용하면 훨씬 쉽습니다. 밀리미터에서 센티미터로 이동하려면 10으로 나눕니다. 밀리미터에서 미터까지 1,000으로 나눕니다. 머리 속에서 1,000으로 나눌 수 있어요.

라디안과 각도는 비슷합니다. 학위로 인해 계산이 어려워집니다. 라디안을 사용하면 계산이 쉬워집니다. 원의 360도는 2π 라디안에 불과합니다. 따라서 100제곱은 2π 라디안입니다. 1/2턴은 π 라디안입니다. 1/4턴, 즉 90의 회귀는 π/2라디안입니다. 따라서 무언가를 90도 회전하려면 Math.PI * 0.5를 사용하면 됩니다. 45도 회전하려면 Math.PI * 0.25 등을 사용합니다.

각도, 원 또는 회전과 관련된 거의 모든 수학은 라디안으로 생각하면 매우 간단하게 작동합니다. 한번 해 보세요. UI 디스플레이를 제외하고 도 단위가 아닌 라디안을 사용합니다.

WebGL 2D 배율

크기 조정은 번역만큼이나 쉽습니다.

이 위치에 원하는 배율을 곱합니다. 다음은 이전 샘플의 변경사항입니다.

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

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;
uniform vec2 u_scale;

void main() {
  // Scale the positon
  vec2 scaledPosition = a_position * u_scale;

  // Rotate the position
  vec2 rotatedPosition = vec2(
     scaledPosition.x * u_rotation.y +
        scaledPosition.y * u_rotation.x,
     scaledPosition.y * u_rotation.y -
        scaledPosition.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;

그리고 그릴 때 배율을 설정하는 데 필요한 JavaScript를 추가합니다.

  ...
  var scaleLocation = gl.getUniformLocation(program, "u_scale");
  ...
  var scale = [1, 1];
  ...
  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Set the translation.
    gl.uniform2fv(translationLocation, translation);

    // Set the rotation.
    gl.uniform2fv(rotationLocation, rotation);

    // Set the scale.
    gl.uniform2fv(scaleLocation, scale);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

한 가지 주목할 점은 음수 값으로 배율을 조정하면 도형이 뒤집힌다는 것입니다. 마지막 세 챕터가 변환, 회전, 배율을 이해하는 데 도움이 되었기를 바랍니다. 다음으로 이 세 가지를 훨씬 간단하고 종종 더 유용한 형태로 결합하는 행렬에 대해 살펴보겠습니다.

왜 'F'가 표시되는가?

누군가 'F'를 사용하는 것을 처음 봤을 때가 텍스처 위에 있었습니다. 'F' 자체는 중요하지 않습니다. 중요한 것은 모든 방향에서 방향을 확인할 수 있다는 것입니다. 예를 들어 하트 ♥ 또는 삼각형 △을 사용하면 수평으로 뒤집었는지 알 수 없습니다. 원 ○은 더 좋지 않습니다. 색상이 지정된 직사각형은 각 모서리에 다른 색상으로 작동할 수 있지만 어떤 모서리가 어떤 모서리였는지 기억해야 합니다. F의 방향은 즉시 알아볼 수 있습니다.

F 방향

방향을 가늠할 수 있는 어떤 모양이라도 괜찮습니다. 저는 이 아이디어를 처음 접한 이후로 'F'를 사용해 왔습니다.

WebGL 2D 행렬

지난 3개 장에서는 도형을 변환하고, 도형을 회전하고, 도형의 크기를 조정하는 방법을 알아보았습니다. 변환, 회전 및 배율은 각각 '변환'의 한 유형으로 간주됩니다. 이러한 각 변환에는 셰이더를 변경해야 했으며 세 가지 변환은 각각 순서에 종속되었습니다.

예를 들어 다음은 배율 2, 1, 회전 30%, 변환 100, 0입니다.

F 회전 및 변환

이것은 100,0의 변환, 30% 회전, 2, 1의

F 회전 및 배율

결과는 완전히 다릅니다. 더 심각한 문제는 두 번째 예가 필요한 경우 원하는 새 순서로 변환, 회전, 배율을 적용하는 다른 셰이더를 작성해야 할 것입니다. 저보다 훨씬 더 영리한 사람들은 행렬 수학으로 모든 같은 일을 할 수 있다는 것을 알았습니다. 2차원에서는 3x3 행렬을 사용합니다. 3x3 행렬은 9개의 상자가 있는 그리드와 같습니다.

1.0 2.0 3.0
4.0 5.0 6.0
7.0 8.0 9.0

이를 계산하기 위해 행렬의 열 아래쪽에 위치를 곱하고 결과를 더합니다. 위치에는 x와 y라는 두 개의 값만 있지만 이 계산을 수행하려면 3개의 값이 필요하므로 세 번째 값으로 1을 사용합니다. 이 경우 결과는 다음과 같습니다.

newX = x * 1.0 + y * 4.0 + 1 * 7.0

newY = x * 2.0 + y * 5.0 + 1 * 8.0

extra = x * 3.0 + y * 6.0 + 1 * 9.0

이 부분을 보고 'What's THE POINT(포인트가 뭐지)'라고 생각하고 계실 겁니다. 번역이 있다고 가정해 보겠습니다. 번역하려는 금액을 tx와 ty라고 지정합니다. 이와 같은 행렬을 만들어 보겠습니다.

1.00.00.0
0.01.00.0
txty1.0

지금 확인해 보세요

newX = x * 1.0 + y * 0.0 + 1 * tx

newY = x * 0.0 + y * 1.0 + 1 * ty

extra = x * 0.0 + y * 0.0 + 1 * 1

대수학을 기억하는 경우 0을 곱하는 모든 장소를 삭제할 수 있습니다. 1을 곱하면 아무 효과가 없으므로 간단히 말해 어떤 일이 일어나는지 확인해 보겠습니다.

newX = x + tx;
newY = y + ty;

그리고 우리는 별로 신경 쓰지 않습니다. 놀랍게도 우리의 번역 예의 변환 코드와 똑같아 보입니다. 마찬가지로 회전을 실행해 보겠습니다. 회전 게시물에서 지적한 것처럼 회전하려는 각도의 사인과 코사인만 있으면 됩니다.

s = Math.sin(angleToRotateInRadians);
c = Math.cos(angleToRotateInRadians);

그리고 이와 같은 행렬을

c-s0.0
sc0.0
0.00.01.0

행렬을 적용하면

newX = x * c + y * s + 1 * 0

newY = x * -s + y * c + 1 * 0

extra = x * 0.0 + y * 0.0 + 1 * 1

모든 것을 블랙아웃하고 0과 1을 곱합니다.

newX = x *  c + y * s;
newY = x * -s + y * c;

회전 샘플에 있던 것이죠. 마지막으로 확장입니다. 두 개의 배율을 sx와 sy로 부르고, 다음과 같은 행렬을 만듭니다

SX0.00.0
0.0sy0.0
0.00.01.0

행렬을 적용하면

newX = x * sx + y * 0 + 1 * 0

newY = x * 0 + y * sy + 1 * 0

extra = x * 0.0 + y * 0.0 + 1 * 1

이는 정말로

newX = x * sx;
newY = y * sy;

이 내용은 확장 샘플과 동일합니다. 아마도 지금도 생각하고 있을 겁니다. 그럼 어떻게 해야 할까요? 요점은 무엇일까요? 우리가 이미 하고 있던 일을 하기 위해 할 일이 많은 것 같나요? 여기에 마법이 필요합니다. 행렬을 곱하고 모든 변환을 한 번에 적용할 수 있다는 것이 밝혀졌습니다. 두 행렬을 곱한 후 결과를 반환하는 matrixMultiply 함수가 있다고 가정해 보겠습니다. 좀 더 명확하게 설명하기 위해 변환, 회전, 배율을 위한 행렬을 빌드하는 함수를 만들어 보겠습니다.

function makeTranslation(tx, ty) {
  return [
    1, 0, 0,
    0, 1, 0,
    tx, ty, 1
  ];
}

function makeRotation(angleInRadians) {
  var c = Math.cos(angleInRadians);
  var s = Math.sin(angleInRadians);
  return [
    c,-s, 0,
    s, c, 0,
    0, 0, 1
  ];
}

function makeScale(sx, sy) {
  return [
    sx, 0, 0,
    0, sy, 0,
    0, 0, 1
  ];
}

이제 셰이더를 변경해 보겠습니다. 이전 셰이더는 다음과 같습니다.

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

uniform vec2 u_resolution;
uniform vec2 u_translation;
uniform vec2 u_rotation;
uniform vec2 u_scale;

void main() {
  // Scale the positon
  vec2 scaledPosition = a_position * u_scale;

  // Rotate the position
  vec2 rotatedPosition = vec2(
     scaledPosition.x * u_rotation.y + scaledPosition.y * u_rotation.x,
     scaledPosition.y * u_rotation.y - scaledPosition.x * u_rotation.x);

  // Add in the translation.
  vec2 position = rotatedPosition + u_translation;
  ...

새 셰이더가 훨씬 간단해집니다.

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

uniform vec2 u_resolution;
uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  vec2 position = (u_matrix * vec3(a_position, 1)).xy;
  ...

이를 사용하여

  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Compute the matrices
    var translationMatrix =
       makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

    // Set the matrix.
    gl.uniformMatrix3fv(matrixLocation, false, matrix);

    // Draw the rectangle.
    gl.drawArrays(gl.TRIANGLES, 0, 18);
  }

그럼에도 불구하고 그리 큰 이점 같지는 않습니다 . 하지만 이제 순서를 변경하려면 새 셰이더를 작성할 필요가 없습니다. 계산만 바꿔도 됩니다.

    ...
    // Multiply the matrices.
    var matrix = matrixMultiply(translationMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, scaleMatrix);
    ...

이와 같은 행렬을 적용할 수 있는 기능은 몸에 있는 팔, 태양 주변의 행성의 위성, 나무의 가지와 같은 계층적 애니메이션에 특히 중요합니다. 계층적 애니메이션의 간단한 예로 'F'를 5번 그릴 수 있습니다. 하지만 매번 이전 'F'의 행렬로 시작해 보겠습니다.

  // Draw the scene.
  function drawScene() {
    // Clear the canvas.
    gl.clear(gl.COLOR_BUFFER_BIT);

    // Compute the matrices
    var translationMatrix = makeTranslation(translation[0], translation[1]);
    var rotationMatrix = makeRotation(angleInRadians);
    var scaleMatrix = makeScale(scale[0], scale[1]);

    // Starting Matrix.
    var matrix = makeIdentity();

    for (var i = 0; i < 5; ++i) {
      // Multiply the matrices.
      matrix = matrixMultiply(matrix, scaleMatrix);
      matrix = matrixMultiply(matrix, rotationMatrix);
      matrix = matrixMultiply(matrix, translationMatrix);

      // Set the matrix.
      gl.uniformMatrix3fv(matrixLocation, false, matrix);

      // Draw the geometry.
      gl.drawArrays(gl.TRIANGLES, 0, 18);
    }
  }

이를 위해 단위행렬을 만드는 makeIdentity 함수를 도입했습니다. 단위행렬은 항등식을 곱해도 아무 일도 일어나지 않도록 1.0을 효과적으로 나타내는 행렬입니다. 좋아요

X * 1 = X

그러니

matrixX * identity = matrixX

다음은 단위행렬을 만드는 코드입니다.

function makeIdentity() {
  return [
    1, 0, 0,
    0, 1, 0,
    0, 0, 1
  ];
}

또 다른 예로, 지금까지의 모든 샘플에서 'F'는 왼쪽 상단을 중심으로 회전합니다. 이는 우리가 사용하는 수학이 항상 원점을 기준으로 회전하고 'F'의 왼쪽 상단 모서리가 원점 (0, 0)에 있기 때문입니다. 하지만 행렬 계산을 할 수 있고 변환이 적용되는 순서를 선택할 수 있으므로 나머지 변환이 적용되기 전에 원점을 이동할 수 있습니다.

    // make a matrix that will move the origin of the 'F' to
    // its center.
    var moveOriginMatrix = makeTranslation(-50, -75);
    ...

    // Multiply the matrices.
    var matrix = matrixMultiply(moveOriginMatrix, scaleMatrix);
    matrix = matrixMultiply(matrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);

이 기법을 사용하면 어느 지점에서든 회전하거나 크기를 조정할 수 있습니다. 이제 Photoshop 또는 Flash로 회전 지점을 이동하는 방법을 알게 되었습니다. 더 열심히 해 보자. WebGL 기초의 첫 번째 도움말로 돌아가면 셰이더에 다음과 같은 픽셀에서 클립스페이스로 변환하는 코드가 있다는 것을 기억할 수 있습니다.

  ...
  // convert the rectangle from pixels to 0.0 to 1.0
  vec2 zeroToOne = position / u_resolution;

  // convert from 0->1 to 0->2
  vec2 zeroToTwo = zeroToOne * 2.0;

  // convert from 0->2 to -1->+1 (clipspace)
  vec2 clipSpace = zeroToTwo - 1.0;

  gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);

이러한 각 단계를 차례로 살펴보면 첫 번째 단계인 '픽셀에서 0.0으로 변환'은 실제로 배율 작업입니다. 두 번째 작업도 확장 작업입니다. 다음은 변환이며 맨 마지막은 Y를 -1로 조정합니다. 실제로 셰이더에 전달하는 행렬에서 이 모든 작업을 수행할 수 있습니다. 2개의 배율 행렬을 만들 수 있습니다. 하나는 1.0/해상도로 조정하고 다른 하나는 2.0으로 조정하고, 세 번째는 -1.0, -1.0으로 변환하고, 4번째는 Y를 -1로 이동한 다음 모두 곱합니다.

function make2DProjection(width, height) {
  // Note: This matrix flips the Y axis so that 0 is at the top.
  return [
    2 / width, 0, 0,
    0, -2 / height, 0,
    -1, 1, 1
  ];
}

이제 셰이더를 훨씬 더 단순화할 수 있습니다. 전체 새 꼭짓점 셰이더입니다.

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

uniform mat3 u_matrix;

void main() {
  // Multiply the position by the matrix.
  gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
}
</script>

그리고 JavaScript에서는 투영 행렬을 곱해야 합니다.

  // Draw the scene.
  function drawScene() {
    ...
    // Compute the matrices
    var projectionMatrix =
       make2DProjection(canvas.width, canvas.height);
    ...

    // Multiply the matrices.
    var matrix = matrixMultiply(scaleMatrix, rotationMatrix);
    matrix = matrixMultiply(matrix, translationMatrix);
    matrix = matrixMultiply(matrix, projectionMatrix);
    ...
  }

해결 방법을 설정하는 코드도 삭제되었습니다. 이 마지막 단계에서는 6~7단계의 다소 복잡한 셰이더에서 1단계만 있는 매우 간단한 셰이더로 모두 행렬 수학의 마법을 부립니다.

이 문서가 행렬 수학을 이해하는 데 도움이 되었기를 바랍니다. 이번에는 3D로 넘어가겠습니다. 3D 행렬에서 수학은 동일한 원칙과 사용법을 따릅니다. 이해하기 쉽도록 2D로 시작했습니다.