前面几篇主要站在客户端和 Transform 的角度讲矩阵,这一篇换一个视角:从 Shader 看顶点变换。
我自己第一次真正把这条链想明白,不是在看教材,而是在改 Shader 的时候发现“宏虽然能跑,但只要要接世界空间法线、屏幕空间效果或者深度效果,脑子里没这条空间链就一定会乱”。
很多 Unity 程序员第一次真正意识到矩阵的重要性,往往不是在 Transform,而是在 Shader 里看到一长串空间名:
- Object Space
- World Space
- View Space
- Clip Space
如果不把这条链串起来,写 Shader 很容易变成“宏能跑就行”。
一、MVP 其实就是一条连续的坐标转换链
MVP 分别是:
- Model
- View
- Projection
它们描述的不是三种孤立矩阵,而是顶点要经过的三段路:
- 模型空间到世界空间
- 世界空间到观察空间
- 观察空间到裁剪空间
一个顶点从模型自己的局部坐标出发,最终能不能落到屏幕上,就靠这条链。
二、模型空间:顶点最初的家
模型空间就是美术资源自身的局部坐标系。
比如一个立方体资源,它的顶点可能天然围绕 (0, 0, 0) 排布。
这个阶段,顶点只知道自己在模型里的相对位置,不知道:
- 这个物体被摆到场景哪里了
- 它朝什么方向
- 摄像机站在哪看它
所以模型空间的顶点,必须先经过 Model 变换。
三、Model 矩阵:把局部顶点带进世界
Model 矩阵本质上就是 localToWorldMatrix。
它把模型自己的顶点,从局部坐标转换到场景世界坐标。
在 Unity 语境里,你可以把它理解成:
这个物体在场景中的 TRS 总结果。
如果没有这一步,顶点永远只能待在资源自己的小世界里。
四、View 矩阵:把世界重新换成“相机眼中的世界”
世界空间看起来很自然,但 GPU 真正做投影前,更关心的是“相机视角下”的位置。
所以接下来要用 View 矩阵,把世界空间转换成观察空间。
你可以把它理解成:
不是相机去看世界,而是把整个世界搬到相机坐标系里。
做完这一步之后,相机就像站在原点,朝固定方向看出去。
五、Projection 矩阵:把三维体积压到可裁剪的空间里
到了观察空间,顶点已经相对于相机定位好了,但还没完成“投影”。
Projection 矩阵做的事情,是把三维观察体映射到一个规范裁剪空间。
这个阶段会引入:
- 透视缩小
- 近平面和远平面范围
- 屏幕最终可见区域
如果是透视相机,远处物体会变小;如果是正交相机,则不会有透视缩放。
这就是 Projection 的核心区别。
六、为什么 Shader 里经常直接写 MVP
因为对顶点来说,这三段变换是连续的。
所以完全可以把它们预先合成一个总矩阵:
$$
MVP = P \cdot V \cdot M
$$
然后一次把模型顶点送到裁剪空间。
这对 GPU 很友好,也符合渲染流水线的处理方式。
七、Unity 里最常见的对应接口和宏
在 Unity Shader 里,你通常会碰到这些:
unity_ObjectToWorldUNITY_MATRIX_VUNITY_MATRIX_PUNITY_MATRIX_MVP
如果你只把它们当神秘常量,后面写世界空间法线、屏幕空间效果、深度相关逻辑时会很吃力。
但如果你知道它们分别在做哪段空间转换,就会清楚很多。
例如顶点着色器里最常见的最小路径,其实就长这样:
1 | struct appdata |
如果你想把这段再拆开看,也完全可以显式走三步:
1 | float4 worldPos = mul(unity_ObjectToWorld, v.vertex); |
这两种写法本质一样,只是后一种更适合调试“我到底在哪一段空间出了问题”。
八、为什么顶点着色器常常输出 SV_POSITION
因为顶点最终要交给光栅化阶段,必须先进入裁剪空间语义。
在 HLSL 里,通常会把经过 MVP 变换后的结果输出到 SV_POSITION。
这不是随便起的字段名,而是图形管线约定好的“顶点最终裁剪位置”。
九、一个最常见的实战问题:明明世界坐标对了,屏幕上却不对
这类问题很常见,特别是在做:
- 屏幕特效
- 轮廓线
- 深度相关着色
- 世界空间贴图
根因通常是空间搞混。
比如:
- 你以为自己在用世界坐标,实际还在模型坐标
- 你以为法线在观察空间,实际还是对象空间
- 你把裁剪空间值拿去和世界空间值直接比较
矩阵知识在这里的作用,不是让你手推公式,而是让你能快速定位:
我现在这份数据,究竟处在哪个空间。
十、为什么做 UI 跟随、屏幕投影时也会回到这条链
把 3D 物体头顶血条投到屏幕上,本质上也离不开这条变换链。
它只是把“模型顶点走进裁剪空间”的逻辑,换成“世界点走进屏幕坐标”的逻辑而已。
换句话说,MVP 不只是 Shader 内部知识,它其实和客户端很多屏幕空间功能是连着的。
十一、调试 Shader 空间问题时最实用的思路
不要一上来怀疑平台、精度、宏定义。
先问自己三个问题:
- 我当前这份坐标在哪个空间?
- 我要比较或使用的另一份数据在哪个空间?
- 这两份数据是否经过了同一条矩阵链?
大多数空间错乱问题,都是这三问里的某一个没对齐。
十二、总结
MVP 不只是图形学教材里的缩写,它是 Unity 渲染里最核心的一条坐标转换链。
理解它之后,你再看模型空间、世界空间、观察空间、裁剪空间,就不会是四个散词,而是一条连续流程。
下一篇我会继续讲一个更容易踩坑的地方:非等比缩放为什么会把法线搞坏,以及 normal matrix 到底解决了什么问题。