今天帮老师判《三维图形程序设计》的试卷,这门课开卷考,用的教材是段菲翻译的DX9的龙书。判卷过程中发现有两道题虽然不难,但是错的比较多:
1、Direct3D中深度缓冲区中值的范围? A.0-1 B.可以任意设置 C.取*面远平面的值
2、Direct3D支持三类光源为点光源、方向光和聚光灯。
第一题
我仔细看了龙书,没有直接答案,但是如果对投影变换内部原理理解比较深入的话,是可以很容易得到这个答案的。龙书的绘制流水线如图1所示,这个比较利于初学者理解,比较专业绘制流水线如图2所示,可以发现图1其实是对图2的Vertex Shader部分进行了细化(图1中的第一行所有阶段相当于图2的Vertex Shader阶段),而对其他部分进行了简化,个人认为图1的流水线比较适合初学者理解。这里以图2进行说明,Triangle Traversal阶段遍历每个三角形,对三个顶点进行插值,计算三角形中对应光栅设备上的像素点,每个像素点的Z值也是在这个阶段得到,当然,在Pixel Shader中还可以对其进行修改。之后的阶段在这里就不详述了。
图1 龙书中的DX9绘制流水线
图2 DX9绘制流水线
回到题目,深度缓冲区值的范围?首先必须确定深度缓冲区的Z值来源于哪个阶段,因为经过不同阶段的坐标变换以后Z值范围是不一样的!这里以图1进行说明,很多人选择了B,我猜测他们是认为深度缓冲区存储的是View Space阶段顶点的Z值,但是很遗憾,深度缓冲区存储的是Projection之后的Z值,所以应该选A。
看到这里,有童鞋肯定纳闷,为什么投影之后Z的范围是0-1呢?投影不是往一个平面上投影吗?最后Z值应该一样的才对啊!这个就要从投影矩阵说起了,虽然投影到一个平面的投影矩阵很简单,但是这个过程是不可逆的。值得注意的是,DX中的所有变换都是可逆的,比方说对顶点P使用矩阵M进行变换,变换后只要乘以M-1即可变回P。为此,聪明的开发人员使用可逆的投影矩阵,具体解释参看MSDN。投影变换后,Z的范围就变为了[0, 1],0表示近裁剪面,1表示远裁剪面,在OpenGL中由于投影矩阵不一样,Z值范围为[-1,1]。
顺便多嘴提一句,深度缓冲区[0, 1]的范围并不是用浮点数表示,而是用类似整型的方式表示,这样既提高了精度,也为之后的Buffer Compression的相关算法买下伏笔。
附:左手坐标系的投影矩阵,具体说明见MSDN
xScale 0 0 0 0 yScale 0 0 0 0 zf/(zf-zn) 1 0 0 -zn*zf/(zf-zn) 0
如果书上有直接答案,明确说明深度缓冲区的范围是[0, 1],估计大家基本上都会回答对,但是深入思考其中原理的又会有多少人呢?不仅知其然,而且还要知其所以然,这才是学习的态度。
第二题
这一题,在书上有,但只是和题目描述略有不同,有些不太理解的童鞋就写成了环境光、漫射光和镜面光。像上一题一样,如果只是知道表面,确实挺模糊的,同样是3个空,同样是和光源相关,凭什么是点光源、方向光和聚光灯而不是环境光、漫射光和镜面光呢?确切的说,前者是光源类型,后者是光照模型,下面将对其进行详细描述。
从物理的角度来说,光与材质的交互包括两个方面:散射(Scattering)和吸收(Absorption)。散射发生在介质不连续的地方,比如不同材质的交界处,或介质密度变化,它只改变光的方向不改变光的能量;吸收发生在材质内部,它将光的能量转化为其他能量(如热能),但不改变光的方向。介质表面对光的散射包含两种方式,分别为折射和反射。对于透明物体,一部分光在表面进行反射,另一部分光发生折射,进入介质继续传播;对于不透明物体,一部分光在表面进行反射,另一部分光在介质中传播、吸收和散射,最后反射回表面(次表面散射),图3为不透明物体与光作用的示意图。
图3 光与材质作用
DirectX光照模型中的漫射光和镜面光分别对应材质表面的次表面散射和表面反射,这两个模型共同模拟了光与材质作用时的散射现象,至于环境光,用来描述间接光照,这就是DirectX光照模型的来历,它们所对应的公式从提出以后一直被不断修正,趋于完善(直到2008年还有一篇文章对镜面光公式进行修正)。DirectX的材质描述了材质对于漫射光、镜面光和环境光的反射,这些对应物理上材质对光的吸收。另外,在材质中还有自发光分量,用于描述自发光的材质。
到此为止,主要的光照现象已经被模拟了,如果需要其他更逼真的渲染,则需要对可编程管线下手了。例如透明玻璃球的折射,如果仅仅使用alpha融合进行渲染,那就太扯淡了,需要利用相关的物理知识,在可编程管线中模拟真实环境,并进行一定的简化;再比如说人脸的渲染,由于皮肤的次表面散射现象比较明显,所以使用漫射光来进行渲染就会让人觉得很假,需要针对次表面散射的物理现象,结合可编程管线进行逼真的渲染,《GPU GEMS 3》封面的人脸就是使用次表面散射相关技术进行渲染,有兴趣的童鞋可以看看这本书的第14篇文章。
既然有光,肯定有光源,点光源、聚光灯和方向光正是由现实中的光源抽象而来。对于太阳这种比较大的光源,可以近似认为方向光;对于一些表面积不大的面光源,可以近似用点光源来模拟;至于聚光灯也是根据现实中的聚光灯进行模拟。虽然用这3种光源不能精确的描述现实中的光源,但是可以做到近似。
回到题目,题目问的是光源类型而不是光照模型,如果理解了这些原理,那么自然想到的是点光源、聚光灯和方向光,无论如何也不可能写成漫射光和镜面光和环境光。虽然DX提供了相关API,但如果能够对其原理进行了解,则使用起来将会得心应手,而且,从DX10开始,取消了固定管线,只有可编程管线,所以理解原理显得更加重要。
写在最后
本文的目的不是希望大家去背题目,以后看到同样的题目就不会出错,而是希望能够理解内部原理,做到不仅知其然,而且还要知其所以然。社会是浮躁的,在浮躁的环境中需要保持一颗平静的内心,也许原理性的东西短期内不会带来什么效益,但是保持这样的态度,很可能若干年后和别人拉开的差距,比多学几门热门技术要大的多。