【hotball技術小舖】Doom 3的打光系統簡介
(2004-10-21)
在本文中,會對Doom 3的打光系統中,最重要的兩個部份:陰影和凹凸貼圖,做一些簡單的介紹。
|
|
原文链接 http://www.poweruser.com.tw/
阴影的产生
经过漫长的等待,id Software 终于在今年八月正式推出Doom 3。虽然Doom 3在游戏性上,和过去的Doom系列游戏并没有太大的不同,但是Doom 3引擎则是一个完全不同、全新的设计。Doom 3引擎最大的特色,就在于它的打光系统。它结合了完整的阴影和凹凸贴图,并号称使用一致的打光模型,对整个场景中的所有物体一视同仁。它产生的效果相当理想,也为3D引擎又设下了一个新的里程碑。
大家都知道,阴影就是光线没有照射到的地方。换句话说,如果一个地方,和光源之间有东西挡住,那它就会在阴影中。从物理的角度来看,其实是刚好相反的:有光线照射到的地方,就会反射光线。因此,它会比没有被光线照射的地方,要显得更亮。不管用什么角度来看,阴影都不是只和某个物体有关,整个场景都会对阴影产生影响。
因此,要在3D绘图中,产生正确的阴影,就需要考虑到整个场景。这和一般对物体进行打光、著色的动作有很大的差别。一般3D绘图中,物体的颜色、亮度等等的变化,只和物体本身,以及光源的位置有关系(有时和观察者的位置也有关系)。它并不会牵扯到其它的物体。因此,它的效果是区域性的。但是,阴影的效果则是整体的,因为随著光源位置改变,任何物体都有可能遮住另一个物体,而产生阴影。也因此,要正确地画出阴影,是一个相当困难的问题。
障碍物在球体上产生阴影。 |
使用3D绘图晶片产生阴影
一般3D绘图晶片,是针对“区域性”的打光效果设计的。3D晶片上并没有保留整个场景的三角面资料,它只是对目前送进来的三角面进行著色的动作。因此,在一般的情形下,3D晶片并不能自行判断出,一个三角面是否被别的三角面遮住,而使它在阴影中。
不过,虽然如此,3D晶片还是得处理一些“整体性”的工作。最简单的例子,就是处理隐藏面问题。在进行3D绘图的时候,离观察者较近的物体,会遮住较远的物体。因此,3D晶片需要能判断出,哪些三角面会被哪些三角面遮住。由于3D晶片并不保留任何三角面的资料,因此,它并不能去检视之前所画的三角面是否会遮住目前所画的三角面。为了解决这个问题,目前的3D晶片,通常是使用Z buffer。这样一来,3D晶片就可以避免以三角面为单位来进行隐藏面的判断,而是以pixel为单位进行。
可以看出,阴影的问题,和消除隐藏面的问题,其实是十分类似的。如果把观察者的位置到在光源的位置,把整个场景画过一次,就可以知道哪些物体是被光源直接照射到的,也就是会反射光线,较亮的物体。相对的,没有被直接照射到的物体,当然就是在阴影中了。这个方法其实就是Shadow map的基本原理。不过,Shadow map还有许多麻烦的问题要克服,而且Doom 3也不是使用这个方法,因此这里就不再多做讨论。但是Shadow map的前景看好,最近推出的3DMark05主要就是使用Shadow map来产生阴影。Shadow map也有比较简单的版本,但是它并不能用在整个场景上面。
Doom 3使用的是所谓的Volumetric Shadow,或称为stencil Shadow(因为Volumetric Shadow通常需要使用到stencil buffer)。Volumetric Shadow的基本原理,是让所有的物体“投射”出阴影。被“投射”到的物体,就是在阴影中。相反的,没有被“投射”到的物体,就是被光源直接照射,会反光的部份。Volumetric Shadow和Shadow map方式最大的不同,是Shadow map基本上是由pixel组成,但是Volumetric Shadow则直接在三角面上进行处理,而不是把物体看成一个一个的pixel。
Volumetric Shadow
Volumetric Shadow的基本方式,是先找出物体的外框。接著,再从外框延伸出一个"Volume"。这个Volume就是阴影的范围,也可以称为Shadow Volume。任何在这个Volume内部的pixel,就是在阴影里面。因此,问题就变成,要如何判断一个pixel是否在Volume里面。这就是 stencil buffer发挥作用的地方。
Stencil buffer有点类似Z buffer,它为每个pixel记录一个数字。在画三角面时,可以指定要进行某个运算,例如把数字加一、减一等等。另外,也可以指定在画三角面时,只处理stencil数字在某个值的pixel。例如,可以要求只画在stencil值是0的pixel,其它的pixel则不画。
Volumetric Shadow 示意图。 |
要利用stencil buffer来判断哪些pixel在Shadow Volume中,首先要把每个pixel的stencil值清为0。接著,画出所有的三角面的Shadow Volume中,正对著观察者的部份(上图的白色部份),同时,把stencil设成“加一”。这样一来,所有在Shadow Volume后面的pixel(包括Shadow Volume里面),其stencil值都会是正数。接著,再画出Shadow Volume中,背对观察者的部份,但这次把stencil 设成“减一”。这样一来,在Shadow Volume后面,但是却不在Shadow Volume中的pixel,其stencil值会回到0,但是在Shadow Volume中的pixel的stencil值则还是正数。这样一来,就可以用每个pixel的stencil值,来判断它是否在阴影中了。
虽然Volumetric Shadow的基本原理就是这样,但是实际上却还有很多问题。最明显的问题,是当观察者跑到Shadow Volume里面的时候,Volumetric Shadow就会出错。另外,在3D绘图时,通常都会设定一个Z clip plane,限制场景绘图的范围,但是如果切到Shadow Volume,则会产生空洞,而造成问题。因此,许多人发展出各种方法,设法解决这些问题。其中,最有效的方法是Carmack's reverse,是由id Software的John Carmack提出的(其中也有许多其他人的贡献)。这个方法是利用反转Z buffer测试的方式,使观察者永远不可能出现在Shadow Volume里面。这样就解决了第一个问题。第二个问题的解决方法,则可以透过取消远端的Z clip plane来解决,不过这样可能较缺乏效率。另一个方法,则是由显示晶片在硬体上,针对Z clip plane进行处理,使Shadow Volume在Z clip plane上可以自动封住Shadow Volume,使它不会产生空洞。
经过这些改进,Volumetric Shadow变成一个相当可靠的方法。由于它是以三角面为基础,因此它非常精确,不像Shadow map等方法会有解析度不足的问题。而且,它并不需要很多特别的硬体支援,只需要stencil buffer。Stencil buffer是OpenGL的标准功能,因此许多3D晶片都有支援。这想必也对id Software决定在Doom 3中使用Volumetric Shadow有一定的影响。
不过,Volumetric Shadow并非全无缺点。它最大的问题,是在绘制Shadow Volume时,会吃掉大量的fillrate。而且,由于Shadow Volume是由物体的3D模型的外框所产生的,而要计算外框,就需要处理器进行处理(这个工作并不适合由显示晶片进行)。这会增加处理器的运算量。它也无法用在利用具有透明区域的贴图的物体上,有些游戏使用这样的贴图来绘制复杂的物体,像是树木。最后,voluemtric Shadow 产生的阴影都非常锐利,但真实世界中的阴影通常都有些模糊。这是因为真实世界中的光源都不是一个小点,而是具有某个大小。要使用Volumetric Shadow产生模糊的阴影,可以用多个靠近的点光源来模拟一个具有大小的光源,但是这样当然就会更慢了。这些都是Shadow Volume的主要问题。
凹凸贴图 除了完整的阴影之外,大量使用凹凸贴图(bump mapping)是Doom 3引擎的另一个特色。在Doom 3引擎中,大部份的物体都有法向量贴图,并用来进行打光的计算。因此,几乎所有的打光都是以pixel为单位,而不像以前的游戏主要使用以顶点(vertex)为单位的打光。这样可以达到更好的效果。
除了一般的凹凸贴图之外,Doom 3在人物和怪物等物体上使用另一种方式产生凹凸贴图。由于一般的显示晶片速度有限,所以一般人物或怪物的3D模型,并不能有很多的三角面。特别是Doom 3大量使用Volumetric Shadow,需要处理器处理这些三角面以计算出物体的外框,因此三角面的数目更是不能太多。因此,id Software在Doom 3中使用了一个很特别的方式:首先,以大量的三角面去设计人物和怪物的3D模型。然后,再将这些3D模型简化,产生一个三角面数目较少的3D模型。这个 3D模型就是游戏中实际会用到的3D模型。然后,再从原本较复杂的3D模型,对照简化的3D模型,产生适当的凹凸贴图。这样一来,在使用凹凸贴图进行打光的时候,就可以用较简单的3D模型,产生看起来接近原本复杂的3D模型的效果。
结语 Doom 3是id Software一个相当大胆的尝试。id Software试图在Doom 3中达到“所有的东西也都产生阴影”的终极目标,因此大量使用了Volumetric Shadow。另外,几乎所有的东西都使用凹凸贴图,其打光效果相当不错。但是,这也对显示卡和处理器造成了很大的负担。由于Doom 3本身的游戏性并不特别出色,因此,其它游戏公司会不会愿意使用Doom 3引擎来开发游戏,就成为Doom 3能不能成功的一个重要关键。
另类观点:什么是Shadow map? |
由于hotball兄并未详谈Shadow map,笔者当时也讶异Doom3并未采用该技术,而使用Shadow Volume作为产生复杂阴影的手段,所以趁机越俎代庖一下。
Lance Williams在1978年发表Shadow mapping的观念时,其出发点就在于有效利用Z-buffer演算法、很快的产生各arbitrary object的阴影。基本原理如下:如果今天我以光源的角度来绘制整个场景,自然Z buffer中就会包含著“最接近光源的pixel”之距离资讯。在Z buffer中的这些资讯,就是我们所称的Shadow map、也有人称为depth map,它本身的作用就是一块“贴图”,所以往往名词后面会加上texture。
问题在于,我们现在有了这些资讯,但是要怎么确定哪些pixel是在阴影之中、哪些不是呢?所以我们必须为了解析整个场景的可视范围,透过ambient light以使用者的视角再绘制一次场景。因为光源和使用者的视角不同,座标系统自然也不一样,所以我们必须先把Shadow map中的Z值另外做过转换至使用者座标系统的动作(这个转换动作可能在实作上会有所不同,例如Nvidia在文件中所描述的方式),再比对使用者视角Z buffer中的Z值。如果两者相同,那么该pixel就不在阴影中,反之亦同。最后,整个场景再以整个light equation绘制过,不在阴影中的pixel color就是原本最后正常绘制过程中的颜色,阴影中的pixel color就是第二次ambient light绘制的结果。
这也就是为何Shadow mapping可以用来处理有洞的三角面之故,因为它是直接从光源的视角来取得阴影的资料,而不是像前述的Shadow Volume是切割一块空间做为阴影的范围。另外,只要光源和物件的位置不变,Shadow map可以一直被多张frame所沿用,而使用者视点却可以改变,因为这些阴影是独立视角(view-independent)。除此之外,Shadow mapping有著沿用既有贴图机制的优点外,此演算法的complexity呈线性分布也是一个好处,例如场景中所绘制的物件数目增加一倍、整个运算所需要的能量和时间也就呈现一倍的增加,这对于评估游戏效能相当的有帮助。
Shadow mapping也不是没有缺点的,首先就是解析度以及Z buffer精度的问题。前者如果解析度太低,在阴影的边缘就会出现明显的区块化(blocky),也就是锯齿感;后者如果光源的色深(depth)值和使用者视角本来应该相同(不在阴影中),但是却因为精度问题导致略有误差所以不同(在阴影中),这就是很严重的计算错误。另外,因为Shadow map是在比对的过程中作取样的(别忘记它是贴图),所以很容易产生alias问题,尤其是在靠近边缘的部份。像是在Shadow mapping中经常出现的self-Shadow aliasing就是一例,因为取样时的精度问题导致物件Shadow自己,鱼尾纹(Moire' pattern)就是这个问题最好的象徵。如果要克服边缘的锯齿感,可以使用bilinear filter,不过这就会增加运算的负担。后者因为解析度、精度有限所导致的razor's edge问题,除了设法提高解析度和精度外,我们可以增加bias factor、也就是显示晶片厂商俗称的polygon offset。
不过,有一件事情容易被众人所忽略,无论何种改良过的Shadow mapping方式,它们都有著相同的先天限制:如果光源的位置是在场景之外某处、透过远方视角观看整个场景,例如spot light,就会运作的很好,这也是Shadow mapping的“先天假设”。但是,如果光源的位置是在场景之内呢?这时候该怎么办?当然,这种位置性的光源,我们可以用一个六面的cube去表示它,然后由这六个面各自绘制整个场景,但是这样除了“爆增”运算能量和记忆体空间外,cube毕竟无法完全表示一个散射性的光源,所以其接缝处一定会造成某些问题,尤其是bias factor在接缝的边缘和cube的平面上绝对有所不同,这都会造成相当程度的困难。
最后,在3D游戏之类的即时运算图学领域中,为了维持顺畅的fps,往往不会采用看起来最酷最炫的技术,而且历史的教训也证明了,只要用心,看似简单的技术也可以做到很棒的效果,从John Carmack在Quake中如何有效利用lightmap做出spot light的效果,即可略见一二。在将计就计(Entrapment)这部电影中,男主角MacDougal的名言“It's impossible. But do able.”(不可能,但作得到),也许就是最佳的注脚。对于real-time rendering的领域来说,要找出作出复杂效果的替代方案,就是最大的挑战,但这也游戏设计者应该必备的能力。 |
|
文⊙劉人豪(夜露死苦技術專欄)
原文链接 http://www.poweruser.com.tw/