转载请注明出处为KlayGE游戏引擎,本文的永久链接为http://www.klayge.org/?p=2381

大家一定很熟悉大场景的阴影渲染容易出现的锯齿问题,我这里就不废话了。这个问题常用的解决方式是Cascaded Shadow Map(CSM),用一系列同样大小的shadow map,每个管视锥的一个范围。现在大部分引擎也都支持CSM,各种资料也很齐全,所以我这里不详细阐述原理了。想了解细节的可以看原论文,或者GPU Gems 3的文章

CSM还是PSSM

一个常见的疑问是为什么有的叫它CSM,有的叫他PSSM(Parallel-Split Shadow Map)。实际上原论文是把它叫做PSSM,工业界实现的时候选择了个更广泛的名字CSM。从名字本身来说,只要是一系列层级关系的shadow map,就能叫CSM,而只有平行切分视锥的才叫做PSSM。换句话说,CSM是个种类,PSSM是具体方法。

PSSM

如前面所说,一般实现的CSM都是用的PSSM。算法分为几个步骤:

  1. 把视锥根据log的距离分布平行切成几个区域。
  2. 计算每个区域的scale和bias,组成crop matrix乘到projection matrix之后。
  3. 对每一区域都生成一张shadow map。
  4. 在使用的时候,根据像素在视空间的位置确定自己是在哪个区域,以选择shadow map。

除了步骤2,其他都没什么特别的。步骤2的本质就是通过放缩和平移,把shadow map“集中”到看得见(视锥内)的区域。实现本身不难,这里就提几个容易遇到的小陷阱。

光锥的建立

在切分视锥之前,需要先建立light的“视锥”(也就是光锥),用来包裹视锥。在不同的区域,这个光锥会用不同的crop matrix变换,以最大化shadow map使用率。由于CSM要处理的光源是阳光这样的非局部平行光,不像spot light那样可以直接给出光锥。在实践中,一个比较好的确定光锥view matrix的方法是:

  1. 用视锥的中心为视点、光的方向为视线方向,视锥的up为上方向,算出一个初始的view matrix
  2. 把视锥的八个角点变换到1的view space内,算个AABB
  3. 把视点沿着光的方向移到AABB之外,重新建立view matrix

这样得到的view能包裹住整个视锥,却又不会有太多多余的面积。projection matrix是个平行投影,宽、高、远近平面也可以从上面的2里得到的AABB计算出来。

有了光锥的view和projection,就能根据PSSM的算法来产生和使用shadow map了。

关闭depth clip

第一次这么尝试之后,PSSM的阴影在大部分时候都挺好,但在视线接近光的方向、以及视线逆着光的方向的时候,部分阴影会突然消失。查了很多地方都没有头绪,最后发现情况发生在,一个区域的物体不会投到另一个区域,因为在产生另一个区域的shadow map时,别的区域的物体已经被远近平面切掉了。解决方法是关闭掉远近平面的裁剪。在D3D10+,这是通过关闭depth clip来做到,在OpenGL,这是通过打开depth clamp来做到。类似的名字,相反的操作,要小心区分。

shadow map的产生

KlayGE中的shadow map都是通过渲染到depth texture,再做一次depth to linear的操作,把非线性的depth转换成线性的、view space的depth,同时计算VSM需要的depth平方。但原先的线性化公式仅仅适用于透视投影,平行投影需要另一个公式。于是就等于需要把depth to linear、VSM等好多个shader都加上支持平行投影的。与其如此,不如简化成在生成shadow map的时候就直接渲染到VSM,把view space depth存出去,不经过线性化。但不排除以后找到更好的方法之后,恢复到原先方法的可能。

光锥裁剪

在产生每一个区域的shadow map是,按说可以根据view * projection * crop后的matrix做一次光锥裁剪,去掉不在光锥内的物体。但目前我的实现里,如果打开了这个,就会多裁一些物体,还没找到原因。

结果

解决或者绕开这几个坑之后,PSSM就可以顺利使用了。分成4个区域的结果如下。残余的一些锯齿来自于deferred shadowing,不是PSSM造成的。

PSSM 4-level

如果不分区域,结果就是这样:

PSSM 1-level

对PSSM做一些分析,可以发现它有两个算法上的缺点。第一个是,PSSM对阴影效果影响很大的地方是光锥的集中程度。如果光锥需要包裹整个视锥,那么即便视锥的近平面上有面墙挡住了整个视野,光锥仍然不能集中到可见的pixel上。有一个优化方法是根据视锥内物体的AABB来提高光锥的集中效率,但如果遇上前面说的那面墙,情况并不会得到改善。另一个缺点是,决定区域划分的时候有一个参数,通过对它的调整可以改善近距离区域或者远距离区域的阴影质量,但不能两全其美。

下一篇打算介绍一下新的CSM方法:SDSM,能根据可见pixel来动态分配区域划分,完全解决掉这两个问题。