您的位置:首页 > 产品设计 > UI/UE

【翻(xue)译(xi)】3D Game Programming With DirectX11 - 5.6

2016-02-19 04:00 459 查看
这一节不懂的词比较多。。所以翻译存疑在每小小节之后 - -。而且出现了一些数学公式我当时直接截图的。。。以后有空再把这些图补上 - -。。。

5.6 顶点着色器阶段

当图元被聚合好了之后,顶点就被喂给顶点着色器了。顶点着色器可以看做是输入一个顶点输出一个顶点的函数。每个顶点都从顶点着色器通过,实际上,我们可以理论上想下边这个函数在硬件中工作:

for(UINT i = 0; i < numVertice s ; ++i)

outp utVerte x[i] = VertexShader(inputVerte x[i]);

顶点着色器是我们实现的但是是GPU来实现的,所以非常快。顶点着色器里边可以做许多事情,比如变形、光照、移位映射。记住我们不仅可以在GPU里边访问顶点缓存,还可以访问到GPU存储的其他纹理啊转换矩阵啊场景光照啊之类的数据。

本书我们将会见到许多顶点着色器的例子,最后你们对它们有个很好的赶脚。不过我们只会用它来转换顶点。下边这一小节展示了通常要做的操作。

5.6.1 局部空间和世界空间

假设你正在做一个电影,需要做一个给一个火车场景做一个小的模型缩影,尤其是,假设你正在做一个小的桥。现在,你不会在场景中创建这个桥,你会从一个角度来做这个东西,小心不让它跟缩影中的其他东西冲突。你的工作会跟这个场景独立,做好之后你再把这个桥放在场景中。

3D艺术家在做3D物件的时候也是做得类似的事情。他们不是把物体的几何信息画到一个全局的坐标系(世界坐标)中,而是把它们画到局部坐标系中。局部坐标系通常是离这个物体近的比较方便的一个坐标系,也和世界坐标系的轴对应。当3D模型的顶点在局部坐标系中定义好了之后,它会被放到世界坐标系中。为了达到这一目标,我们必须定义局部坐标系和世界坐标系是如何联系到一块的。实际上当我们定义了局部坐标系在世界坐标系中的位置以及坐标轴的方向之后,我们就可以通过坐标变换来完成这个目的。从局部坐标系变换到世界坐标系的操作叫做世界变换,变换矩阵叫做世界矩阵。场景中每个物体都有它的世界矩阵。当每个物体都被从它的拒不坐标转换到世界坐标之后,所有物体就都处于同一个坐标系了。如果你想要直接定义一个物体,你可以把世界矩阵设置为单位矩阵。

把每个模型定义到它的拒不坐标有以下好处:

1. 更简单。比如,通常一个局部坐标中的物体会被置于正中心,按照某个轴有对称性。又比如,如果我们选择一个局部坐标系,我们将会很容易的定义一个正方体的顶点——我们把正方体中心置于原点,然后把它的面平行于坐标轴。

2. 物体可能会被多个场景使用,因此没有必要用hardcode来把物体的坐标定义到一个特定的场景中。相反的,最好是把它的坐标结构定义到一个局部坐标系中,然后通过一个变换矩阵来定义它和每个场景的联系。

3. 最后,有时候我们需要在同一个场景中定义多次这个物体。不过在不同的位置、方向和大小时重复定义它的信息到一个场景就很费了。相反的,每次定义一个世界矩阵到新的位置、方向、大小。这就叫做“实例化”(instancing)。

如第三章所述,世界矩阵是用来描述物体如何从它的局部坐标系转换到世界坐标系的,通过把局部坐标系的轴的坐标作为矩阵的行来得到。

比如这个变换由 Qw = (Qx, Qy, Qz, 1),uw = (ux, uy, uz, 0), vw = (vx, vy, vz, 0), ww = (wx, wy, wz, 0)来描述,其中x,y,z分别表示世界坐标系中局部坐标系的三个轴的坐标。那么我们可以得到局部坐标到世界坐标的矩阵为:

可以看出,在计算世界矩阵的时候,我们必须首先计算局部坐标系的原点和坐标轴在世界坐标系中的坐标。这有时候不是那么直观简单的。一个更普遍的方法是定义W为一系列的变换 W = SRT,放大矩阵S、旋转矩阵R、平移矩阵T的乘积。这三个矩阵SRT可以根据坐标系变换而得到,而W = SRT的行向量就存储了齐次坐标系的x,y,z轴和原点在世界坐标系中的坐标。

(一个例子:略)

翻译存疑:

