第五章:矩阵与变换——从数学到屏幕的魔法
说实话,矩阵这东西,我第一次接触的时候也是一头雾水。大学线性代数课上,老师讲得天花乱坠,我就在想:这玩意儿到底能干嘛?直到我开始做 OpenGL,才恍然大悟——原来矩阵就是图形学的「通用语言」。今天我们就来聊聊,怎么用矩阵把 3D 世界搬到 2D 屏幕上。
5.1 正交投影 vs 透视投影:你选哪个?
先问个问题:你希望你的 3D 场景看起来像「工程图纸」还是「真实照片」?
正交投影,说白了就是没有近大远小。不管物体离你多远,大小都一样。我最早做 CAD 工具时用的就是它——因为要精确测量尺寸,透视反而碍事。
透视投影,模拟人眼效果。近处的东西大,远处的东西小。游戏、VR、AR 都用它。嗯,这里有个坑:透视投影的矩阵计算比正交复杂不少,但效果也真实得多。
核心区别一句话:
- 正交投影:平行线永远平行
- 透视投影:平行线在远处汇聚于一点(灭点)
我在项目中遇到过一件事:有个同事做地图应用,用了透视投影,结果远处的地标缩成一个小点,用户根本看不清。后来改成正交投影,问题就解决了。所以,选哪个,看你的应用场景。
5.2 平移、旋转、缩放:三大基本变换
这三个操作,是 3D 图形学的「三原色」。任何复杂的变换,都可以拆解成它们的组合。
5.2.1 平移矩阵
平移,就是把物体从 A 点挪到 B 点。数学上很简单:
// 平移矩阵(4x4)
[1, 0, 0, tx]
[0, 1, 0, ty]
[0, 0, 1, tz]
[0, 0, 0, 1 ]
tx、ty、tz 就是你在 x、y、z 方向上移动的距离。为什么用 4x4 矩阵?因为 3x3 搞不定平移——这是个历史遗留问题,但也是标准做法。
5.2.2 旋转矩阵
旋转稍微麻烦点。绕 x 轴、y 轴、z 轴旋转,公式都不一样。我记性不好,从来不去背这些公式,直接查表或者用现成的函数。
// 绕 Z 轴旋转 θ 角度
[cosθ, -sinθ, 0, 0]
[sinθ, cosθ, 0, 0]
[0, 0, 1, 0]
[0, 0, 0, 1]
我的小技巧:旋转顺序很重要!先绕 x 再绕 y,和先绕 y 再绕 x,结果完全不同。我习惯用「XYZ」顺序,但具体看项目需求。
5.2.3 缩放矩阵
缩放最简单:
[sx, 0, 0, 0]
[0, sy, 0, 0]
[0, 0, sz, 0]
[0, 0, 0, 1]
sx、sy、sz 是各轴的缩放比例。如果三个值相等,就是均匀缩放;不等,就是拉伸。
注意:非均匀缩放(比如 sx=2, sy=1, sz=1)会导致法向量计算出错。我曾经在这上面栽过跟头——光照效果怎么调都不对,最后发现是缩放矩阵搞的鬼。
5.3 MVP 矩阵:从模型到屏幕的完整流水线
MVP 是 Model、View、Projection 的缩写。说白了,就是三个矩阵的乘积:
MVP = Projection × View × Model
为什么要分开?因为每个矩阵管一件事:
- Model 矩阵:把物体从「本地坐标」变换到「世界坐标」。比如一个茶壶,它的顶点坐标是相对于茶壶本身的,Model 矩阵把它放到游戏场景中。
- View 矩阵:把「世界坐标」变换到「观察坐标」。说白了,就是「摄像机」的位置和朝向。
- Projection 矩阵:把「观察坐标」变换到「裁剪坐标」。这一步决定了你是正交还是透视。
我刚开始学的时候,总搞不清 View 和 Model 的区别。后来想明白了一件事:移动摄像机,和反向移动整个世界,效果是一样的。View 矩阵其实就是「反向的 Model 矩阵」——只不过作用在摄像机上。
5.4 知识体系总览
下面这张图,是我自己总结的 MVP 矩阵完整流程。你看一遍,应该就能串起来了:
5.5 实战:构建 MVP 矩阵
理论说完了,来点实际的。假设我们要渲染一个旋转的立方体:
// 伪代码:构建 MVP 矩阵
Matrix4x4 model = Matrix4x4.identity();
model = model * Matrix4x4.translate(0, 0, -5); // 移到摄像机前方
model = model * Matrix4x4.rotateY(angle); // 绕 Y 轴旋转
Matrix4x4 view = Matrix4x4.lookAt(
vec3(0, 2, 5), // 摄像机位置
vec3(0, 0, 0), // 看向原点
vec3(0, 1, 0) // 上方向
);
Matrix4x4 projection = Matrix4x4.perspective(
45.0f, // 视场角 45°
16.0f / 9.0f, // 宽高比
0.1f, // 近裁剪面
100.0f // 远裁剪面
);
Matrix4x4 mvp = projection * view * model;
避坑指南:我曾经把投影矩阵的 near 值设成了 0,结果画面全黑了。为什么?因为 near 必须大于 0,否则透视除法会出问题。记住:near 和 far 都要是正数,且 near > 0。
5.6 矩阵运算的注意事项
最后,分享几个我踩过的坑:
- 矩阵乘法不满足交换律:A × B ≠ B × A。所以顺序一定要搞对。
- OpenGL 是列主序:存储矩阵时,先存列再存行。这和数学课本上的写法是转置关系。我刚开始就搞反了,结果图形全乱套了。
- 避免频繁计算逆矩阵:逆矩阵计算开销很大。如果只是做变换,用正向矩阵就够了。
- 注意浮点精度:连续做几十次矩阵乘法后,误差会累积。我习惯每隔一段时间重新计算一次,而不是一直累乘。
好了,矩阵与变换这部分就聊到这儿。记住一句话:矩阵是工具,不是目的。理解它背后的几何意义,比死记硬背公式重要得多。