一起学 WebGL:复合矩阵

开发 前端
矩阵乘法,一种理解方式是:A 的第 i 行的每个元素,依次乘以 B 的第 j 列的对应元素,然后加起来,作为新矩阵的第 [i][j] 个元素。

大家好,我是前端西瓜哥。之前讲了平移矩阵、旋转矩阵以及缩放矩阵,以及演示了在 WebGL 中的单独应用的效果。

这次我们看看同时进行多次矩阵变换的组合写法。

我们将会对一个三角形先平移,然后旋转。

矩阵乘法

一个坐标(矢量),先进行矩阵变形 1,然后再做矩阵变换 2,它的写法是:

m2 * m1 * vec

第一个矩阵 A 的列数如果等于第二个矩阵 B 的行数,那这两个矩阵可以做乘法。

矩阵乘法不满足交换律,即 AxB 通常不等于 BxA。

矩阵乘法,一种理解方式是:A 的第 i 行的每个元素,依次乘以 B 的第 j 列的对应元素,然后加起来,作为新矩阵的第 [i][j] 个元素。

矩阵乘法的计算顺序为从左往右。

以一个比较低维度的 2x2 矩阵为例:

图片

矩阵乘法满足交换律。

m2 * m1 * vec
= m2 * (m1 * vec)
= (m2 * m1) * vec

所以,一个顶点要做多次矩阵变换,我们可以先计算这些矩阵的结果,得到一个复合矩阵,然后再乘以顶点。

当多个顶点做的矩阵变换是一样的时候,我们就能减少计算量,一些线性计算就得到结果。

这种将一个模型丢到世界坐标系中进行各种变换,我们也称为 模型变换(model transform)。

顶点着色器中运算

顶点着色器代码:

const vertexShaderSrc = `
attribute vec4 a_Position;
uniform mat4 u_translateMatrix;
uniform mat4 u_rotateMatrix;

mat4 compoundMatrix = u_rotateMatrix * u_translateMatrix;
void main() {
 gl_Position = compoundMatrix * a_Position;
}
`;

声明了两个 uniform 类型的矩阵 u_translateMatrix 和 u_rotateMatrix,分别保存平移矩阵和旋转矩阵,然后将它们相乘。

compoundMatrix 因为不需要外部传入值,所以不需要加 uniform 声明。

因为是先平移再旋转,所以平移矩阵在右,旋转矩阵在左。

平移矩阵的构造:

/****** (1) 平移矩阵 ****/
const dx = 0.5; // 向右移动
const dy = 0; // 向下移动
// z 先不管,没用到透视矩阵,设置值也看不到效果
const translateMatrix = new Float32Array([
  1, 0, 0, 0,
  0, 1, 0, 0,
  0, 0, 1, 0,
  dx, dy, 0, 1
]);
// 传入顶点着色器
const u_translateMatrix = gl.getUniformLocation(
  gl.program,
  "u_translateMatrix"
);
gl.uniformMatrix4fv(u_translateMatrix, false, translateMatrix);

旋转矩阵的构造:

/****** (2) 旋转矩阵 ****/
const angle = 60;
const radian = (angle * Math.PI) / 180;
const cos = Math.cos(radian);
const sin = Math.sin(radian);
// prettier-ignore
const rotateMatrix = new Float32Array([
  cos, sin, 0, 0,
  -sin, cos, 0, 0,
  0, 0, 1, 0,
  0, 0, 0, 1
]);
const u_rotateMatrix = gl.getUniformLocation(gl.program, "u_rotateMatrix");
gl.uniformMatrix4fv(u_rotateMatrix, false, rotateMatrix);

demo 地址:

https://codesandbox.io/s/eg66e2?file=/index.js。

渲染结果:

图片

使用 JavaScript 运算

换成用 JavaScript 来做矩阵乘法,将计算好的矩阵传入到着色器中。

顶点着色器只要声明一个 uniform 类型的 u_compoundMatrix 就好。

