坐标空间
Coordinate Spaces
顶点从模型到屏幕要依次经过模型空间、世界空间、观察空间、裁剪空间、标准化设备坐标 (NDC)、窗口坐标。每一步变换都有明确目的,大部分可以用 4×4 矩阵表示并相乘合并。
变换链
| 空间 | 由什么变换得到 | 目的 |
|---|---|---|
| 模型空间 (Model Space) | 原始数据 | 模型自身坐标,未做任何变换 |
| 世界空间 (World Space) | 模型变换 (Model Transform) | 所有模型放进同一个唯一的世界 |
| 观察空间 (View / Camera / Eye Space) | 观察变换 (View Transform) | 相机移到原点,朝向 −z,y 朝上、x 朝右,让投影和裁剪更简单、更快 |
| 裁剪空间 (Clip Space) | 投影变换 (Projection) | 齐次坐标,在透视除法之前;顶点着色器必须输出这种坐标 |
| NDC | 透视除法(除以 w) | 视景体变成标准立方体 (−1,−1,−1) 到 (1,1,1),有的 API 用 0 ≤ z ≤ 1 |
| 窗口坐标 (Window Coordinates) | 屏幕映射 | x、y 平移加缩放到窗口,z 映射到 [0, 1](默认) |
下图是顶点从模型空间到窗口坐标的完整路径,箭头上写的是对应的变换。
flowchart LR
M["模型空间"] -->|"模型变换"| W["世界空间"]
W -->|"观察变换"| V["观察空间"]
V -->|"投影变换"| C["裁剪空间<br/>齐次坐标"]
C -->|"裁剪 + 透视除法"| N[NDC]
N -->|"屏幕映射"| S["窗口坐标"]模型变换和观察变换会作用于顶点和法线。
实例化 (Instancing)
一个模型可以对应多个模型变换,这样同一份几何数据能在场景里出现多次,位置、朝向、大小各不相同,而不用复制几何数据本身。
两种投影
- 正交投影 (Orthographic):视景体是长方体,平行线投影后仍平行,本质是平移加缩放。它是平行投影的一种,另外还有斜投影、轴测投影等。
- 透视投影 (Perspective):视景体是平截头体 (frustum),越远的物体越小,平行线会汇聚到地平线,模拟人眼感知。
两者都把视景体变换成标准立方体,都能写成 4×4 矩阵(RTR4 第 4.7 节)。之所以叫"投影",是因为显示时 z 不存进图像,而是存进 深度缓冲。
为什么先投影再裁剪
先做观察变换和投影,裁剪问题就统一了:永远对着标准立方体裁剪。裁剪时要用齐次坐标的第四个分量 w,因为在透视空间里数值在三角形上并不是线性插值的,需要 w 才能正确裁剪和插值。
API 差异(换引擎或换平台时的常见坑)
- 观察方向约定:RTR4 用 −z 朝前,有些资料用 +z,两者互相转换很简单。
- NDC 的 z 范围:OpenGL 是 [−1, +1],DirectX 是 [0, 1]。
- 像素坐标:像素左边缘是 0.0,中心是 0.5,换算为 d = floor(c)、c = d + 0.5(OpenGL 和 DirectX 10 之后都这样)。
- 原点位置:OpenGL 的 (0, 0) 在图像左下角,DirectX 在左上角(视情况而定),在两个 API 之间移植时一定要注意。
GEA 的视角:坐标空间与换基(卷 I 5.3.11–5.3.12)
GEA 从引擎程序员角度补充了模型/世界/观察空间的命名习惯,以及如何构造和拆解「换基矩阵」。GEA 使用行向量约定(),与上文 RTR4 的列向量写法互为转置。
坐标系就是参考系
刚体可看作无穷多点的集合,变换物体就是变换它的每个顶点。点(位置向量)总是相对某组坐标轴表达,换一组轴,数字就变。物理中称坐标轴为参考系 (frame of reference),游戏业也叫坐标空间 (space)。
模型空间(卷 I 5.3.11.1)
- 也叫物体空间、局部空间。在 Maya、3ds Max 中建模时顶点相对的坐标系;原点通常放在物体中心(如质心)或人形/动物角色两脚之间的地面上。
- 模型轴通常按物体的自然方向命名:front(,物体前进或面向的方向)、up()、left/right( 或 ,取决于引擎用左手系还是右手系)。
- (front, up, left) 到 (x, y, z) 的映射完全任意:右手系常见 ;同样常见 +x 为前、+z 为右();作者也用过 z 竖直的引擎。关键是全引擎统一。
- 欧拉角无法用任意朝向的 定义,但能用 定义:pitch 绕 或 ,yaw 绕 ,roll 绕 (见 欧拉角与万向节锁)。
世界空间(卷 I 5.3.11.2)
- 固定的空间,所有物体的位置、朝向、缩放都在其中表达,把各个物体连成一个世界。
- 原点位置任意,但常放在可玩区域中心附近,以减少坐标很大时的浮点精度损失(见 数值表示与浮点数)。
- 作者见过的引擎多用 y-up 或 z-up:y-up 大概延续了数学课本二维图 y 朝上的习惯;z-up 让俯视正交视图看起来像传统的 xy 平面图。
- 例:Lear 喷气机左翼尖在模型空间为 (5, 0, 0),模型 +z 为前、y 为上。飞机在世界中朝 +x、原点在 (−25, 50, 8),即绕世界 y 轴转了 90°;若在世界原点,翼尖在 (0, 0, −5),平移后为 (−25, 50, 3)。
观察空间(卷 I 5.3.11.3)
也叫相机空间,原点在相机焦点。典型约定是 y 上、z 沿相机朝向增大(左手系),这样 z 直接表示深度;OpenGL 等则定义为右手系,相机朝 −z,z 为负深度。与 RTR4 的分歧:上文 RTR4 默认相机朝 −z 的右手系;GEA 把左手、+z 向前称为「典型」。两书都说任何约定都可以。
坐标空间层级(卷 I 5.3.12.1)
坐标系是相对的:描述一组轴的位置、朝向、缩放,必须相对另一组轴。因此坐标空间构成一棵树,每个空间都有父空间;世界空间是根,没有父空间。
构造换基矩阵(卷 I 5.3.12.2)
子空间 C 到父空间 P 的矩阵 (行向量约定)由子空间的基向量和平移按行组成:
- 是子空间 x/y/z 轴的单位基向量,用父空间坐标表示; 是子坐标系相对父空间的平移。
- 验证:子空间原点 (0,0,0) 变为 ;子空间绕 z 转 时 、,代入正好得到 (见 基本仿射变换)。
- GEA 说上 3×3 由单位向量组成,「因此」是纯旋转矩阵。严格说还需要三个基向量两两正交且构成与父空间相同手性(),即 5.3.2 定义的特殊正交矩阵;仅单位长度不够。
- 子空间缩放:直接缩放基向量,如放大 2 倍则基向量长度为 2。
从矩阵中提取基向量(卷 I 5.3.12.3)
反过来,给定任意仿射 4×4 矩阵,取出相应的行(列向量库则取列)就得到子空间基向量。例:游戏约定 +z 为物体朝向,则载具的模型到世界矩阵的第三行就是它的朝向单位向量,已经归一化(无缩放时),可直接用于点与向量运算里的前后判断。
变换坐标系还是变换向量(卷 I 5.3.12.4)
- 原书此处说第四行 是子坐标轴「相对世界空间轴」的平移,与 5.3.12.2「相对父空间」的定义不一致;应理解为相对父空间(父空间恰为世界空间时两者相同)。
- 把点和方向从子空间变到父空间,等价地也可以看成把父坐标轴变成子坐标轴——方向相反。把点向右移 20 单位等同于把坐标轴向左移 20 单位。
- GEA 选定的「规范」思路:变换作用于向量(而不是坐标轴),向量写成行。两者合用可以从左往右读:,中间的 B、C 像约分一样抵消。若改为想象坐标轴移动,就要从右往左读,或翻转其中一个约定。
- 有些问题按向量变换想更容易,有些按坐标轴移动想更容易,熟练后可按需切换。
GEA 的视角:虚拟相机、观察空间与投影(卷 II 11.3.2、11.3.8–11.3.9)
相机的局部坐标系与视锥
- 相机位置 取在焦点。前向量 只确定俯仰和偏航;还需要上向量 确定滚转;左向量 。三者构成右手坐标系(卷 II 11.3.2.2)。
- 观察体积由穿过成像矩形四边的四个平面、成像面所在的近平面和远处平行的远平面围成,透视投影时是平截头体 (frustum),正交投影时是长方体。远平面是渲染优化,也给深度缓冲中的深度定了上限;光线追踪引擎不太需要它(卷 II 11.3.2.3、11.3.9.1)。
- 六个平面可紧凑地存成六个四元向量 ,或点-法线对 。
模型、世界与光源空间
- 模型空间的轴向随意,但通常对齐模型的前、左(或右)、上方向,例如 、、;关键是全引擎统一(卷 II 11.3.8.1)。
- 网格实例引用共享的网格数据,并带一个模型到世界矩阵(世界矩阵)。按 GEA 的行向量约定 ,矩阵各行依次是模型空间基向量 和平移 在世界空间中的表示(卷 II 11.3.8.2)。
- 法线要乘模型矩阵的逆转置;若矩阵没有缩放和切变,把法线的 w 设为 0 再乘模型矩阵即可。完全静态且唯一的背景网格常直接用世界坐标存顶点,世界矩阵为单位阵。
- 光源空间的构造与观察空间完全相同,常用于生成阴影贴图(卷 II 11.3.8.4,见 阴影贴图)。
观察空间与"相机矩阵要求逆"
- GEA 的映射:、、,平移为 ,得到观察到世界矩阵 (式 11.2)。这样 、 对应图像的 , 就是深度(卷 II 11.3.8.3)。
- 世界到观察矩阵(观察矩阵)是它的逆:。GEA 提醒,相机矩阵相对于场景物体矩阵是"反过来"的,这是新手常见的困惑和 bug 来源。常把它与世界矩阵预先相乘成 OpenGL 所说的模型-观察矩阵 ,顶点只需乘一次矩阵。
与 RTR4 的分歧:观察空间的手性与朝向
| 来源 | 约定 |
|---|---|
| RTR4(本页「API 差异」) | 观察空间相机朝 看(OpenGL 风格),有些资料用 ,互相转换很简单 |
| GEA 卷 II 11.3.8.3 | 全书其他地方用右手系,但观察空间为了让 x 朝右、z 表示深度,把左向量取负,因而是左手系,相机朝 看;OpenGL 也用左手观察空间,但它取反的是 z 轴而不是 x 轴,因为它把 定义为与相机实际朝向相反(作者认为这比取反 x 更让人困惑);本来就是左手系的引擎(如 DirectX)直接用 |
GEA 对 OpenGL 观察空间手性的描述与 RTR4 的" 朝前"说的是同一件事的两种看法:从相机实际朝向看是 ,GEA 则把它表述为对 z 轴取反。
原书问题(11.3.8.3):GEA 说 OpenGL 的观察空间是左手系,这不对。OpenGL 的眼睛坐标(观察空间)是右手系,相机位于原点朝 看,x 朝右、y 朝上;手性翻转发生在投影矩阵中——
glFrustum/gluPerspective把 映射到 NDC 的 ,所以 NDC(及裁剪空间)才是左手系(songho.ca "OpenGL Projection Matrix" / "OpenGL Transformation";OpenGL 2.1 规范 2.11 节中Frustum矩阵的第三行带负号)。OpenGL 也没有定义所谓"与相机实际朝向相反的 ":gluLookAt算出 后把 放在观察矩阵的 z 行,相机仍朝 看。
手性与行列向量的总体分歧见 齐次坐标与矩阵约定。
齐次裁剪空间与投影矩阵
- 透视和正交投影都把观察空间变到齐次裁剪空间,它本质是变形的观察空间,目的是得到与投影类型、屏幕分辨率和宽高比都无关的标准观察体积:x、y 为 ,z 在 OpenGL 中为 、在 DirectX 中为 。观察体积的平面在这里都与坐标轴对齐,所以便于裁剪;裁剪空间 z 朝屏幕里、y 朝上、x 朝右,通常是左手系(卷 II 11.3.9.2)。
- GEA 给出了 OpenGL 和 DirectX 的透视矩阵以及正交矩阵(行向量形式),推导则引用书中参考文献 [36] 的第 4.5.1 节。按其约定,近平面在 、远平面在 ,屏幕四边在近平面上的 、(通常 、,但不一定)。逐项核对:书中 OpenGL 透视矩阵(行向量)第三行为 、第四行 ;DirectX 版第三、四行的 z 分量改为 和 ,代入 、 分别得到 NDC 深度 0 和 1,正确。
- 书中前后不一致:推导投影时 GEA 写"若把观察空间取为右手系,近平面在 ",用的是 OpenGL 式的右手、朝 看的观察空间;而 11.3.8.3 自己定义的观察空间是左手系、相机朝 看()。直接把这里的矩阵用于书中 11.3.8.3 的观察空间会把深度符号弄反;应把矩阵整个第三行(乘 的那一行)取负,这等价于先做 再用原矩阵。
- 原书问题(11.3.9):GEA 说正交投影是平行投影的一种,平行投影"也叫轴测投影 (axonometric projection)"。层级关系弄反了:平行投影包括正交投影和斜投影;轴测投影(等轴、二等角、三等角)是投影方向不与任何坐标轴平行的正交投影的子类,斜投影(如卡瓦列投影)是平行投影但不是轴测投影(Wikipedia "Parallel projection"、"Axonometric projection")。
- 为什么会透视缩短:视空间点乘透视矩阵后得到 ,除以 w 分量就是除以 ,x、y 被深度除,于是远处的物体变小。正交矩阵只是缩放加平移,因为观察体积在两个空间里都是长方体。
- 屏幕空间以像素为单位,原点通常在左上角、y 朝下(因为 CRT 从上往下扫描);把裁剪空间坐标缩放平移到屏幕空间叫屏幕映射。视口是投影的目标矩形,可以小于渲染目标;DirectX 的视口还包含深度范围,因而实际上是三维体积,OpenGL 则把二维矩形和深度范围分开设置(卷 II 11.3.9.3–11.3.9.4)。
PBRT 的视角:渲染空间与远离原点的精度(PBRT 4e 5.1.1、5.2)
PBRT 在世界空间之外定义了物体空间、相机空间、相机-世界空间 (camera-world space) 和渲染空间 (rendering space),投影相机另有屏幕、NDC、光栅三个空间(约定见 投影矩阵 的 PBRT 小节)。
- 问题:浮点数越靠近原点越精确。pbrt 前几版都在世界空间里求交和着色,相机离原点很远时,看到的那部分场景精度不够。书中图 5.1 把场景和相机都平移 1,000,000 个单位后在世界空间渲染,几何上能看到浮点离散化的阶梯。本页验算:float32 在 附近的间距(ULP)为 0.0625,原本 ±10 个单位的场景在每个轴上只剩约 320 个可表示位置(见 数值表示与浮点数)。
- 相机空间渲染(光栅化的传统做法)精度最好,平移会相互抵消;但建筑等场景的主要结构往往与坐标轴对齐,轴对齐包围盒在一个维度上退化、表面积小,BVH 特别高效;相机一旋转,包围盒就变松,书中该场景渲染时间增加 27%。
- 相机-世界空间(pbrt-v4 默认):原点平移到相机位置(取帧中点时刻),但不旋转,兼得精度与紧的包围盒,渲染时间不增加(图 5.1(c) 中视点因相机位置本身的精度损失有轻微偏移)。
CameraTransform把 world-from-camera 分解为 render-from-camera(可以动画)和 world-from-render(不可动画),保证相机运动不会让静态几何也变成"动画几何"而拖慢速度。 与上文 GEA 的建议对照:GEA 主张把世界原点放在可玩区域中心附近以减少精度损失;PBRT 的做法更进一步,渲染时总是以相机为原点,相当于大世界游戏中常见的"浮动原点 / 相机相对渲染"。
出处
- realtime_rendering_4ed.pdf 第 2 章 The Graphics Rendering Pipeline
- Game_Engine_Architecture_4Ed_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 I 第 5 章 5.3.11–5.3.12 节
- Game_Engine_Architecture_4th_Edtion_Volume_2_-_Jason_Gregory.pdf
- Jason Gregory《Game Engine Architecture》第 4 版(2026)卷 II 第 11 章 11.3.2、11.3.8–11.3.9 节
- PBRT 4e source excerpt: chapter 05 (Cameras and Film)
- Pharr, Jakob, Humphreys《Physically Based Rendering: From Theory to Implementation》第 4 版(MIT Press, 2023)第 5 章 5.1.1 节,pbr-book.org/4ed/Cameras_and_Film