在看《Introduction to 3D Game Programming with DirectX 11》的时候,发现里面固定渲染管线已经被抛弃了,取而代之的是可编程渲染管线,虽然复杂度变高了,但同样的自由度也变大了。之前在学DirectX 9的时候,我只是对其中的一些空间变化概念有一些比较粗糙的理解,通过传递一些简单的参数给函数来构建矩阵,然后将其传递给D3D设备函数去应用这些矩阵变换,就可以实现了从3D场景到屏幕的转换,这看起来非常简单。但是到DirectX 11的时候,没有了这些应用矩阵变换的函数,全部都转移到了顶点着色器来进行。光看顶点着色器的代码话,还并不能明白它是怎么样实现这个转换过程的。为此还需要重新理解,空间变换是怎么通过矩阵来进行的。写这篇博文是为了帮助自己能对空间变换有个较为全面的了解,所以接下来的内容主要都是数学(要有线性代数的基础),即理论知识为主,大部分翻译自书中内容。 左手坐标系 与 单位矩阵在高中的时候通常我们接触到的坐标系为右手坐标系,而在DirectX 中采用的则是左手坐标系: 在该坐标系中,X轴指向右边,Y轴指向正上方,Z轴指向正前方。以该坐标系为基准,对应的矩阵如下: 该矩阵也刚好是一个单位矩阵,其中上面的三个3D列向量 使用下面的矩阵就可以实现坐标点的平移操作: 其中 在使用4D向量前,我们要规定好: ① ② 这样,对于一个坐标点 而对于向量的话,平移矩阵是对它无效的: 旋转矩阵使用下面的矩阵可以实现坐标点/向量的绕X轴、绕Y轴和绕Z轴的逆时针旋转: 例如,对向量 >注意:旋转矩阵具有正交性,即 使用下面的矩阵可以实现坐标点/向量在X轴、Y轴、Z轴的缩放比例: 例如,对向量 我们可以将前面的三个缩放 为了描述一个物体,我们需要定义这些顶点所在的坐标位置。通常我们对物体的描述是在局部坐标系中进行的,又或者是靠近中心的地方,因为我们并不能确定它在世界中的位置。并且我们将物体的中心定义为局部坐标系的原点,这样可以方便我们对这个物体的坐标进行描述,并确保在经过世界变换后能够准确地将物体放置在对应的位置上。 世界坐标系 与 世界变换矩阵局部坐标系可以有很多个,但是世界坐标系只有一个。世界坐标系的矩阵可以看作一个单位矩阵: 为了把处在局部坐标系的物体搬移至世界坐标系中某个特定的位置,以特定的形式出现,我们需要用到世界变换矩阵(一般是缩放、旋转、平移的组合矩阵)来对物体的每个坐标点进行处理。
所以一个世界矩阵的形式可能为 其中, 上述形式是已知局部坐标系在世界坐标系的位置,并进行从局部坐标系到世界坐标系的逆变换,当然你也看成对局部坐标系的物体进行了缩放、旋转、平移的变换,两者是等价的。 观察坐标系 与 观察矩阵为了获取一个2D图像,我们必须引入虚拟摄像机的概念。一个虚拟摄像机可以看作一个观察坐标系,它也是一个局部坐标系,原点为摄像机的位置,Z轴为摄像机的观察方向,Y轴为摄像机的上方向,而X轴则为摄像机的右方向。 若已知 然而,我们想做的并不是这样,而是从世界坐标系转换到观察(局部)坐标系。因此,使用 所以观察变换矩阵的形式如下: 这里的u为摄像机的右方向向量,v为摄像机的上方向向量,w为摄像机的目标视野方向向量,Q为摄像机在世界坐标系的坐标。为了获取一个观察变换矩阵,我们需要提供三个信息:摄影机在世界坐标系的坐标 然后利用 w和j进行叉乘并标准化,得到摄影机右方向向量u: 最后利用 w和u进行叉乘,得到的才是摄影机上方向向量v: v=w×u 由于 w和u已经都是单位向量了,因此不需要再进行标准化。 当然,也可以使用下面的函数来获取一个投影矩阵: XMMATRIX XMMatrixLookAtLH( // 输出视图变换矩阵V FXMVECTOR EyePosition, // 输入摄影机坐标 FXMVECTOR FocusPosition, // 输入摄影机焦点坐标 FXMVECTOR UpDirection); // 输入摄影机上朝向坐标 现在我们已经来到了摄影机的坐标系所在,但是我们还需要描述摄像机所看到的视野区域,通常它可以被描述为一个截头锥体。 接下来的任务是获取对应的3D的截头锥体并投影到一个2D窗口上。 一个2D窗口通常是一个矩形区域,因此从摄影机发射出的4条射线就可以确定一个四棱锥(其中 中心水平夹角为β,中心垂直夹角α),并确定近平面和远平面(物体最近和最远可见距离构成的平面)将两端截掉即为一个截头锥体。 在投影空间中定义一个投影窗口,然后假设投影空间中进行透视投影,发出的射线到物体某处的位置为v,而在投影窗口留下的点则为v'。 在3D物体的Z坐标将会用于表示为深度属性,深度越大的物体在固定窗口显示得越小,并且还要考虑到离摄像机越近的物体会遮挡后面的物体。 定义一个截头锥体我们可以在观察空间定义一个截头锥体,它的焦点朝向为Z轴,并需要提供四个参数:近平面距离 可以看到这里设投影窗口的高度为2(窗口顶部到中心的高度为1),并且摄影机到投影窗口的距离为 为了用我们已经获得的FOV 现在我们知道了 给定一个坐标点 通过观察,我们可以确定一个点 前面投影窗口的高度为2,宽度为2r,其中r是宽高比。然而,窗口的尺寸取决于宽高比,这意味着我们需要告诉硬件的宽高比,因为硬件在后面还需要做一些涉及投影窗口尺寸(如将其映射到后台缓存)的操作。如果我们能删除对宽高比的依赖,操作会更加方便。 接下来要将x坐标系从区间 这样在NDC坐标系下,1个NDC单位 等价于 r个观察坐标系的单位( 由于 标准化后的z满足线性方程 然后我们用坐标点 再对向量除以 尽管在投影之后,我们可以放弃原来的3D Z坐标,但是我们还需要深度缓冲算法中的深度(Z)信息来判断哪一个物体在前面从而被渲染。Direct3D想要让深度坐标 标准化为区间 通过解这两个方程构成的方程组,可以求得: 故最后的方程 这样我们就可以得到最终的投影矩阵: 通过下面的函数我们也能够获得一个投影矩阵: XMMATRIX XMMatrixPerspectiveFovLH( // 返回投影矩阵 FLOAT FovAngleY, // 中心垂直弧度 FLOAT AspectRatio, // 宽高比 FLOAT NearZ, // 近平面距离 FLOAT FarZ); // 远平面距离 对于一个在局部坐标系的顶点,先通过世界矩阵的变换使其转换到世界坐标系中,然后通过观察矩阵的变换使得我们来到观察坐标系中,再通过投影矩阵变换来决定观察到的区域,经过对截头锥体的标准化使之最终变成一个立方体区域,从而方便DirectX进行绘制。所以我们可以将世界矩阵 因为经过了这三个变换后最终会来到2D屏幕空间,所以如果想要在Direct3D中绘制2D,我们可以在顶点着色器中不提供矩阵,直接提供NDC下的坐标从而来对屏幕进行映射,然后让Z设为0到1的任意值即可(如满屏显示的四个2D顶点坐标为 由于在这里用的时间比较多,还有很多东西没能讲到,有机会的话会再做补充。 |
|