uniform 是一个不变的类型,使用下一顶点时还会使用原来的值。如果顶点更换时,值会变,你应该使用的是 attribute 类型。

const vertexShaderSrc = `
attribute vec4 a_Position;
uniform mat4 u_compoundMatrix;
void main() {
 gl_Position = u_compoundMatrix * a_Position;
}
`;

创建完两个 Float32Array 的数组后,我们用自己实现的 JavaScript 将它们相乘。

/*************** 计算复合矩阵 *************/
// 两个 4x4 矩阵相乘的方法。m1 x m2
function multiply(m1, m2) {
  const size = 4;
  const m = new Float32Array(size * size);
  for (let i = 0; i < size; i++) {
    for (let j = 0; j < size; j++) {
      let sum = 0;
      for (let k = 0; k < size; k++) {
        sum += m2[i * size + k] * m1[k * size + j];
      }
      m[i * size + j] = sum;
    }
  }
  return m;
}

// 这里创建了俩 Float32Array 数组
// ...

// 两个矩阵相乘,得到复合变换矩阵
const xformMatrix = multiply(rotateMatrix, translateMatrix);
// 将计算出来的矩阵传给着色器
const u_compoundMatrix = gl.getUniformLocation(gl.program, "u_compoundMatrix");
gl.uniformMatrix4fv(u_compoundMatrix, false, xformMatrix);

在实现 multiply 方法过程中,有一个容易踩的坑,就是 WebGL 中的矩阵是 按列主序 的,即 m[0] 到 m[1] 对应的是第一列,而不是第一行,我们实现 JavaScript 矩阵乘法的时候要注意,否则就会从 AxB 变成了 BxA。

西瓜哥我就踩了这个坑,发现 JavaScript 实现和预期不符,居然是先旋转再移动,大眼瞪了半天才发现自己把左边矩阵的列当成行去运算了。

demo 地址:

https://codesandbox.io/s/hw8wfi?file=/index.js。

渲染效果和在顶点着色器计算的一样:

图片

如果我们将两个变换矩阵的顺序交换一下,就会得到一个先旋转再移动的效果。感兴趣的朋友可以去线上 demo 修改试试。

图片

结尾

我是前端西瓜哥,欢迎关注我,学习更多前端知识。

顶点的变换可以用矩阵乘法来表示,乘以一个矩阵就是一个变形(比如平移、旋转、缩放)。如果应用了多个矩阵,我们可以利用矩阵乘法的结合律将这几个矩阵先预先计算好。

另外一个坑,就是矩阵是用一维数组表示的,且为比较别扭的按列主序,声明的数组在形式上和矩形真正的样子有一点不同,需要沿着从左上到右下的轴线进行一个翻转才行。

责任编辑:姜华 来源: 前端西瓜哥
相关推荐

2023-04-27 08:27:29

WebGL变形矩阵

2023-04-12 07:46:24

JavaScriptWebGL

2023-03-29 07:31:09

WebGL坐标系

2023-06-26 15:14:19

WebGL纹理对象学习

2023-04-26 07:42:16

WebGL图元的类型

2023-05-08 07:29:48

WebGL视图矩阵

2023-05-16 07:44:03

纹理映射WebGL

2023-04-13 07:45:15

WebGL片元着色器

2023-05-31 20:10:03

WebGL绘制立方体

2023-04-11 07:48:32

WebGLCanvas

2023-05-17 08:28:55

2023-04-17 09:01:01

WebGL绘制三角形

2022-12-02 14:20:09

Tetris鸿蒙

2022-11-29 16:35:02

Tetris鸿蒙

2022-11-14 17:01:34

游戏开发画布功能

2023-03-30 09:32:27

2023-05-06 07:23:57

2024-02-28 12:12:20

Pipeline数据机制

2023-11-13 22:27:53

Mapping数据库

2023-02-28 07:28:50

Spritepixijs
点赞
收藏

51CTO技术栈公众号