world matrix: 世界矩阵

instancing: 实例化

5.6.2 观察坐标系

为了给场景创建一个2D的观察图,我们必须在场景里放一个虚拟的摄像机。摄像机决定观众能看到世界中的哪部分,我们从而用这部分的物体来生成2D图像。我们给这个摄像机添加一个局部坐标系(观察坐标系),也就是说,坐标系坐在自己的原点,向z轴看,右胳膊向着x轴,头向着y轴。不同于在世界坐标系中描述我们场景中的顶点,在观察坐标系中描述顶点可以让渲染管线稍后的阶段变得方便。从世界坐标系变换到观察坐标系叫做观察变换,相应的变换矩阵叫做观察矩阵。

(观察空间到世界空间的W的求法重述了一遍,见上文)

不过,这个不是我们要的。我们想要把这个变换翻转成从世界空间到观察空间的。回忆一下前边讲过的,W的逆矩阵就是世界空间到观察空间的变换。

世界坐标系和观察坐标系在位置和旋转上不同。所以,直觉上看把W分解成RT是合理的。这个变换可以让计算机在计算逆矩阵的时候更加简单。

(略)

结果是:

我们现在再展示一种更直接的创建观察矩阵的方式。记Q为相机位置,T为摄像机朝向的方向。进一步的,让j表示摄像机空间向上的轴(y轴)在世界空间的坐标。这只是我们的习惯,有些书可能会让xy平面作为地面而z作为向上的方向。于是现在摄像机的朝向可以由 w = normal( T-Q )来表达。

这个向量描述了摄像机的z轴。摄像机向右的方向w可以由 u = normal( j × w)来得到。这个是摄像机的x轴。最后,y轴为 v = w ×u。因为w和u都是单位向量,所以v也是单位向量,不需再归一化。

所以,给了摄像机位置、指向的点、向上的方向,我们就可以得到摄像机的局部坐标系,也就可以得到观察矩阵。

XNA Math库提供了如下的函数来根据如上所述的三个变量计算观察矩阵。

XMMATRIX XMMatrixLook AtLH(

FXMVECTOR EyePosition,

FXMVECTOR FocusPosition,

FXMVECTOR UpDirection);

通常世界的y轴总是向上的,也就是第三个参数总是 j = (0, 1, 0),比如,我们想要摄像机在(5, 3, -10),让摄像机看着(0, 0, 0)。

(例子略)

翻译存疑:

view transform: 观察变换

5.6.3 投射和齐次裁剪空间

现在我们已经描述了摄像机在世界控件中的位置和方向。不过摄像机还有一个重要的属性,就是摄像机看到的空间中的部分。这个部分是用一个平截头体来描述的。

我们的下一个目标就是把3D几何中的物体投射到2D投射窗中。这个投射过程必须满足我们之前讨论的3D影像的几个现象:平行线重合于无穷远点;近大远小。一个投射投影可以做这些。我们把从眼睛到顶点的线叫做顶点的投影线。然后我们定义把3D顶点v投射到沿着投射线到2D投影面的投射变换,我们把v’叫做v的投影。一个3D物体的投射的意味着组成这个3D物体的所有顶点的投射。

5.6.3.1 定义一个平截头体

我们可以在观察空间定义一个平截头体,把它的中心放在原点,朝向z轴方向,要使用到以下四个量:近平面n,远平面f,垂直夹角a,观测的比例r。注意到在观察空间中,近远平面和xy平面都是平行的,所以我们简单地定义它们到z轴的距离。观察比例用宽高比来定义。投射窗是场景2D图像的基础。这里的图像最终会被绘制到后台缓存。所以,我们希望投射窗的规格和后台缓存的规格一样。所以后台缓存的宽高比通常被设置为投射窗的宽高比。比如,如果后台缓存的规格是800*600,那么宽高比r=800/600≈1.333。如果投射窗宽高比和后天缓存不同,那么我们需要一个不归一的拉伸变换,这会导致一定的混乱(圆被拉伸成椭圆)

我们把水平的夹角记为β。注意它由垂直夹角α和宽高比r来决定。注意投射窗实际的分辨率并不是很重要,需要注意的是宽高比。所以我们把高度记为2。(以下是计算过程,略)。

翻译存疑:

projection window: 投射窗

vertex’s line of projection: 顶点的投影线

aspect ratio: 观察比例

5.6.3.2 投射顶点

给定一个点(x, y, z),我们现在来找这个在z=d平面上的投影(x’, y’, d)。使用相似三角形,我们很容易得到计算结果。

