坐标空间总览
需要预先学习:
1. 一个顶点的旅程
前面几篇笔记分别讲了变换(变换)、视图与投影(视图(摄像机)与投影)、光栅化(光栅化),每一篇都工作在某一个特定的坐标空间里。这篇笔记把它们串成一条完整的链:一个顶点从模型文件出发,到成为屏幕上的像素,中间经过哪些空间、每一步由哪个矩阵驱动、 分量经历了什么。
| 空间 | 又称 | 变换矩阵 | 关键操作 |
|---|---|---|---|
| 模型空间 | 局部空间(local space) | —(起点) | 顶点属性定义于此 |
| 世界空间 | world space | 模型矩阵 | 摆放物体、光源与摄像机 |
| 视图空间 | 摄像机空间 / 眼睛空间(eye space) | 视图矩阵 | 摄像机移到原点、看向 |
| 裁剪空间 | clip space | 投影矩阵 | 可视区域规范化, 携带深度 |
| NDC | 标准化设备坐标 | 透视除法(除以 ) | 坐标落入 |
| 屏幕空间 | screen space | 视口变换 | 映射到像素网格,进入光栅化 |
整个链路就是着色器里那句熟悉的乘法加两步硬件操作:
gl_Position = P * V * M * vec4(aPos, 1.0); // 顶点着色器输出:裁剪空间坐标
↓ 硬件透视除法(xyz / w) // NDC
↓ 硬件视口变换 // 屏幕空间
注:矩阵乘法满足结合律(见 变换),CPU 侧常把 预乘成一个矩阵再传入着色器;需要世界空间坐标(光照)时, 与 分开传。离顶点最近的矩阵最先作用: 表示先模型、再视图、再投影。
2. 各空间的定义
2.1 模型空间
建模软件里顶点坐标所在的空间,每个模型自带一个:原点通常在模型中心或脚底,轴向由建模习惯决定。它只在"这个模型自己"的语境下有意义,模型文件里存的数字就是模型空间坐标。
2.2 世界空间
左乘模型矩阵 (平移、旋转、缩放的组合,见 变换)后,顶点进入所有物体共享的世界空间。光源位置、摄像机位置、物体的世界变换都在这个世界里描述——场景搭建完成的空间。光照计算(Phong模型与Blinn-Phong模型、PBR)通常在这里进行,因为光源、视线、表面法线都需要在同一个空间里点积。
模型矩阵 同时也是场景图(scene graph)父子变换的载体:父物体移动带动子物体,子物体的世界矩阵 = 父矩阵 × 子矩阵。
2.3 视图空间
左乘视图矩阵 后,世界被变换到"以摄像机为中心"的空间。约定(右手系,如 OpenGL):
- 摄像机位于原点;
- 看向 方向;
- 上方向为 ,右方向为 。
的构造是"把摄像机搬到标准位置"的逆过程:先平移 (摄像机位置取反),再用正交旋转把朝向对齐坐标轴(推导见 视图(摄像机)与投影)。
注:视图空间的 是负值递增的——物体越远, 越小(、、)。很多推导(如 CSM级联阴影 的视图深度)需要取 作为"正的深度",写代码时最容易在这里丢一个负号。
2.4 裁剪空间
左乘投影矩阵 后,顶点进入裁剪空间,这是顶点着色器输出 gl_Position 所在的空间。它的两个特征:
- 裁剪的舞台:图形管线在这个空间里做裁剪(clipping)——完全落在可视范围外的图元丢弃,跨越边界的图元切出新顶点。裁剪范围由 、、 描述(注意是与 比较,不是与 1 比较);
- 分量开始有意义:
- 正交投影:,各分量本身就是线性映射的结果;
- 透视投影:(视图空间深度的相反数),近大远小的全部秘密都存在这个 里。
透视投影矩阵不是真的"投影"(没有降维),它做的是把视锥体扭曲成长方体,同时让 记下"离摄像机多远",等下一 步用。
2.5 NDC(标准化设备坐标)
裁剪空间坐标除以自身的 ,这一步称为透视除法(perspective division):
除法由硬件在顶点着色器之后自动执行(等价于 GLSL 的 gl_Position.xyz / gl_Position.w)。除完之后坐标落入标准立方体 ——与分辨率、宽高比、视锥形状全部无关的规范形式。正交投影 ,除法是空操作,这就是"正交没有近大远小"的数学表述。
注:透视除法是"除以 "这一步把远处的 、 压小的——视锥体里远处物体 、 大,但 同样大且成正比,除完远处物体的 NDC 坐标聚拢到中心,正是近大远小。 的除法同时带来非线性深度:NDC 的 在近处精度高、远处精度低,这是 深度缓冲 z-fighting 与 reverse-z 的根源。
2.6 屏幕空间
视口变换把 NDC 的 、 线性映射到像素网格(推导见 光栅化):
通常被映射到 写入深度缓冲。到此为止几何处理结束,接下来的故事属于 光栅化:三角形覆盖哪些像素、属性如何插值(见 透视校正插值)、片元如何通过深度测试(见 深度缓冲)。
3. 各图形 API 的差异
坐标空间的定义不是唯一的,不同 API 与不同硬件在两处约定上存在分歧,跨平台开发(引擎的 RHI 层)必须显式处理。
3.1 z 的范围
| API | NDC 的 z 范围 | 屏幕深度范围 |
|---|---|---|
| OpenGL | 映射到 | |
| Direct3D / Vulkan / Metal |
OpenGL 把 先映射到 (投影矩阵里乘 2 减 1),硬件再映射回 存深度,一来一回多了一步;D3D 系直接用 。
3.2 左手系与右手系
| API | 约定 | 摄像机看向 |
|---|---|---|
| OpenGL | 右手系 | |
| Direct3D | 常用左手系 | |
| Vulkan / Metal | 右手系裁剪,但 y 翻转等细节各有约定 | 视图空间自定,裁剪空间规范固定 |
左右手的差异直接影响三角形环绕顺序(winding order,见 剔除总结 的背面剔除)与叉积方向——同一份顶点数据在两个 API 下正面朝向相反,镜像缩放翻转环绕的经典 bug(剔除总结)就是它的低配版。
3.3 y 的方向
屏幕像素的 轴朝向也不统一:OpenGL 窗口坐标原点在左下角( 向上),Direct3D 与大多数图像文件原点在左上角( 向下)。纹理 里 stb_image 需要垂直翻转加载,根源正是这里。
注意:跨 API 移植时,这三处差异(z 范围、手性、y 方向)要集中在一处处理——通常是投影矩阵的构造函数。引擎里 RHI 层按目标 API 生成不同的"深度修正矩阵",上层代码完全无感,这是图形抽象层的价值之一。
4. 法线的特殊旅程
上面的链路是位置的旅程,法线不能直接乘模型矩阵(推导与原因见 Phong模型与Blinn-Phong模型 的正规矩阵一节):非等比缩放会破坏垂直性,法线必须左乘模型矩阵左上 的逆转置矩阵(normal matrix):
旋转与等比缩放下逆转置可以化简(正交矩阵的逆是转置,等比缩放只影响长度),所以"没有非等比缩放时直接 mat3(model) 也能用"——但正确性只有在逆转置下才严格成立。变换后必须重新归一化:插值会破坏单位长度(见 透视校正插值)。
5. 小结
一张表收拢全部要点:
| 步骤 | 执行者 | 输入 → 输出 | 的变化 |
|---|---|---|---|
| 模型变换 | 顶点着色器 | 模型 → 世界 | 恒为 1 |
| 视图变换 | 顶点着色器 | 世界 → 视图 | 恒为 1 |
| 投影变换 | 顶点着色器 | 视图 → 裁剪 | 透视:;正交: |
| 裁剪 | 固定功能阶段 | 裁剪空间内剔除/切割图元 | 不变 |
| 透视除法 | 固定功能阶段 | 裁剪 → NDC | 变为 1 |
| 视口变换 | 固定功能阶段 | NDC → 屏幕 | 不再参与 |
| 属性插值 | 光栅化 | 顶点属性 → 片元属性 | 需要透视校正(见 透视校正插值) |
两个贯穿始终的主线:
- 规范化的动机:把各种形状的可视区域统一到 ,让裁剪、光栅化等后续阶段不必关心视锥长什么样——与 视图(摄像机)与投影 的标准立方体、纹理映射 的 UV 是同一种设计哲学;
- 的生命周期:齐次坐标引入 是为了统一平移(变换),透视投影赋予它深度的职责,透视除法兑现它——理解 在每一步的值,是排查坐标变换 bug 的第一手段:把中间坐标打印出来,先检查 。


