引言
为什么 3D 游戏能在 2D 屏幕上显示?这归功于一系列的坐标变换,统称为渲染管线 (Rendering Pipeline)。 核心公式是: \[ P_{screen} = Viewport \times Projection \times View \times Model \times P_{local} \]
1. 坐标空间 (Coordinate Spaces)
1.1 局部空间 (Local Space)
模型原本的坐标系。例如,一个角色的原点通常在脚底。 所有顶点都是相对于这个原点定义的。
1.2 世界空间 (World Space)
变换矩阵: Model Matrix (\(M\)) 将物体放入游戏世界中。包含平移、旋转和缩放。 \[ P_{world} = M \times P_{local} \]
1.3 观察空间 (View Space)
变换矩阵: View Matrix (\(V\)) 以摄像机为原点的坐标系。 实际上,移动摄像机等同于反向移动整个世界。 \[ P_{view} = V \times P_{world} \]
1.4 裁剪空间 (Clip Space)
变换矩阵: Projection Matrix (\(P\)) 这是最神奇的一步。它定义了摄像机能看到的范围(视锥体 Frustum)。 在这个空间中,坐标是齐次坐标 \((x, y, z, w)\)。 \[ P_{clip} = P \times P_{view} \]
2. 透视除法 (Perspective Division)
在裁剪空间之后,GPU 会自动执行一步操作:将 \(x, y, z\) 除以 \(w\)。 \[ P_{ndc} = (x/w, y/w, z/w) \] 结果称为标准化设备坐标 (NDC),范围通常是 \([-1, 1]\)。
- 为什么远处的物体看起来小? 投影矩阵会根据顶点的 \(z\) 值(深度)来设置 \(w\) 值。 \(z\) 越大(越远),\(w\) 越大。 除以一个大的 \(w\),得到的 \(x, y\) 就越小,物体在屏幕上就越小。
3. 屏幕空间 (Screen Space)
最后,将 NDC 坐标映射到屏幕像素坐标(例如 \(1920 \times 1080\))。 \[ x_{screen} = (x_{ndc} + 1) \times 0.5 \times width \] \[ y_{screen} = (y_{ndc} + 1) \times 0.5 \times height \]
4. 代码示例 (Vertex Shader)
在实际的 Shader 中,这一切通常浓缩为一行代码。
#version 330 core
layout (location = 0) in vec3 aPos; // 局部空间坐标
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
// 注意矩阵乘法顺序:从右向左应用
// Local -> World -> View -> Clip
gl_Position = projection * view * model * vec4(aPos, 1.0);
}总结
- MVP 矩阵是将顶点从局部空间转换到裁剪空间的组合矩阵。
- 齐次坐标中的 \(w\) 分量是实现透视效果的关键。
- 理解这个流程对于编写 Shader 至关重要。