注意只有在 x’ 在(-r, r)之间,y’ 在(-1, 1)之间,z在(n, f)之间的时候,这个点才在平截头体内。

5.6.3.3 归一化的设备坐标(NDC)

之前章节的投射点是在观察空间里边计算的。在观察空间,观测窗高为2宽为2r(r为宽高比)。这个过程的问题在于,分辨率和宽高比有关。这意味着我们需要告诉硬件宽高比,因为硬件之后将会使用宽高比和投影窗做些计算。如果我们可以把这个对宽高比的依赖关系去掉就方便多了。解决方法是把x坐标从(-r,r)拉伸到(-1,1):

-r <= x’ <= r

-1 <= x’/r <= 1

这样处理之后,x和y坐标就被叫做归一的设备坐标(NDC)。z轴坐标没有被归一化。

从观察空间到NDC控件的变换可以被看做是一个单位转换。NDC中的一个单位等于观察空间中的r个单位。对NDC来说我们只要稍加变换就可以得到观察矩阵。(略)。

注意在NDC坐标中,投射窗长宽都是2.所以尺寸问题解决了,硬件不需要知道宽高比了。不过把投射坐标转换到NDC空间就是我们的责任了。(图形卡假定我们会这么做的。)

翻译存疑:

Normalised device coordinate (NDC): 归一化的设备坐标

unit conversion: 单位转换

5.6.3.4 使用一个矩阵来写投影方程

为了一致性,我们想要把投影变换用矩阵来表达。不过,上边得到的那个方程不是线性的,没有矩阵表示。我们来做一个“小动作”:一个线性的部分和一个非线性的部分。非线性的部分是用z除,接下来会讲到,我们将会把z坐标归一化,这个意思是我们将不会使用原始的z坐标来除。所以,我们需要在变换之前把输入的z坐标记下来,为了做这件事,我们使用齐次坐标的特性,把输入的z坐标复制到输出的w坐标上。考虑到矩阵乘法,我们把矩阵的[2][3]=1,[3][3]=0。我们的矩阵是这样的:

注意到我们放了两个常量A和B在里边。这两个常量将被用于把z坐标变换到归一化的范围。

(哥受不鸟了哥要开始截图了)

矩阵乘法之后(线性的部分),我们再除以z,这是非线性的部分:

你可能会好奇是不是会有除0的情况。然而,近平面应该大于0,所以不会有这样一个点。被w除有时候被叫做透视除法或者齐次除法。可以看出投影之后的x和y也满足前几节所述的等式。

翻译存疑:

perspective divide: 透视除法

homogeneous divide: 齐次除法

5.6.3.5 归一化的深度值

似乎当投影之后,我们就可以抛弃原始的3D坐标的z分量了,因为所有点都在2D投影窗里了。不过,我们依然需要3D深度值来给深度缓存程序用。就像Direct3D希望x和y投影分量是一个归一化的范围,它也需要深度坐标是在[0, 1]范围的。所以我们需要定义一个加工函数g(z),把z从[n, f]投影到[0, 1]。因为这个投影是正相关的,所以虽然深度值被转换过,它们之间的关系依然不变,所以我们还是可以按照这个值来比较深度大小。深度程序中我们也就这点需求。

把[n, f]转换到[0, 1]可以用一个伸缩和一个平移来做。然而,我们并不会把这个操作放到我们现在的投影算法中。我们从上一节的那个矩阵变换的式子中看到z轴产生的变化:

我们现在可以来确定A和B的值了。结果如下图(过程略)。

如下图所展示的,这个函数是严格递增、非线性的。它也显示了大多数的范围都集中在近平面的附近。因而,深度值的主要部分都被映射到了一个小的范围中。这可以导致深度值的精确度问题(电脑不能分辨只有很近的差值的深度值,因为有有限的数值精度)。通常的建议是把近平面和远平面设置的尽可能近,从而减轻深度值的精度问题。

在投影变换之后,投影除法之前,这个几何被称之为齐次剪切空间,或者叫做投影控件。在投影除法之后,这个几何被称作是归一化了的设备坐标。

翻译存疑:

homogeneous clip space: 齐次剪切空间

normalized device coordinates: NDC 归一化了的设备坐标

5.6.3.6 XMMatrixPerspectiveFovLH

一个投影矩阵可以用如下XNA数学函数来创建:

XMMATRIX XMMatrixPerspectiveFovLH(

FLOAT FovAngleY,

FLOAT AspectRatio,

FLOAT NearZ,

FLOAT FarZ);

如下的代码片段阐述了如何使用这个函数。(略)
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: