Jim's GameDev Blog

Shadow Volume 2

2017-11-5

Shadow Volume 的两种实现方法及其优缺点

Shadow Volume 的原理就是在 Stencil Buffer 给阴影区域做上标记,特定标记的地方就是在阴影中的像素。在 Stencil Buffer 上做标记有两种方法,一种是 Z-Pass,一种是 Z-Fail。

Z-Pass 方法:

Z-Pass 方法的一个问题是,当相机位于 Shadow Volume 内部时就无法正确绘制阴影了,因为 Front Face 位于相机的后面,所以无法绘制一个完整的 Shadow Volume 了。

为了解决 Z-Pass 的这个问题,Z-Fail 方法出现了(Z-Fail 方法由 John Carmack 发明)。其步骤为:

当然,从 Z-Pass 方法并不能无缝过渡到 Z-Fail 方法,其中有些注意点:

上面介绍的 Z-Pass 和 Z-Fail 两种方法都需要使用两个 Pass 来绘制,每一个 Pass 分别对应 Cull Back 或 Cull Front。而使用 Two-Side Stencil 方法,可以在一个 Pass 中完成原来两个 Pass 的工作。即在绘制时,关闭背面裁切(Cull Off),分别指定 Front Face 和 Back Face 的 Stencil Operation。在使用这种优化方法的时候需要注意,需要将 Stencil Value 设置为 Wrap 模式,而不是 Saturate 模式,或者将 Stencil 值初始化为一个较为合理的中间值(比如 128,大多数情况够用,复杂的情况可能会出错)。

上文提到了 Z-Fail 方法避免了 Z-Pass 方法的一个弊端,就是相机的位于 Shadow Volume 内部的时候的一个 bug,也可以解释为 Front Face 被相机的 Near Clip Plane 裁掉了。但是 Z-Fail 同样有类似的问题,只是把问题转移到了 Far Clip Plane 上。使用 Z-Fail 方法时,Shadow Volume 必须是一个封闭体,且 Shadow Volume 的远端是在无穷远处,但是 Far Clip Plane 并不在无穷远处,这就造成了无穷远处的 Shadow Volume 由于被 Far Clip Plane 裁切到了,所以无法将值写入到 Stencil Buffer 中。这个问题一个最简单的解决办法是 Shadow Volume 远端不要投射到无穷远处,而是根据实际场景投射到足够远且保证在 Far Clip Plane 范围以内。还有一种方法是通过修改投影矩阵,来避免在齐次空间中被裁切掉。