从 WebGL 的渲染链路来拆解矩阵:先解释它是什么、为什么必须有,再把模型矩阵、视图矩阵、投影矩阵和它们的相乘顺序串起来。
核心概念
- 在 WebGL 里,
矩阵可以理解成一种"批量描述空间变换的表格"。 - 它最重要的作用是:把一个点从"原来的坐标系"转换到"另一个坐标系"。
- 这些变换包括:
平移、旋转、缩放、投影,以及它们的组合。 - 如果说"向量"描述的是一个点或一个方向,那么"矩阵"描述的就是"怎么去变换这些点和方向"。
用一句话理解
- 你在场景里画一个立方体,最开始它可能只是建模时的一组顶点坐标。
- 但真正显示到屏幕上前,它要经历几次坐标变换:
- 从"物体自己的局部坐标"变成"场景中的世界坐标"
- 从"世界坐标"变成"摄像机看到的坐标"
- 从"摄像机坐标"变成"最终可投影到屏幕的坐标"
- 这几步,基本都是靠矩阵完成的。
为什么 WebGL 特别依赖矩阵
- GPU 擅长做大规模并行的线性代数运算。
- 顶点着色器一次要处理海量顶点,矩阵乘法非常适合这种场景。
- 多个变换可以预先合并成一个矩阵,避免每个顶点做很多零散计算。
- 所以图形学里几乎所有"空间变换"都被抽象成矩阵。
矩阵到底是什么
- 数学上,矩阵就是一个按行列排列的数字表。
- 在 WebGL / 3D 图形里,最常见的是
4x4 矩阵。 - 原因是:3D 空间中的常见变换,用
4x4可以统一表示。 - 尤其是
平移,如果只用3x3很难和旋转、缩放统一起来,所以引入了齐次坐标,用4x4一把梭。
比如一个 4x4 矩阵长这样:
text
| m00 m01 m02 m03 |
| m10 m11 m12 m13 |
| m20 m21 m22 m23 |
| m30 m31 m32 m33 |
它作用在一个顶点上时,通常是:
text
v' = M * v
这里:
v是原始顶点M是变换矩阵v'是变换后的顶点
为什么是 4x4,不是 3x3 在 2D 里,很多变换用 3x3 就够了。 在 3D 里,为了把以下操作统一起来,通常使用 4x4:
缩放旋转平移透视投影
关键点是 齐次坐标:
text
(x, y, z) -> (x, y, z, 1)
多出来的这个 w,让平移也能写进矩阵乘法里。
一个最直观的例子:平移 假设一个点是 (1, 2, 3),你想把它沿 x 方向移动 5。
平移矩阵大致可以写成:
text
| 1 0 0 5 |
| 0 1 0 0 |
| 0 0 1 0 |
| 0 0 0 1 |
点写成齐次坐标:
text
| 1 |
| 2 |
| 3 |
| 1 |
相乘后得到:
text
| 6 |
| 2 |
| 3 |
| 1 |
也就是点从 (1,2,3) 变成 (6,2,3)。
这就是矩阵的本质:用一套统一规则变换顶点。
WebGL 里最常见的 3 个矩阵 图形学里最经典的是这三个:
Model Matrix,模型矩阵View Matrix,视图矩阵Projection Matrix,投影矩阵
很多时候还会把它们合成:
MVP = Projection * View * Model
下面分别讲。
1. 模型矩阵 Model Matrix
- 作用:把顶点从"模型本地坐标系"变到"世界坐标系"。
- 本地坐标系可以理解成:模型自己的坐标原点。
- 比如一个立方体建模时中心在
(0,0,0),这是它的局部空间。 - 当你把它放到场景中
(10, 0, -5),再旋转 30 度、缩放 2 倍,这些操作都在模型矩阵里。
模型矩阵常包含:
- 平移
- 旋转
- 缩放
举例:
- 原始顶点:
(1, 0, 0) - 模型旋转 90 度后,可能变成
(0, 1, 0) - 再平移到世界空间某个位置
所以模型矩阵回答的是:
- "这个物体在世界里放哪?"
- "朝向哪?"
- "有多大?"
2. 视图矩阵 View Matrix
- 作用:把世界坐标变成"摄像机坐标"。
- 你可以把它理解成:不是"摄像机在动",而是"整个世界相对摄像机反向移动"。
比如:
- 摄像机在
(0, 0, 5)看向原点 - 那么视图矩阵会把整个世界往
z负方向重新映射到摄像机眼里
核心理解:
- 模型矩阵决定"物体在哪"
- 视图矩阵决定"摄像机怎么看"
视图矩阵本质上是:
- 摄像机变换的逆矩阵
因为图形学里通常不是直接"移动摄像机",而是把所有物体变换到以摄像机为原点的空间里。
3. 投影矩阵 Projection Matrix
- 作用:把摄像机空间里的点投影到裁剪空间。
- 它决定"远小近大""视野范围""近平面远平面"等规则。
投影分两种:
透视投影 Perspective正交投影 Orthographic
透视投影:
- 远处东西看起来更小
- 更符合人眼和真实相机
- 3D 游戏、漫游场景最常见
正交投影:
- 远近大小不变
- 常用于 CAD、UI、工程图、编辑器视图
投影矩阵做完后,顶点会进入裁剪空间,后面再做透视除法和视口映射,最后到屏幕。
整个坐标变换链路 一个顶点从模型到屏幕,大致经过:
text
局部坐标 -> 世界坐标 -> 观察坐标 -> 裁剪坐标 -> NDC -> 屏幕坐标
对应矩阵过程通常写成:
glsl
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(position, 1.0);
这句非常重要。
它表示:
- 先用
modelMatrix把模型放到世界里 - 再用
viewMatrix转到摄像机空间 - 再用
projectionMatrix做投影 - 最后得到
gl_Position
为什么矩阵乘法顺序这么重要 矩阵乘法一般不满足交换律,也就是:
text
A * B != B * A
这意味着:
- 先旋转再平移
- 先平移再旋转
结果通常完全不同。
举个例子:
- 一个物体先旋转,再平移,像是"物体原地转完,再搬到别处"
- 先平移再旋转,像是"物体先离开原点,再围绕原点转圈"
所以在 3D 中,顺序错了,效果就会很怪。
矩阵为什么能把多个变换合并 如果一个物体要经历:
- 缩放
- 旋转
- 平移
你可以写成:
text
M = T * R * S
然后顶点只需要:
text
v' = M * v
而不需要每次分别算三遍。
这就是图形学里矩阵强大的地方:
- 可以组合变换
- 可以提前预计算
- 可以高效交给 GPU
再说说顶点和方向的区别 在图形学里,点和方向不完全一样。
常见写法:
- 点:
vec4(x, y, z, 1.0) - 方向:
vec4(x, y, z, 0.0)
区别在最后一个分量 w:
w = 1表示这是一个位置点,会受平移影响w = 0表示这是一个方向,不受平移影响
这很重要,比如:
- 法线
- 方向向量
- 光照方向
它们不应该因为物体平移就改变方向。
法线为什么也和矩阵有关 很多人刚学 WebGL 时容易忽略:
- 顶点位置要变换
- 法线也要变换
但法线不能总是直接用模型矩阵去乘,尤其在有非均匀缩放时会出问题。
通常会用:
normalMatrix = transpose(inverse(modelMatrix))
更准确地说,常用的是模型视图矩阵左上角 3x3 的逆转置。
原因是:
- 法线要保持与表面的垂直关系
- 普通位置变换矩阵在非均匀缩放时会破坏这种关系
所以法线矩阵本质上是"专门给法线用的修正版矩阵"。
矩阵和坐标系的关系 理解矩阵最好的方式之一,不是把它只看成"数字表",而是看成:
- 它定义了一个新的坐标系
- 或者把一个坐标系中的点,映射到另一个坐标系
比如模型矩阵其实描述了模型局部坐标系在世界中的:
- 原点在哪
- x 轴指向哪
- y 轴指向哪
- z 轴指向哪
所以矩阵不只是"移动点",更是在描述"空间的基底怎么变化"。
WebGL 里矩阵通常长什么样 在 JavaScript 里,WebGL 常把矩阵作为长度为 16 的数组传给 GPU,例如:
js
const matrix = new Float32Array(16);
配合:
js
gl.uniformMatrix4fv(location, false, matrix);
这里的几个点要注意:
uniformMatrix4fv用来给着色器传 4x4 矩阵- WebGL 里传的是一维数组
- 底层按列主序使用,这一点经常让初学者绕晕
行主序和列主序 这是很多人学矩阵时最头疼的地方之一。
你会经常看到两种说法:
- 行主序 row-major
- 列主序 column-major
简单理解:
- 它们是矩阵在内存里的排布方式
- 以及你阅读、书写、乘法习惯的差异
在 WebGL / OpenGL 体系里,通常按列主序理解和传递更自然。
但对初学者来说,更重要的不是死记术语,而是统一自己的思维:
- 你的数学定义是什么
- 你的库怎么存
- 你的乘法顺序是什么
只要这三者一致,就不容易错。
WebGL 中一个典型的顶点着色器
glsl
attribute vec3 aPosition;
uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;
void main() {
gl_Position = uProjectionMatrix * uViewMatrix * uModelMatrix * vec4(aPosition, 1.0);
}
这段代码做的事就是:
- 取模型原始顶点
aPosition - 扩展成齐次坐标
vec4(..., 1.0) - 依次乘上三个矩阵
- 输出最终裁剪空间坐标
gl_Position
如果没有矩阵会怎样 那你就得手写每个顶点的:
- 位移公式
- 旋转公式
- 缩放公式
- 透视投影公式
不仅麻烦,而且很难组合,更无法高效放到 GPU 并行执行。
所以矩阵其实是图形学里的一种"统一语言"。
从直觉上理解旋转矩阵 以 2D 为例,旋转矩阵本质是在说:
- 原来的 x 轴和 y 轴旋转后指向哪里
所以旋转不是"点自己变魔术",而是:
- 坐标轴基底变了
- 点在新基底下的位置被重新表达
这个理解对 3D 非常重要。 因为一旦你把矩阵看成"坐标系变换器",很多问题都会清楚:
- 为什么顺序不能乱
- 为什么父子节点变换要相乘
- 为什么摄像机矩阵常常是逆矩阵
和 three.js 里的关系 虽然学的是 WebGL,但如果你接触 three.js,会更容易串起来:
object.matrix:对象本地变换矩阵object.matrixWorld:对象世界矩阵camera.matrixWorldInverse:视图矩阵camera.projectionMatrix:投影矩阵
最后在渲染时,本质上仍然会形成类似:
text
projection * view * model
所以 three.js 只是帮你把这些矩阵管理好了,底层思想和 WebGL 一样。
父子层级为什么离不开矩阵 如果一个物体挂在另一个物体下面,比如:
- 手臂挂在身体上
- 摄像机挂在车辆上
那么子物体的世界矩阵通常是:
text
childWorldMatrix = parentWorldMatrix * childLocalMatrix
这意味着:
- 父对象一动,子对象跟着动
- 父对象一转,子对象整体跟着转
这就是场景图的核心,也是矩阵在引擎架构里最重要的用途之一。
初学者最容易混淆的几个点
矩阵不是点:矩阵描述的是变换规则,不是某个位置顺序很重要:乘法顺序一错,结果就变模型/视图/投影不是一回事:它们负责不同坐标空间的转换平移能写进 4x4:靠的是齐次坐标,不是普通 3x3法线不能随便乘模型矩阵:非均匀缩放时要用法线矩阵摄像机矩阵常常看起来反着:因为视图矩阵本质是摄像机变换的逆
一句话总结
- 在 WebGL 中,矩阵就是"把顶点从一个空间变到另一个空间的数学工具"。
- 它是整个 3D 渲染流程的骨架。
- 你看到的物体位置、旋转、大小、摄像机视角、透视效果,几乎都离不开矩阵。