Direct3D渲染管线简介
渲染管线负责执行一系列必要的步骤从而把3D场景转换为可以在显示器上显示的2D图像。在Direct3D中,渲染管线的步骤大致如下:
(1)局部坐标系到世界坐标系
假设我们在制作一款游戏,现在,要求构建一个铁匠铺用来放在游戏场景中。我们不可能在游戏场景(世界坐标系)中构建铁匠铺,因为我们不知道它会被放在哪里,大小如何,以及朝向哪里。所以,我们在局部坐标系中构建铁匠铺,所谓局部坐标系就是以物体本身为中心的坐标系,这样的话,我们就不用考虑铁匠铺在游戏场景中的因素。下图显示了一个立方体在局部坐标系下的描述:
一旦我们在局部坐标系中构建完物体后,我们就把它放在世界坐标系中,为了完成这个步骤,我们需要构建一个世界变换矩阵,关于构建世界变换矩阵,我们可以直接使用D3DX提供的接口直接构建一个世界变换矩阵。在这里,我将介绍另一种构建世界变换矩阵的方法,因为后面介绍视图变换时会用到。
先看图:
假设Frame A是局部坐标系,Frame B是世界坐标系,B为局部坐标系Frame A中的一点,那么如何将B从局部坐标系转换到世界坐标系呢?很简单,假设B在局部坐标系中的坐标点描述为(x, y),同时局部坐标系Frame A的两个坐标轴以及原点在世界坐标系Frame B中描述为u,v以及o,那么由图可知,o + xu + yv即为B在世界坐标系中的描述。转换到3D坐标系是同样的道理,只不过多了个z轴,那么假设z轴在世界坐标系中描述为w,那么将局部坐标系中的某个点(x, y, z)转换到世界坐标系中可以写为o + xu + yv + zw。
由此,我们便构建出了世界变换矩阵,假设局部坐标系的原点以及3个坐标轴在世界坐标系中分别被描述为p,r,u以及f,那么世界变换矩阵可以构建如下:
(2)世界坐标系到视图坐标系
所谓视图坐标系就是摄像机所处的局部坐标系,我们可以把摄像机想象为一个在3D场景中的物体。在视图坐标系中,摄像机处于坐标系中心,同时朝向z轴正方向。因为把任意位置的物体进行投影变换是十分困难和低效的,所以我们把物体变换到视图坐标系后再进行投影变换。为什么要说明上面的那种构建世界变换矩阵的方法呢?因为它以3个坐标轴以及原点来构建世界变换矩阵。现在,假设视图坐标系的3个坐标轴以及原点在世界坐标系中描述为r,u,f以及p,那么我们知道将视图坐标系的物体转换到世界坐标系下的世界变换矩阵是,我们只要构建出它的逆矩阵,就可以将世界坐标系中的物体转换到视图坐标系。
由于变换矩阵可以分解成平移矩阵和旋转矩阵的乘积,因此,我们可以构建逆矩阵如下:
(3)投影变换
在3D场景最终被转换为2D图像显示在显示器上之前,还需要经过投影变换,Direct3D期望经过投影变换后的点处于标准视域体内(x,y的范围是[-1, 1],z的范围是[0, 1])。
为了进行投影变换,我们需要定义平头截体和横纵比。平截头体类似于被削平的金字塔,为此我们需要定义一个近平面n,一个远平原f,以及一个垂直范围的可视角度α。而横纵比通常定义为后台缓冲区的宽/高的值。如图:
那么我们如何根据垂直范围的可视角度α求得水平范围的可视角度β呢?根据三角公式,我们有tan(α/2) = d/n,以及tan(β/2) = c/n,同时由于R(横纵比) = 2c / 2d ⇒ c = Rd。于是,我们可以得到tan(β/2) = c/n = Rd/n = Rtan(α/2)。
定义完数据后,接下来,我们要进行实际的投影变换了,先变换x和y坐标:
根据相似三角形理论,我们有x/x' = z/n ⇒ x' = xn/z,同理y/y' = z/n ⇒ y' = yn/z。由于x'必定位于区间[-c, c]以及y'必定位于区间[-d, d],而Direct3D期望经过投影变换后的点的x,y的范围是[-1, 1],所以我们需要将它们规范化到区间[-1, 1]。
-c ≤ x' ≤ c ⇒ -1 ≤ x'/c ≤ 1
-d ≤ y'< d ⇒ -1 ≤ y'/d ≤ 1
由此我们获得了在区间[-1, 1]的x,y坐标:
接下来,我们变换z坐标,Direct3D期望经过投影变换后的点的z的范围是[0, 1],所以我们需要将它规范化到区间[0, 1]。
由于规范化的x,y坐标中都包含了z坐标,所以我们想要把它们乘以z,这样的话,对于z坐标就会写成如下形式 ⇒ ,这样的话就可以写成矩阵的形式了。
现在问题就转换为如何求u和v,我们知道,近平面n经过投影变换后将转换为0,远平面f经过投影变换后将转换为1,所以,我们有u + v/n = 0,u + v/f= 1,我们就可以求得:
u+(-un)/f =1
uf -un = f
u = f /(f -n)
v = -un = -fn/(f - n)
最后,我们得到了
然后,我们可以写成矩阵形式了:
注意,(x, y, z, 1)经过该变换后尚未在标准视域体内,还需要进行渲染管线中的齐次变换。
(4)视口变换
视口变换负责将在标准视域体内的点转换到后台缓冲区(视口)。通常来说,视口是整个后台缓冲区矩形,但也可以是后台缓冲区的一部分。下图显示定义了4个视口:
定义视口还是很简单的,只需定义视口的起始x,y坐标,视口的宽和高,以及视口的最小以及最大z值。
如何将标准视域体内的点转换到视口呢?假设视口的起始坐标为X,Y,视口的宽和高为Width,Height,最小以及最大z值为MinZ,MaxZ。
我们知道在标准视域体内x,y的区间为[-1, 1],我们需要转换到区间[X, X+Width]以及[Y, Y+Height],如下:
-1 ≤ x' ≤ 1
0 ≤ x'+1 ≤ 2
0 ≤ (x'+1)/2 ≤ 1
0 ≤ Width*(x'+1)/2 ≤ Width
X ≤ Width*(x'+1)/2 + X ≤ X+Width
X ≤ Width*x'/2 + Width/2 + X ≤ X+Width
y坐标的转换相同,只是需要注意由于y轴是垂直向上的,所以y的-1转换到视口后应该为Y+Height,如下:
-1 ≤ y' ≤ 1
-2 ≤ y'-1 ≤ 0
-1 ≤ (y'-1)/2 ≤ 0
-Height ≤ Height*(y'-1)/2 ≤ 0
(乘以-1) 0 ≤ -Height*(y'-1)/2 ≤ Height
Y ≤ -Height*(y'-1)/2 + Y ≤ Y+Height
Y ≤ -Height*y'/2 + Height/2 + Y ≤ Y+Height
在标准视域体内,z的区间为[0, 1],我们需要转换到区间[MinZ, MaxZ],如下:
0 ≤ z' ≤ 1
0 ≤ z'*(MaxZ-MinZ) ≤ MaxZ-MinZ
MinZ ≤ z'*(MaxZ-MinZ) + MinZ ≤ MaxZ
至此,我们构建出了视口变换矩阵: