Skip to main content

为什么矩阵更方便?—— 直观对比与底层逻辑

💡 核心结论:矩阵不仅是数学工具,更是图形学的"万能压缩包"。它把旋转、缩放、平移等复杂操作打包成 16 个数字,让 GPU 一次性完成所有变换。


1. 直观对比:代码复杂度的天壤之别

假设我们需要对一个 3D 模型做一个常见的复合变换:

  1. 旋转 45 度
  2. 缩放 2 倍
  3. 平移 到 (5, 3, 0)

❌ 方式一:不用矩阵(手动计算)

如果不使用矩阵,我们需要在 Shader 中手动一步步算出顶点的新位置。

// 顶点着色器 (Vertex Shader)
attribute vec3 position;

void main() {
vec3 pos = position;

// -------------------------------------------------
// 1. 手动计算旋转 (容易写错公式)
// -------------------------------------------------
float angle = radians(45.0);
float c = cos(angle);
float s = sin(angle);
// 旋转公式:x' = x*cos - y*sin, y' = x*sin + y*cos
float newX = pos.x * c - pos.y * s;
float newY = pos.x * s + pos.y * c;
vec3 rotated = vec3(newX, newY, pos.z);

// -------------------------------------------------
// 2. 手动计算缩放 (这就还得再乘一遍)
// -------------------------------------------------
vec3 scaled = rotated * vec3(2.0, 2.0, 2.0);

// -------------------------------------------------
// 3. 手动计算平移 (这就还得再加一遍)
// -------------------------------------------------
vec3 finalPos = scaled + vec3(5.0, 3.0, 0.0);

// -------------------------------------------------
// 4. 最后还得手动转齐次坐标...
// -------------------------------------------------
gl_Position = vec4(finalPos, 1.0);
}

痛点总结

  • 代码冗长:简单的操作写了 20 多行。
  • 容易出错:三角函数公式 x*c - y*s 很容易记反。
  • 顺序混乱:如果先平移再旋转,结果完全不同,逻辑很难维护。
  • 性能浪费:每个顶点都要在 GPU 上重复做三角函数运算(sin/cos),非常慢。

✅ 方式二:使用矩阵(一行搞定)

使用矩阵,所有的变换在 CPU 端就被"打包"好了一个矩阵 modelMatrix

// 顶点着色器 (Vertex Shader)
attribute vec3 position;
uniform mat4 modelMatrix; // CPU 传来的"万能变换包"

void main() {
// -------------------------------------------------
// 💎 核心逻辑:一行代码完成所有变换
// -------------------------------------------------
gl_Position = modelMatrix * vec4(position, 1.0);
}

优势总结

  • 极其简洁:核心逻辑只有 1 行。
  • 逻辑清晰:不需要关心具体是先加减还是先乘除,矩阵乘法自动处理。
  • 性能极高:矩阵乘法是 GPU 的原生强项,硬件加速。

2. 深度解析:为什么矩阵这么神?

你可能会问:"矩阵不就是把算式换了个写法吗?" 不完全是。矩阵不仅是数学简写,它改变了计算的维度。

2.1 核心概念:矩阵 = "坐标系变换器"

不要把矩阵看作一堆数字,要把它看作三个新的坐标轴

一个 4x4 矩阵的前三列,实际上就代表了变换后的 X、Y、Z 轴的方向和长度

mat4 modelMatrix = mat4(
// 第一列:变换后的 X 轴 (New X Axis)
1.414, 1.414, 0.0, 0.0, // 这里的数字包含了"旋转45度"和"放大2倍"的信息

// 第二列:变换后的 Y 轴 (New Y Axis)
-0.707, 0.707, 0.0, 0.0, // 这里的数字包含了"旋转45度"的信息

// 第三列:变换后的 Z 轴 (New Z Axis)
0.0, 0.0, 1.0, 0.0, // Z 轴没变

// 第四列:原点位置 (Translation)
5.0, 3.0, 0.0, 1.0 // 物体被移动到了这里
);

直观理解: 当你做 matrix * position 时,GPU 实际上是在说:

"以前的 X 轴方向不作数了,现在沿着矩阵第一列的方向走 position.x 那么多;沿着矩阵第二列的方向走 position.y 那么多..."

2.2 核心优势:组合性 (Composability)

这是矩阵最强大的地方。如果你需要做一个极其复杂的动作:

  1. 旋转 30 度
  2. 往右移 10 米
  3. 缩小 0.5 倍
  4. 再绕 X 轴转 90 度
  5. 再往上移 2 米

不用矩阵:你需要写 5 段复杂的代码,而且如果不小心搞乱了顺序(比如先平移再缩放),结果全错。

使用矩阵: 在 CPU (JavaScript) 端,你只需要做简单的矩阵乘法:

// 伪代码:变换组合
// 最终矩阵 = 变换5 * 变换4 * 变换3 * 变换2 * 变换1
const finalMatrix = T2.multiply(R2).multiply(S).multiply(T1).multiply(R1);

无论过程多复杂,最终传给 GPU 的永远只是一个 4x4 的矩阵(16 个数字)。Shader 代码永远不需要改动!


3. 实际工作流对比 (Three.js / WebGL)

让我们看看从 CPU 到 GPU 的数据流向,彻底理解为什么这种方式高效。

场景:渲染 10,000 个顶点

❌ 低效流程(手动计算)

  1. CPU:不干活,直接把顶点发给 GPU。
  2. GPU
    • 拿到第 1 个顶点:算 sin(45), 算 cos(45), 乘法, 加法...
    • ...
    • 拿到第 10,000 个顶点:重复算一遍 sin(45)...
    • 结果:做了 10,000 次重复的三角函数运算。

✅ 高效流程(矩阵流水线)

  1. CPU(JavaScript):
    • 计算 modelMatrix(只算 1 次)。
    • 包含:Rotate(45) * Scale(2) * Translate(5,3)
    • 把这 16 个数字传给 GPU。
  2. GPU
    • 拿到第 1 个顶点:Matrix * Vertex (硬件指令,极快)。
    • ...
    • 拿到第 10,000 个顶点:Matrix * Vertex
    • 结果:没有重复的三角函数计算,纯粹的线性代数运算。

4. 总结:何时该用矩阵?

维度不用矩阵 (Scalar Math)使用矩阵 (Matrix Math)
代码量几十行,逻辑分散1 行核心代码
可读性差,充满数学公式好,语义清晰
灵活性极差,改需求要重写 Shader极高,改 CPU 参数即可
性能差,GPU 重复计算复杂函数优,CPU 预计算,GPU 纯乘法
适用场景几乎没有(除非做极为特殊的非线性扭曲)99.9% 的图形学变换

💡 记忆口诀

CPU 负责"配方"(构建矩阵),GPU 负责"炒菜"(矩阵乘顶点)。 无论配方多复杂(旋转缩放平移一百次),传给大厨的永远只是一张纸(一个矩阵)。

这也是为什么 MVP 矩阵(Model-View-Projection)是图形学的基石——它把从物体坐标到屏幕像素的所有复杂数学变换,都浓缩成了三次矩阵乘法。