变换(模型、视图、投影)
在上一章中,我们已经掌握了如何对三维物体进行平移、旋转和缩放。但在真正的 3D 游戏中,模型并不是孤立存在的——它们处在一个庞大的世界中,并且需要通过一个“虚拟摄像机”的镜头,最终投影到你那二维的平面显示器上。
完成这一整套将三维坐标“拍扁”到屏幕上的数学管线,就是图形学中大名鼎鼎的 MVP 变换 (Model, View, Projection)。
1. MVP 管线概览
Section titled “1. MVP 管线概览”当我们在渲染管线中处理一个顶点向量 时,它通常会依次经历以下几个空间(Space)的跃迁:
- 模型空间 (Local/Model Space):模型刚被美术在建模软件中画出来时的原始坐标。
- 世界空间 (World Space):应用了模型矩阵 (Model Matrix, ) 后,模型被摆放到了游戏世界的绝对坐标系中。这部分就是我们在上一章学习的 TRS 组合变换。
- 观察空间 (View Space / Camera Space):应用了视图矩阵 (View Matrix, ) 后,世界坐标系被重新计算,使得摄像机永远位于坐标原点,并看向 轴方向。
- 裁剪空间 (Clip Space):应用了投影矩阵 (Projection Matrix, ) 后,三维场景被挤压进一个标准的规范化立方体中(准备被裁剪并输出到二维屏幕)。
所以,顶点在 Vertex Shader(顶点着色器)中执行的最核心代码永远是:
(注意:矩阵乘法从右向左结合,所以先执行 ,再执行 ,最后执行 。)
2. 视图变换 (View Transformation)
Section titled “2. 视图变换 (View Transformation)”在真实世界中,你想要拍一张照片,你需要移动相机去对准目标。但在计算机图形学中,要让算法变得简单,我们通常不移动相机,而是移动整个世界。
相对运动原理
Section titled “相对运动原理”“把相机向前推 5 米” 和 “把整个世界向后拉 5 米”,在相机的取景器里看起来是完全等价的。这就是视图变换的核心思想。 我们要找出一个视图矩阵 ,将原本散落在世界各地的物体,统统移动到一个新的坐标系中。在这个新坐标系里:
- 相机永远处于绝对原点 。
- 相机永远看着 轴方向。
- 相机的正上方永远是 轴方向。
你可以通过下面的交互组件来直观体验这种“相对运动”:
视图矩阵的推导
Section titled “视图矩阵的推导”要计算 ,我们首先需要知道相机的状态。通常用三个向量系来定义相机:
- 位置 (Eye / Position)
- 观察方向 (Gaze / LookAt direction)
- 向上方向 (Top / Up direction)
我们要做的,就是把这台相机通过变换,移回原点并对齐标准轴。这分为两步:
- 平移 :把相机的坐标 平移回原点。
- 旋转 :把 旋转对齐到 轴, 旋转对齐到 轴, 旋转对齐到 轴。
平移矩阵很容易写出(就是取位置的反方向):
但是计算直接将特定向量对齐到坐标轴的旋转矩阵很难。这里图形学采用了一种方法:逆向思维。 可以写出“把原点坐标轴旋转到相机的 方向”的矩阵 (只需把向量填入矩阵的列中即可)。
而所需的 即为 的逆矩阵。
这意味着,我们连矩阵求逆都不需要算,只需把 按对角线翻转一下,就能得到我们想要的 。最后,将旋转与平移结合,就得到了完整的视图矩阵:
3. 投影变换 (Projection Transformation)
Section titled “3. 投影变换 (Projection Transformation)”经过模型变换和视图变换后,所有的三维物体都已经整整齐齐地摆在相机的镜头前了。接下来的最后一步,就是将这些三维坐标“投影”出来。 投影分为两大类:正交投影 (Orthographic) 和 透视投影 (Perspective)。
正交投影 (Orthographic Projection)
Section titled “正交投影 (Orthographic Projection)”正交投影没有“近大远小”的透视现象,相机的视域是一个标准的长方体 (Cuboid)。这在工程制图、等距视角(Isometric)游戏(如早期的《模拟城市》)中较为常用。
我们通过定义六个面来确定这个长方体视域:
- 左右: (left), (right)
- 上下: (bottom), (top)
- 远近: (far), (near)
正交投影矩阵 的任务是:把这个空间中长方体视域,平移并缩放成一个范围是 的规范立方体 (Canonical View Volume)。一旦进入了这个规范立方体,显卡就能将它们光栅化到屏幕上了。
你可以通过下方的交互组件,亲自拖动滑块,观察一个处于任意位置的 的长方体视域(包含里面的红色球体和绿色圆锥),是如何被“压缩并平移”到位于原点的 规范立方体中的:
透视投影 (Perspective Projection)
Section titled “透视投影 (Perspective Projection)”这才是图形学中的重头戏。人眼和真实世界的照相机都遵循透视投影:近大远小。 透视相机的视域不再是一个长方体,而是一个被切掉尖端的金字塔,我们称之为视锥体 (Frustum)。
图形学界为了计算高效,并没有发明一套全新的体系来处理视锥体,而是采用了一种数学转换:先将视锥体“挤压”成正交长方体,随后直接复用已有的正交投影矩阵。
-
相似三角形映射 X 与 Y 观察上方交互图。假设视锥体内的任意一点为 。根据相似三角形原理,如果要把它“压”到与近平面()相同的尺寸,它的新坐标 和 将会被缩小: (注:因为 和 均为负数,相除后得到一个正的缩放系数。越远的物体其 绝对值越大,对应的缩放系数越小,从而产生透视收缩效果。)
-
利用齐次坐标强行除以 Z 我们需要在矩阵运算中实现“除以 ”的操作,但这在普通的线性乘法中无法直接实现。此时,我们可以借助齐次坐标的特性来完成这一步。 我们规定一个挤压矩阵 ,将第三行的 信息转移到第四维的 上: 当转化为三维坐标(整体除以 ,即除以 )时,前两维便转换为 和 。
-
解密神秘的第三行(求解 Z) 对于挤压前后的 坐标,我们有两条明确的几何约束:
- 近平面()上的任何点,挤压后 坐标不能变。
- 远平面()上的任何中心点,挤压后 坐标不能变。 通过解这两个约束条件构成的二元一次方程组,我们可以推导出矩阵的第三行为 。
-
最终的透视投影矩阵 结合了上述所有推导,完整的透视投影矩阵其实就是:先进行视锥体挤压,再执行标准的正交投影。 首先,我们终于可以写出那个名震图形学界的“视锥体挤压矩阵”了:
将它与之前的正交投影矩阵相乘,我们就得到了终极的透视投影矩阵(通常在代码里直接调用这个结果):
定义视锥体:FOV 与宽高比 (Aspect Ratio)
Section titled “定义视锥体:FOV 与宽高比 (Aspect Ratio)”在实际使用游戏引擎(如 Unity 或 Unreal)时,较少需要手动填写 这四个边界值。大多数时候,主要设置两个参数:
- 视野 (Field of View, FOV):通常指垂直视野角度(
fovY)。这就像换摄像机镜头,长焦镜头的 FOV 小,广角镜头的 FOV 大。 - 宽高比 (Aspect Ratio):屏幕的宽度除以高度,比如常见的 16:9(即
1.777...)。
有了这两个直观的参数,加上给定的近平面距离 ,我们可以通过简单的三角函数瞬间反推出 和 (假设视域是对称的):
有了上下左右的边界,就可以直接套用上面推导的 矩阵了。这正是现代 3D 引擎底层每天都在狂飙计算的代码逻辑。
4. 视口变换 (Viewport Transformation)
Section titled “4. 视口变换 (Viewport Transformation)”当顶点经过了 MVP 矩阵的变换后,它们的坐标均被映射到了一个 均在 范围内的规范立方体中。 但别忘了,你的显示器可不是 这么小的方块,它可能是一块 分辨率的屏幕。
渲染管线的最后临门一脚,就是视口变换。 它的任务是忽略深度的 坐标(后续留给 Z-Buffer 处理),仅仅把 和 组成的 的正方形,贴合拉伸到屏幕物理像素的矩形框内:。
由于这只是一个从中心在原点、边长为 2 的正方形,变换到中心在 、宽高为 的矩形的简单过程,它也是一个基础的缩放加平移矩阵:
经过此步骤,三维坐标被转换为屏幕像素坐标,进入后续的光栅化 (Rasterization) 阶段。