【技术美术】渲染空间变换概述
【技术美术】渲染空间变换概述
GPU渲染管线中,我们只要知道如何在屏幕上画画就行,在一个2D坐标系上表示2D物体是很简单的。但在引擎渲染管线中,为了在屏幕上显示一个仿真的游戏世界,该任务就变成了如何将3D画面,映射到2D屏幕中。
为此人们发明了一套空间变换流程,利用多个矩阵来实现3D到2D的变换。(提示:矩阵=空间=变换,不同的术语只是从不同的角度理解而已)
渲染空间

渲染管线中的空间变换共经历以下几个空间:
- 物体空间(模型空间):模型文件的坐标系,以每个模型的自身为准。
- 世界空间:场景中的坐标系,所有物体都需要放在该空间。
- 视图空间(观察空间):以相机视角为准的坐标系。
- 裁剪空间(DNC 标准设备坐标系):图形接口绘图所用的坐标系。
其中“物体空间”、“世界空间”、“视图空间”是引擎开发者自己定义的空间,这些空间通常都是由 TRS 矩阵表示的,其矩阵参数跟随物体的变换状态而改变。
“裁剪空间”则是确实存在于图形接口的一个固定范围的方形空间,用于对应到实际的输出附件(如自定义附件或屏幕使用的附件)。在不同的图形接口中,以及根据接口使用者的设置,其空间参数各种各样。
物体空间
在游戏引擎中,顶点不再是屏幕空间,而是基于物体空间的,物体空间就是以物体自身为世界原点的坐标系。因为游戏中,大部分物体是复用的,或需要实时变换的,所以建模师都是以物体为单位来建模,自然的这样建出来的模型顶点,都是以模型的原点为基点。
物体空间是基于TRS矩阵实现的,利用物体的位置、旋转、缩放即可制作一个TRS矩阵:
世界空间
由于模型本身的顶点是不考虑其他物体的,但物体间的交互、相对位置是存在的,因此需将其统一到一个新的空间坐标系,即“世界空间”,这样子才能批量统一的处理网格数据,并使不同物体间的数据可以互通。
世界空间对应没有矩阵实现,因为世界空间是最根源的基空间,物体空间的基向量也是基于世界空间的,所以硬要说的话,世界空间等于单位矩阵。
视图空间
游戏是仿真的,有了被观察的物体,自然需要观察他们的眼睛,而视图空间就是以眼睛为坐标系的空间。因为最终的屏幕画面是相对眼睛的,所以需要将所有物体先转到视图空间,然后才能方便的转换到裁剪空间。
就视图空间本身来讲,其实不过是相机的物体空间的花哨说法,所以其就是一个物体空间,只是拿的是相机的位置、旋转、缩放。但要注意的是,Unity 的视图空间是基于 OpenGL 的,和默认的左手坐标系相比,其 Z 轴是翻转的!
剪辑空间(NDC空间)
剪辑空间和NDC空间本身是同一种空间,只是其空间下坐标的表示方式不同,前者表示坐标是基于齐次坐标,后者则是齐次坐标归一化的结果。齐次坐标主要是为了兼容深度测试、以及TRS矩阵,使顶点可以很方便的实现位移、深度除法等计算。
NDC空间就是GPU渲染管线中讲到的,存在于GPU中的“2D”空间坐标系(当然他实际上是3D的,有一个额外的z轴来实现深度测试的功能),它相当于是最终的屏幕空间坐标系。NDC空间根据图形引擎的不同、用户设置的不同,其设定也不一样。
以屏幕中心为参考坐标系,常见的一些 NDC 空间定义如下:
| 图形接口 | X 轴 | Y 轴 | Z 轴 | z 与深度关系 |
|---|---|---|---|---|
| 默认 D3D | 向右 (-1,1) | 向上 (-1,1) | 向前 (0,1) | 越大越深 |
| 默认 Vulkan | 向右 (-1,1) | 向下 (-1,1) | 向前 (0,1) | 越大越深 |
| Unity OpenGL(Unity 默认) | 向右 (-1,1) | 向上 (-1,1) | 向后 (-1,1) | 越小越深 |
| Unity D3D | 向右 (-1,1) | 向上 (-1,1) | 向前 (1,0) | 越小越深 |
提示:表格内容表示轴向和值域。
在 Unity 中官方默认推荐的形式是“Unity OpenGL”,因此当切换到与其不一致的环境时,需要利用宏进行特殊处理,具体见后文。
渲染空间变换
从一个空间变换到下一个空间,都是由矩阵实现的,具体实现方式如下:
- 物体到世界:就是物体空间矩阵本身。
- 世界到视图:就是相机的物体空间矩阵的逆矩阵。
- 视图到裁剪(投影变换):分正交和透视两种变换方案,具体见投影变换章节。
物体到世界
物体到世界变换矩阵等价于物体空间矩阵,因此直接用即可。
世界到视图
视图空间相当于把所有世界中的物体重新以相机的坐标系来表示。如果把相机也看成物体,那该矩阵恰巧是“物体到世界”的反向操作,所以只要把矩阵求逆,另外再加上z轴翻转即可。
视图到剪辑
NDC空间兼容性
前文说到 NDC 空间变体很多,这在 Unity 中也是一样,因此在不同的图像接口环境下,会出现一些特殊情况:
z轴翻转
在D3D平台,z轴是01翻转的,即从近到远为 (1,0)。越近的z越大,越远的z越小,这很反直觉,不过这种翻转的深度可以更好的利用浮点数的分布情况,所以被广泛使用。在Unity中可以使用UNITY_REVERSED_Z来判断是否是翻转z轴的平台。
y轴翻转
在D3D平台,如果在片段着色器使用NDC坐标,会发现y轴是翻转的,为什么?
这和Unity读写纹理的方式有关,因为除了NDC空间,哪怕纹理坐标在不同图形接口下也是不一致的,OpenGL中纹理空间以左下角为零点,而D3D中以左上角为原点。那为什么在Unity中我们一直都是直接用OpenGL的方式读写纹理,却不会导致颠倒?
因为Unity在D3D平台会故意颠倒存储纹理。这种方式同样应用于渲染纹理,正好渲染纹理无论是作为材质输入,还是复制到屏幕,使用时都要进行采样,颠倒存储加颠倒采样,负负得正反而对了。
因此如果你在着色器里半路截胡,就会发现Unity为了颠倒存储而翻转了y轴。对于翻转y轴的平台,同样也可以利用对应的宏 UNITY_UV_STARTS_AT_TOP 来判断。

浙公网安备 33010602011771号