概况 阴影贴图(Shadow mapping)是在三维计算机图形中加入阴影的过程。阴影贴图的概念最初是由 Lance Williams 于 1978年在“在曲面上投射阴影”这篇论文中提出的。从那时开始,这种方法就已经用于场景预渲染、实时甚至是许多游戏设备以及高端电脑游戏中。在Pixar 的 RenderMan 中就使用了阴影贴图技术,同样,在 玩具总动员 这样的游戏中也使用了这项技术。
像素与以纹理形式保存的光照深度缓冲区或者深度图像比较,通过这种方式计算像素是否处于光源照射范围之内,从而生成阴影。
也叫做光照贴图(Light Mapping)——这是一种可以在不减少帧率的情况下达到真实感光照和阴影效果的方法。阴影贴图编辑器允许在场景中放置任意数目的静态光源,它会为每个面预计算光
流量(light flow)和静态阴影。现代商业3D游戏中多数都会使用到阴影贴图。
从光源位置看出去,所有能够看到的物体都处在光照之中,但是这些物体后面的东西将处于阴影这种。这就是生成阴影图的最基本的原理。光照场景进行渲染,保存能够看到的物体表面深度,即为阴影图。然后,正常的场景中的每个点都与这个深度图进行比较,就好像判断场景中的每个点能否被光线看到,从而进行正常场景的渲染。
对于实时阴影来说,这项技术要比阴影体的精度差一些,但是根据在特定应用中每种技术所需填充时间的不同,阴影图有些情况下是一种速度比较快的选择。并且,阴影图不需要额外的模板缓存,并且可以经过修改生成柔和边界的阴影。但是,与阴影体不同,阴影图的精度受到分辨率的限制。
对于阴影场景的渲染需要两个步骤来完成。第一步是产生阴影图本身,第二步是将阴影图应用到场景中。根据实现方式以及光源数目的不同,阴影场景渲染过程可能需要两个或者更多的绘制过程。
第一步是从光源的视角渲染场景。对于点光源来说,视场是一个视角足够宽的透视投影。对于象太阳光这样的定向光来说,需要使用正投影。
根据这个渲染结果提取、保存深度缓冲。因为只与深度信息相关,因此通常避免更改颜色缓冲区,并且不对光线及纹理进行计算以及节省时间。这个深度图经常在图形卡的内存中保存为纹理。
一旦场景中的光源或者物体发生变动,就必须重新更新深度图,但是在其它场合下也可以重复使用深度图,例如只有照相机运动的场合。如果场景中有多个光源,必须针对每个光源分别生成深度图。
在许多实现方法中为了节省重绘深度图所花费的时间,可以只对场景中的一部分物体进行渲染来生成阴影图。另外,为了解决当前深度值与下一步绘制的表面深度太接近产生的深度冲突问题(Z-fighting),也可能在阴影图渲染过程中对物体的照明深度添加一个偏移。实现这种效果的另外一种可选方法是裁剪掉前面的物体,仅对后面的物体进行渲染生成阴影图。
第二步就是使用阴影图按照普通的透视投影绘制场景。这个过程分为三个主要的部分,第一是找出物体在光照视景中的坐标,第二是将这个坐标与深度图中的对应值进行比较,最后就是根据处于阴影或者光照下的位置将物体绘制出来。
光照空间坐标
为了将物体点与深度图进行比较,物体在场景坐标系中的位置必须要转换到光照空间中的对应位置,这个变换可以用矩阵乘法实现。物体在屏幕上的位置可以通过普通的坐标变换确定,但是必须要生成第二套坐标来定位物体在光照空间中的位置。
将世界坐标系变换到光照空间坐标系的变换矩阵与第一步中用于渲染阴影图的变换矩阵相同,在OpenGL中它是模型视图与投影矩阵之积。这样就生成一组齐次坐标,如果可见的话经过透视除法(perspective division)转换成归一化设备坐标,每个分量(x、y、z)都落在 -1 到 1 之间。有许多的实现方法要进行另外的缩放及偏移矩阵乘法将 -1 到 1 之间的数值转换到深度图或纹理图中更常用的 0 到 1 之间的数值。这个缩放过程也可以在透视除法之前完成
如果使用shader或者其它的图形硬件扩展完成这项工作的话,那么通常在顶点级进行这样的变换,生成的数值在其它定点之间进行插值,然后传递到片断处理的部分。
找到光照空间的坐标之后,x 与 y 通常对应于深度图纹理的位置,z 对应于相应的深度,这样就可以与深度图进行比较。
如果 z 大于深度图中相应位置保存的数值,那么就认为物体被其它物体所遮挡,这个点就标记为失败,在绘制过程中将它描绘成阴影状态;否则的话,就将它绘制成照亮状态。
如果位置落在深度图之外,那么程序就需要确定表面要用缺省的照亮或者阴影方式绘制,通常是绘制成照亮的状态。
在 shader 的实现中,这个过程是在片断层次完成的。另外,当选择硬件所用的纹理图存储类型的时候需要注意:如果无法进行插值,那么阴影将会出现明显的锯齿边界,可以通过使用较高的阴影图分辨率来减少这种现象。
根据与阴影边界的接近程度使用一个取值范围而不是简单的通过或者失败,通过这样的修改就可以实现更加平滑的阴影边界。
阴影贴图技术也可以经过修改用于在照亮区域生成纹理,这样就可以模拟投影机的效果。上面标题为“投影到场景中的深度图的可见性”的图片就是这样的一个例子。
绘制场景
带有阴影的场景的可以通过几种不同的方法进行绘制。如果有可用的可编程 shader,深度图检验可以用碎片 shader 完成。在最初的生成阴影图之后,它一次就可以根据结果完成阴影或者被照亮物体的绘制。
如果没有可以使用的阴影工具,通常必须使用一些硬件扩展如 GL_ARB_shadow 等进行阴影图检验,它们通常不允许在两个光照模型之间进行选择,并且需要更多的渲染过程:
将整个场景渲染到阴影。对于大多数的光照模型(参见Phong反射模型)来说,在技术上仅仅使用光照中的环境光成分就可以完成,但是通常加入一个模糊的漫射光使得阴影中平坦的表面看起来有一定的弧度。使用深度图检验,然后渲染照亮的场景。深度图检验失败的区域不要覆盖,仍然保持在阴影状态。对于每一个额外的光照都可能需要额外的处理过程,用加性混合方法它们的效果与已经绘制的效果合成在一起。每个这样的过程都需要前面的生成相应阴影图的过程。
这篇文章中的例图是使用 OpenGL 的扩展 GL_ARB_shadow_ambient 完成阴影贴图过程的。