正交投影
前言
在上一讲中,我们学习了如何将三维坐标系转换为其他三维坐标系,特别是世界坐标系变换。世界坐标系变换可以将美术人员使用的模型坐标系中的物体转换到关卡,设计师和游戏逻辑所使用的世界坐标系中。我们还学习了视图变换,它考虑了摄像机的位置和方向。进入视图空间后,我们需要将场景中的物体投影到二维计算机屏幕上。
有几种方法可以实现这一点,虽然有一些很少用但很巧妙的方案,但大多数游戏都会使用正交投影或透视投影。在本讲中,我们将学习正交投影,这是一种比较简单的投影方式,这将为我们在下一讲中讨论透视投影奠定基础。最后一组顶点变换的任务是将三维空间映射到一个归一化的空间,通常称为裁剪空间,剩余的硬件组件负责实际执行光栅化并将三角形转换为两组像素。不同的 API 会使用不同的方法,这里展示的是 Direct3D 使用的方法。
这张图比较难懂,这里有一个坐标轴,即 X 轴和 Y 轴,位于 Z 轴等于 0 的平面上。这个平面也是立方体的一部分所在的平面。所以,它实际上也位于 Z 轴等于零的位置。这很难看清,你的大脑可能会想把它放在立方体的中心,但实际上,这个点位于立方体的表面上。你应该想象你看到的整个立方体都在向背景延伸,而不是朝向你(观察者)。三角形的位置由 X 和 Y 决定。Z 坐标在三角形定位中并不那么重要,它用于确定哪些物体位于其他物体的前面,我们将在之后的几节课中详细讲解。此外,这种深度信息经常被许多后期处理效果使用,例如景深等聚焦效果以及雾效。我
所有关于裁剪空间的约定最终都是左手系统,它们的 Z 值从远离观察者的方向为正。尽管它们在转换方式上有所不同,但 Direct3D 本质上是一个左手系统。微软在其基础上构建的 XNA 框架在其视空间(也可以称为视图空间或摄像机空间)中使用了右手系统。因此,在我们即将讨论的裁剪空间变换中,X 轴的转换是从右手到左手的转换,而 Direct3D 则保持了相同的左手性。如果我们查看 OpenGL 或 Unity 标准,也会发现类似的奇怪现象。左右两边的主要区别在于 Z 轴的处理方式。这两个系统的 X 和 Y 轴值都是从 -1 到 1,但 Z 轴的处理方式不同。正如我在上一张图中提到的,这有点难以想象。左侧平面上发生了什么?因为立方体的这个正面实际上位于 Z 轴零点,所以你应该看到这个点位于立方体的正面。OpenGL 的坐标系是从 -1 到 1,而不是从 0 到 1。因此,你应该把这个点看作位于立方体的中心,因为这里的 Z 轴是 -1,而这里它仍然位于 Z 轴零点。就坐标系而言,Unity 在其摄像机空间中使用左手坐标系,因此在进入裁剪体积时不会切换坐标系。但 OpenGL 的眼空间(也称为摄像机空间或视图空间)本质上是右手坐标系,因此会切换坐标系。在我最初的 3D 坐标讲座中,我花了很多时间讨论不同的程序、不同的游戏引擎和美术工具如何使用不同的系统。这种情况一直持续下去,每个人都使用不同的坐标系。我在这里花这么多时间的原因是,如果你阅读一本书,它通常会选择一种特定的坐标系并使用它。虽然整个流程都遵循某种约定,但它永远不会明确指出。
正交投影
我们假设有一组从场景指向观察者的平行线,然后我们将选择一个空间平面,并将场景中的某些物体投影到这些特定点上。可能存在部分位于场景之外的物体,在这种情况下,通常硬件(如果是软件渲染器)的一部分会获取位于边缘的某些三角形,并进行相交检查。它会获取部分位于场景之外的三角形,并根据需要将它们分割成更小的三角形,以处理位于裁剪空间内的物体部分。下一讲我们将学习透视投影,它考虑到现实生活中物体距离观察者较近时会发生透视形变这一事实。你会看起来比远处的物体更大。
假设我们已经完成了世界变换和视图变换,现在所有物体都处于摄像机的范围内。我们可以定义一些空间点来定义我们想要渲染的体积,例如左坐标、右坐标、下坐标、上坐标、近坐标和远坐标。我们想要做的是将这些点映射到一个体积中,例如使用 Drug 3D 风格。
现在让我们考虑一下 Z 方向的情况。如果我们使用 Direct3D 约定,它不映射到 ,而是映射到 。
如果我们将所有这些组合到一个大矩阵中,并且使用像 Direct3D 那样的行约定,我们可以将 X、Y 和 Z 轴的所有乘数放在对角线上,然后最后一行处理平移项。
记住,如果我们使用列约定,则矩阵将位于向量之前,在这种情况下,我们将使用该矩阵的转置。
- LHS is default system in Direct3D & Unity
- In Direct3D:
D3DXMatrixOrthoOffCenterLH(*o, l, r, b, t, n, f) - In Unity:
Matrix4x4.Perspective(l, r, b, t, n, f)
这里数学运算的一个有趣之处在于,无论你使用左手坐标系还是右手坐标系来表示 z 轴,这个矩阵都能正常工作。例如,在左手坐标系中,你可以输入像 54(近)和 104(远)这样的数字;或者在右手坐标系中,你可以输入像 -54(近)和 -104(远)这样的数字,而无需更改任何内容。这导致一个极其令人困惑的问题,如果你查看微软的文档,你会发现他们实际上在这个右手坐标系的调用中交换了 N 和 F 的值,从而生成右手矩阵。XNA 的矩阵类中也内置了一个例程自动生成右手矩阵,它也有同样的翻转。结果发现,即使你使用的是右手坐标系,其中视图(也就是相机空间)中近平面是 -50,远平面是 -100,这些 API 调用仍然要求你的近平面和远平面被列为正数,即使在坐标系中它们实际上是负数。所以它们实际上会为你进行翻转。这就是为什么会有两种不同的调用,左手坐标系的调用分母是 f-n,而右手坐标系的调用分母是 n-f。他们基本上是帮你加了个负号,因为我猜他们认为即使实际是负数,在这里输入负数也会让人困惑。所以你在这里输入正数,它会自动帮你加上。
- In OpenGL:
glOrtho(l, r, b, t, n, f)(matrix is different) - OpenGL maps z to [-1, 1] & uses column vectors
简化的正交投影,前提是投影体对称于 Z 轴。