鍍金池/ 教程/ HTML/ WebGL 2D 矩陣
WebGL 文本 HTML
WebGL 文本 Canvas 2D
WebGL 2D 圖像旋轉(zhuǎn)
WebGL 圖像處理(續(xù))
WebGL 2D 矩陣
WebGL 繪制多個東西
WebGL 圖像處理
WebGL 2D 圖像轉(zhuǎn)換
WebGL 3D 透視
WebGL 是如何工作的
WebGL 文本 紋理
WebGL 2D 圖像伸縮
WebGL 場景圖
WebGL 3D 攝像機
WebGL 文本 使用字符紋理
WebGL 正交 3D
WebGL 基本原理
WebGL - 更少的代碼,更多的樂趣
WebGL 著色器和 GLSL

WebGL 2D 矩陣

在前面三篇文章中我們講解了如何平移幾何圖形,如何旋轉(zhuǎn)幾何圖形,如何伸縮變換圖形。平移,旋轉(zhuǎn)和伸縮都被認為是一種變化類型。每一種變化都需要改變渲染器,而且他們依賴于操作的順序。在前面的例子中我們進行了伸縮,旋轉(zhuǎn)和平移操作。如果他們執(zhí)行操作的順序改變將會得到不同的結果。例如 XY 伸縮變換為 2,1,旋轉(zhuǎn) 30%,接著平移變換 100,0。

http://wiki.jikexueyuan.com/project/webgl/images/transformation1.png" alt="transformation1" />

如下是平移 100,0,旋轉(zhuǎn) 30%,接著伸縮變換 2,1。

http://wiki.jikexueyuan.com/project/webgl/images/transformation2.png" alt="transformation2" />

結果是完全不同的。更糟糕的是,如果我們需要得到的是第二個示例的效果,就必須編寫一個不同的渲染器,按照我們想要的執(zhí)行順序進行平移,旋轉(zhuǎn)和伸縮變換。

然而,有些比我聰明的人利用數(shù)學中的矩陣能夠解決上面這個問題。對于 2d 圖形,使用一個 3X3 的矩陣。3X3 的矩陣類似了 9 宮格。

http://wiki.jikexueyuan.com/project/webgl/images/nine_boxes.png" alt="9 boxes" />

數(shù)學中的操作是與列相乘然后把結果加在一起。一個位置有兩個值,用 x 和 y 表示。但是為了實現(xiàn)這個需要三個值,因為我們對第三個值設為 1。

在上面的例子中就變成了:

http://wiki.jikexueyuan.com/project/webgl/images/matrix_transform.png" alt="matrix transformation" />

對于上面的處理你也許會想“這樣處理的原因在哪里”。假設要執(zhí)行平移變換。我們將想要執(zhí)行的平移的總量為 tx 和 ty。構造如下的矩陣:

http://wiki.jikexueyuan.com/project/webgl/images/matrix.png" alt="matrix" />

接著進行計算:

http://wiki.jikexueyuan.com/project/webgl/images/multiply_matrix.png" alt="multiply matrix" />

如果你還記得代數(shù)學,就可以那些乘積結果為零的位置。乘以 1 的效果相當于什么都沒做,那么將計算簡化看看發(fā)生了什么:

http://wiki.jikexueyuan.com/project/webgl/images/simplify_result.png" alt="simplify result" />

或者更簡潔的方式:

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

extra 變量我們并不用在意。這個處理和我們在平移中編寫的代碼驚奇的相似。

同樣地,讓我們看看旋轉(zhuǎn)。正如在旋轉(zhuǎn)那篇中指出當我們想要進行旋轉(zhuǎn)的時候,我們只需要角度的 sine 和 cosine 值。

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

構造如下的矩陣:

http://wiki.jikexueyuan.com/project/webgl/images/rotate_matrix.png" alt="rotate_matrix" />

執(zhí)行上面的矩形操作:

http://wiki.jikexueyuan.com/project/webgl/images/rotate_apply_matrix.png" alt="rotate_apply_matrix" />

將得到 0 和 1 結果部分用黑色塊表示了。

http://wiki.jikexueyuan.com/project/webgl/images/rotate_matrix_result.png" alt="rotate_matrix_result" />

同樣可以簡化計算:

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

上面處理的結果剛好和旋轉(zhuǎn)例子效果一樣。

最后是伸縮變換。稱兩個伸縮變換因子為 sx 和 sy。

構造如下的矩陣:

http://wiki.jikexueyuan.com/project/webgl/images/scale_matrix.png" alt="scale_matrix" />

進行矩陣操作會得到如下:

