变换链概览
理解渲染管线中顶点数据的完整变换过程
在 Unity Shader 中,顶点从模型数据到最终显示在屏幕上,需要经过一系列坐标系转换。这个过程被称为顶点变换管线,是理解 3D 渲染的核心基础。

💡
为什么需要这么多坐标系?
每个坐标系都有其特定的用途:模型空间方便建模,世界空间用于场景管理,观察空间简化投影计算,裁剪空间用于视锥体裁剪,屏幕空间对应最终像素位置。
模型空间 (Model Space)
顶点数据的原始坐标系,以模型自身为参考
📐定义与特性
-
局部坐标系
每个模型都有自己的坐标系原点,通常位于模型的几何中心或底部中心。
-
建模坐标系
3D 建模软件(如 Blender、Maya)中创建模型时使用的坐标系。
-
顶点属性
顶点位置、法线、切线等数据在模型空间中定义,存储在 Mesh 中。
🎯实际应用
在 Unity Shader 中,模型空间顶点数据通过顶点着色器输入获取:
cs
struct appdata {
float4 vertex : POSITION; // 模型空间顶点位置
float3 normal : NORMAL; // 模型空间法线
float2 uv : TEXCOORD0; // 纹理坐标
};

03
世界空间 (World Space)
场景的统一坐标系,所有模型的公共参考系
🌍模型到世界的变换

其中 Mmodel 是模型变换矩阵,由平移、旋转、缩放组合而成:

cs
// Unity 内置矩阵:unity_ObjectToWorld
float4 worldPos = mul(unity_ObjectToWorld, vertex);
// 或者使用 TransformObjectToWorld 函数
float3 worldPos = TransformObjectToWorld(vertex.xyz);
⚠️
注意法线变换
法线需要使用逆转置矩阵进行变换,以保证在非标量缩放时保持正确:worldNormal = normalize(mul((float3x3)unity_WorldToObject, normal))
观察空间 (View Space)
以摄像机为原点的坐标系,简化投影计算
📷观察空间特性
-
摄像机坐标系
原点位于摄像机位置,Z轴指向摄像机朝向的反方向(观察方向)。
-
右手坐标系
Unity 使用右手坐标系:X 向右,Y 向上,Z 向后(远离摄像机)。
-
视图变换
将世界空间转换为以摄像机为中心的局部空间。
🔢观察矩阵推导
观察矩阵是摄像机变换矩阵的逆矩阵:

包含两个步骤:
- 平移:将世界原点移动到摄像机位置
- 旋转:对齐坐标轴到摄像机朝向
💻Shader 实现
cs
// 方法1:使用内置矩阵 unity_MatrixV
float4 viewPos = mul(unity_MatrixV, worldPos);
// 方法2:使用 UNITY_MATRIX_V 宏
float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
// 方法3:URP/HDRP 中的变换函数
float3 viewPos = TransformWorldToView(worldPos.xyz);
裁剪空间 (Clip Space)
投影变换后的齐次裁剪坐标系
🔺投影变换
裁剪空间是顶点着色器的输出空间,通过投影矩阵将观察空间转换为裁剪空间。这个变换同时处理透视除法前的齐次坐标。

| 投影类型 | 特点 | 应用场景 |
|---|---|---|
| 透视投影 | 近大远小,符合人眼视觉 | 3D 游戏、真实感渲染 |
| 正交投影 | 平行线保持平行,无透视变形 | 2D 游戏、UI、工程制图 |

cs
// 顶点着色器输出裁剪空间位置
float4 vert (appdata v) : SV_POSITION {
float4 worldPos = mul(unity_ObjectToWorld, v.vertex);
float4 viewPos = mul(UNITY_MATRIX_V, worldPos);
float4 clipPos = mul(UNITY_MATRIX_P, viewPos);
return clipPos;
}
// 或者使用 MVP 矩阵一次性变换
return mul(UNITY_MATRIX_MVP, v.vertex);
屏幕空间 (Screen Space)
最终的像素坐标系,对应显示器上的实际位置

📝屏幕空间坐标获取
cs
// 片段着色器中获取屏幕坐标
float4 frag (float4 pos : SV_POSITION) : SV_Target {
// pos.xy 已经是屏幕空间坐标 (像素位置)
float2 screenUV = pos.xy / _ScreenParams.xy;
// 或者使用内置宏
float2 screenUV = ComputeScreenPos(pos).xy;
return float4(screenUV, 0, 1);
}
总结与速查表
坐标系转换的核心要点回顾
| 坐标系 | 范围 | 变换矩阵/函数 | 用途 |
|---|---|---|---|
| 模型空间 | 模型局部 | 顶点输入数据 | 存储顶点原始位置 |
| 世界空间 | 场景全局 | unity_ObjectToWorld |
光照计算、物理模拟 |
| 观察空间 | 摄像机局部 | UNITY_MATRIX_V |
视锥体剔除、投影准备 |
| 裁剪空间 | 齐次坐标 | UNITY_MATRIX_P |
顶点着色器输出 |
| NDC | [-1, 1]³ | 透视除法 (w 除法) | 硬件裁剪 |
| 屏幕空间 | [0, W]×[0, H] | 视口变换 | 像素位置、后处理 |
🎓
学习建议
理解这些坐标系转换是掌握 Shader 编程的基础。建议通过实际编写 Shader 来加深理解,尝试在顶点着色器中输出不同空间的坐标作为颜色,观察变换效果。