Chapter 7 – Shadows 阴影

7.3 阴影体积(Shadow Volume)

1991年,Heidmann提出了一种基于阴影体积的方法,其通过使用stencil buffer,能够在任意物体上投射出阴影。所有支持stencil buffer的GPU都能够实现这一算法。其不是基于图像的(不同于我们下一小节学习的阴影映射算法),同时也避免了采样的问题,因此这一技术能够在任何地方生成正确而清晰的阴影。但有时这也是个缺点。例如,游戏角色身上的布料有一些褶皱那么这将会生成一些hard shadow。由于阴影体积的开销的不确定性,现在这一技术很少被使用了。本小节只会简单介绍这一技术。

首先,我们想象一个点与一个三角形。从该店射出三条射线穿过三角形的三个顶点并不断延伸形成一个无限的“金字塔”,如下图所示。现在我们将这个点想象为一个点光源。之后,任何位于红色三角形下方的截断金字塔中的物体都是位于阴影中的。而这个体积就是阴影体积。

假设,我们将camera置于场景中,并从camera射出一条射线,其穿过像素直到与某个物体相交,而与该物体的交点将显示在屏幕上。之后,我们创建一个计数器,初始值为0,当射线与阴影体积的某个面相交,且这个面是朝向camera的,我们对计数器进行累加。因此,每当射线进入阴影体积时,计数器都会增加。同样,当射线穿过阴影体积的面是背向camera的,我们对计数器进行累减。也就是说,射线离开了阴影体积。我们不断进行累加和累减,直到射线与将要显示在屏幕上的物体相交。这一原理也适用于多个三角形投射出的阴影。如下图所示。

上图是一个二维的俯视图,使用了两种方法对阴影体积进行计数。在z-pass体积计数中,当射线与阴影体积相交的面是正面的,我们对计数器进行累加,当射线与阴影提及相交的面是背面的,也就是离开阴影体积时,我们进行累减。所以,对于点A,射线两次进入阴影体积,所以+2,之后又两次离开阴影体积,所以+2。因此计数器为0,点A并没有位于阴影中。另一种计数方式被称为z-fail体积计数,其在射线通过对应像素点之后开始计数。射线每一次进入阴影体积都会使得计数器减一,而离开阴影体积则会加一。上图中的斜体字就代表了z-fail体积计数。对于点C,-1表示其进入了阴影体积,而后方的两个+1分别表示离开了阴影体积。由于计数不为0,所以该点位于阴影中。

进行一系列的射线检测是耗时的。但是我们可以选择一个更聪明的方式:使用stencil buffer。首先,stencil buffer被清空为0。第二步,将整个场景以未被光照影响的材质(例如,只包含贴图的颜色信息)绘入framebuffer中,这么做只是为了得到RGB颜色以及z-buffer中的深度信息。第三步,我们关闭z-buffer更新与back buffer的写入(但是z-buffer test照常运行),再绘制阴影体积的每一个正面三角形(我们将阴影体积当作一个多边形进行绘制,其每一个面由一个或者两个三角形组成)。在这个过程中,每当我们绘制一个三角形都会对stencil buffer中对应的像素进行累加。第四步,与刚才相似,这一次我们只绘制阴影体积的背面三角形。而此时我们每一次绘制三角形都会对stencil buffer进行累减。只有当阴影体积各个面中的像素是可见的(例如,并未被场景中其他的几何体遮盖),才会对stencil buffer进行累加或者累减。此时stencil buffer将包含场景中每一个像素的阴影状态。最后,根据stencil buffer我们再绘制一次场景,只有值为stencil为0,我们才绘制相应的像素,并运用正确的光照等式。当stencil为0,则表示由camera指向该像素的射线,进入阴影体积与离开阴影体积的次数一致,且该点受到光线的影响。

上述就是阴影体积的基本原理。阴影体积算法所生成的阴影如下图所示。

我们可以在一个渲染通道中实现阴影体积,这一种方法的效率将会更高。但是,当场景中的物体camera的近平面穿插时,会发生计数不准确的问题。其对应的解决办法就是上文我们提到的z-fail。

为每一个三角形创建阴影体积的四边形无疑会产生极大的消耗。也就是说,网格模型的每一个三角形都需要绘制三个额外的四边形以组成阴影体积。一个由一千个三角形组成的球体对应三千个四边形,而每一个四边形都有可能跨越整个屏幕。一个解决办法是只绘制物体边缘轮廓的四边形,例如,我们提到的球体只需要50个四边形来表示。几何着色器可以自动生成这种轮廓边缘。剪裁和clamp技术也可以减少填充模型中间部分所产生的消耗。

但是,阴影体积算法仍然有一个严重的缺陷:非常不稳定。假设,视野中只有一个微小的三角形。如果camera和光源位于同一个位置,那么阴影体积的开销是最小的。组成阴影体积的三个四边形并不会覆盖任何相似像素(大家脑补一下,这三个四边形将变成三角形的三边,也就是三条线,并不会占据任何空间)。假设现在camera绕着三角形转动,并始终将三角形保持在视野内。随着camera与光源的位置不再重叠,阴影体积的四边形将变得越来越明显,并且覆盖屏幕中更多的像素,这也意味着需要进行的计算将不断增加。如果camera碰巧进入三角形的阴影中,那么阴影体积将会填充整个屏幕,其计算开销将会大大增加。这一不稳定性使得我们无法在那些交互性较强的游戏中使用该技术,因为我们必须保证帧数的稳定。当camera朝向光源时,这一算法的开销将会大大增加。

正是上述这些原因,大多数游戏并不会使用阴影体积算法。但是,随着GPU的不断进化以及研究者的持续努力,阴影体积算法可能再将来重新被我们所使用。我们在下一小节所学习的算法,shadow mapping(阴影映射),其开销较为稳定且非常适合GPU,所以在许多游戏与应用中都会使用这一算法生成阴影。

留下评论

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据