随着硬件的越来越高端化,各种以前可望而不可及的效果越来越多地被应用到网络游戏里。本篇文章是介绍目前游戏中影子的实现方式,它们的优缺点以及应用的场合。

    一般来说,在游戏中,应用比较多的影子技术有三种,Projective Shadow、 Shadow Map以及Shadow Volume。我们接下来便依次的介绍它们。

Projective Shadow

 图01:投影的原理可以应用在任意贴图上

    Projective Shadow就是我们通常所说的投射影子,它的实现比较简单,具体有两个步骤:

    1. 以光源为视点,渲染有影子的物体到一张预备好的Shadow Texture中。要注意的是,这里的渲染在计算投影的时候,需要把顶点从设备空间转换到贴图空间,以便于后面的贴图。

    2. 渲染被影子覆盖到的物体,然后把影子覆盖到物体上。这里是整个Projective Shadow最为关键的步骤。通过上面提供的Shadow Texture,可以使用一个PASS把受影子影响的物体渲染出来。也可以根据实际应用通过Blend,使用多个PASS来渲染。在渲染Shadow Texture的时候,需要API来为它生成最后的贴图坐标。在DX中,我们需要把D3DTSS_TEXCOORDINDEX设置成D3DTSS_TCI_CAMERASPACEPOSITION,同时把D3DTSS_TEXTURETRANSFORMFLAGS设置为D3DTTFF_PROJECTED。如果我们使用OGL,可以把GL_TEXTURE_GEN_MODE设置为GL_EYE_LINEAR, 同时设置纹理矩阵为投影纹理矩阵。

    Projective Shadow原理简单,实现方便,对于硬件配置的要求很低,效果也不错。对于对影子效果要求不高的游戏来说,只需要不多的代码就可以加入。但是由于Projective Shadow是需要计算能被影子投射到的对象,需要根据影子Caster来计算出它所对应的Receiver,这个步骤在场景对象过多时会大量消耗CPU时间。同时,对于场景中物体渲染的管理也是对于实时应用的一个挑战,我们必须小心地管理物体渲染的层次关系。

Shadow Map

    Shadow Map是一种成熟已久的技术,对于它的原理,很多初涉图形学的爱好者都能够很清晰地讲出它的实现步骤。简单来说,它是一种基于图像空间的技术。首先它需要根据光源的位置作为视点把整个场景渲染出来,以得到它们相对于光源的深度信息,然后再使用这些深度信息去决定场景中哪一个部分是处于阴影之中的。

 

图02:判断点是否处在阴影中

    标准的Shadow Map,我们可以在DX SDK中找到它的例子,看起来它的实现也并不复杂。但不幸的是它的作用也只是停留在教学上。换句话说,它一点都不实用,即使在例子中,我们也能很容易发现它的缺点。

    首先它的精度不够,虽然DX的例子还能看得过去,但由于Shadow Map是基于整个场景来渲染的,也就是说,它会用一张贴图来渲染整个场景的影子。这样来说,用于存储信息的贴图大小就直接影响到影子的质量。小场景还勉强能够应付,但只要碰到稍大的场景,影子就有可能出现很严重的锯齿,影响到影子的质量。另外深度缓冲的精度问题也是难于解决的,特别是当影子的Caster和Receiver很接近的时候,Z-Fighting的情况发生得非常明显。

    当然,Shadow Map也有自己的优点,实现方便,效率较高,易于理解等,尤其是由于它是基于图像空间的一项技术,可以用不大的代价就实现软阴影的效果。所以近些年来,很多图形学的学者也基于Shadow Map的原理提出了很多改进型的算法,Shadow Map也越来越多地应用到了大型的游戏中。我们要介绍的也是一种Shadow Map很重要的改进算法的思想――Perspective Shadow Map(PSM)。需要注意的是,这种思想本身也是不尽完美的,但是以它为主导思想,近几年来,图形学学者来提出了不少新型的算法。

    PSM是在当前摄像机的透视后的空间中计算中,用传统图形学的说法,就是归一化后的设备空间中计算的。它的基本想法是首先把场景映射到Post-perspective空间中,然后在这个空间中通过把变换后的光源转换到一个归一化空间里生成一个标准的Shadow Map。

    如下图,S是场景Bounding、L是光产生的视锥、V是视锥,而M包含了所有从V到L的涉嫌。这个算法比较关键的是最后能够生成下图中黄色的区域H,H包含了所有Shadow Map所需要包含的空间。这也是生成Post-Perspective空间的最小Bounding。

 
图03

    有了Bounding之后,Post-Perspective空间的视点就很容易得到了。

    根据以上的计算得到Post-Perspective空间的矩阵之后。其他的步骤就和标准的Shadow Map是一致的了。

 

图04:标准的Shadow Map


 

图05:Perspective Shadow Map

    同样的视点,同样的贴图大小,使用PSM我们可以得到更加细致的效果。

    但PSM也有一些缺陷,比如说影子的质量比较依赖视点,影子近处和远处所得到的Z分布差异过大等。近些年提出的TSM、LISPSM都基于PSM的原理使得这些问题得到了一些改进,也使得Shadow Map更多的使用到游戏中。

Shadow Volume

    Shadow Volumes技术是一种基于几何形体的技术,它需要几何体在一定方向灯光下的轮廓去产生一个封闭的容积,然后通过光线的投射就可以决定场景的阴影部分(通常来说使用模板缓冲去模拟光线的投射)。

    这种技术最大的优点就是影子可以精确到像素级别。影子的效果会很细致。但它也有一些缺点,首先对几何体有一定的要求(必须是闭合的),同时对于效率的损耗也非常大(高填充率)。

    为了构造阴影体,我们需要从灯光的位置引出通过要投射影子物体的每个顶点,这些投射的射线组成了一个封闭的体积,任何处于这个体积中的点我们便可以认为它是被阴影覆盖的。反之,处于体积之外的点便是处于光照中的。

 


图06:处于阴影体中的物体才会接收到影子

    通常来说,判断物体是否处在阴影中有两种基本算法:Z-PASS和Z-FAIL。简单来说Z-PASS算法是从视点引一条射线,当射线进入阴影体的时候,stencil加1,当射线出阴影体的时候,stencil就减1。这样的话,如果stencil的值在计算完成后为0,就代表物体不处在阴影中。对于Z-Fail算法,不同的是在渲染阴影体的时候,对back face和front face的情况做了一些特殊的判断,当对于back face 的Z test失败的时候,stencil值加1,而对于front face的Z test失败的时候,stencil值减1。

    对于构建Shadow Volume来说,找出物体的轮廓线很重要。基本的思想就是找出所有朝向相反的两个三角形的共享边,最终组成整个物体的轮廓。

总结

    本文讲解了目前主流的几种生成阴影的方式。总体来说,各自都有应用的场合。Projective Shadow比较适合应用在对效率要求比较高,对影子要求不是完全真实的场景中。Shadow Map比较适合用于场景中静态物体的影子渲染,它也能实现比较柔和的软阴影。而Shadow Volume更适合于场景中的动态物体,比如说角色,怪物,风车等。大家可以根据自己具体项目的特点和需求来选择合适的技术。