http://wiki.jikexueyuan.com/project/webgl/images/scale_apply_matrix.png" alt="scale apply matrix" />

實際需要計算:

http://wiki.jikexueyuan.com/project/webgl/images/scale_matrix_result.png" alt="scale matrix result" />

簡化為:

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

和我們以前講解的伸縮示例是一樣的。

到了這里,我堅信你仍然在思考,這樣處理之后了?有什么意義??雌饋砗孟笏皇亲隽撕臀覀円郧耙粯拥氖隆?/p>

接下來就是魔幻的地方。已經(jīng)被證明了我們可以將多個矩陣乘在一起,接著一次執(zhí)行完所有的變換。假設有函數(shù) matrixMultiply,它帶兩個矩陣做參數(shù),將他們倆相乘,返回乘積結果。

為了讓上面的做法更清楚,于是編寫如下的函數(shù)構建一個用來平移,旋轉(zhuǎn)和伸縮的矩陣:

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

如下是使用新的代碼的示例。平移,旋轉(zhuǎn)和伸縮滑動條是一樣的。但是他們在渲染器上應用的更簡單。

此時,你仍然會問,之后了?這個看起來并沒有方便多少。然而,此時如果你想改變執(zhí)行的順序,就不再需要編寫一個新的渲染器了。我們僅僅只需要改變數(shù)序公式。

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

如下是新版本:

能夠按照這種方式執(zhí)行矩陣操作是特別重要的,特別是對于層級動畫的實現(xiàn)比如身體上手臂的,在一個星球上看月球同時在圍繞著太陽旋轉(zhuǎn),或者數(shù)上的樹枝等都是很重要的。舉一個簡單的層級動畫例子,現(xiàn)在想要繪制 5 次 ‘F’,但是每次繪制是從上一個 ‘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);
    }
  }

為了實現(xiàn)這個,我們要編寫自己的函數(shù) makeIdentity,這個函數(shù)返回單位矩陣。單位矩陣實際上表示的類似于 1.0 的矩陣,如果一個矩陣乘以單位矩陣,那么得到的還是原先那個矩陣。就如:

X*1 = X

同樣:

matrixX*identity = matrixX

如下是構造單位矩陣的代碼:

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

如下是 5 個 F:

再來一個示例,在前面示例中,‘F’ 旋轉(zhuǎn)總是繞坐上角。這是因為我們使用的數(shù)學方法總是圍著源點旋轉(zhuǎn),并且 ‘F’ 的左上角就是原點,(0,0)。

但是現(xiàn)在,因為我們能夠使用矩陣,那么就可以選擇變化的順序,可以在執(zhí)行其他的變換之前先移動原點。

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

如下所示,注意 F 可以圍著中心點進行旋轉(zhuǎn)和伸縮。

使用如上的方法,你可以圍著任何點進行旋轉(zhuǎn)或者伸縮?,F(xiàn)在你就明白了 Photoshop 或者 Flash 中實現(xiàn)繞某點旋轉(zhuǎn)的原理。

讓我們學習更深入點。如果你回到本系列的第一篇文章 WebGL 基本原理,你也許還記得我們編寫的渲染器的代碼中將像素轉(zhuǎn)換成投影空間,如下所示:

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

如果你現(xiàn)在反過來看下每一步,第一步,“將像素變換成 0.0 變成 1.0”,其實是一個伸縮操作。第二步同樣是伸縮變換。接下來是平移變換,并且 Y 的伸縮因子是 -1。我們可以通過將該矩陣傳給渲染器實現(xiàn)上面的所有操作。可以構造二維伸縮矩陣,其中一個伸縮因子設置為 1.0/分辨率,另外一個伸縮因子設置為 2.0,第三個使用 -1.0,-1.0 來進行移動,并且第四個設置伸縮因子 Y 為 -1,接著將他們乘在一起,然而,因為數(shù)學是很容易的,我們僅僅只需編寫一個函數(shù),能夠直接將給定的分辨率轉(zhuǎn)換成投影矩陣。

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
  ];
}

現(xiàn)在我們能進一步簡化渲染器。如下是完整的頂點渲染器。

<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.clientWidth, canvas.clientHeight);
    ...

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

我們也移出了設置分辨率的代碼。最后一步,通過使用數(shù)學矩陣就將原先需要 6-7 步操作復雜的渲染器變成僅僅只需要 1 步操作的更簡單的渲染器。

希望這篇文章能夠讓你理解矩陣數(shù)學。接下來會講解 3D 空間的知識。在 3D 中矩陣數(shù)學遵循同樣的規(guī)律和使用方式。從 2D 開始講解是希望讓知識簡單易懂。