Cascaded Shadow Map(CSM)是目前引擎中主流的阴影技术,效率与效果均不错。它与传统的单张Shadow Map的区别主要在于将视锥体进行了层次的分解,每一层单独计算相关的SM,这样在渲染大场景的阴影就可以避免传统的SM的各种缺点。典型的CSM原理基本上如下图所示:
虽然看起来原理简单,但是在实际中想实现一个健壮的CSM并不容易,涉到各种问题,其中一些是这种方法本身就固有的,需要使用多种手段来解决或规避。
为了提高SM的有效利用率(也即是要在生成的SM中尽量减少无关区域的面积),需要尽量减小生成SM时所使用的投影矩阵,这样就需要计算出在当前Cascade下所对应的Frustum的较为紧密的投影矩阵。这一般是在光源空间下计算出视锥体所对应的AABB包围盒:
不过这样直接使用AABB会带来另外一个问题,由于Camera的属性会实时改变,因而其对应的Frustum的属性也会不断改变,而AABB对Frustum的变化又较为敏感,这样就可能出现前后两帧中Frustum所对应的AABB变化巨大,进而导致生成的SM的有效分辨率(是指对于某一固定物体来说的其投影到SM中后所占用的pixel的数量,SM的大小并不变化)可能在连续两帧中出现突变,这样就会出现阴影的抖动(Flickering)的问题。关于这个问题有论文(Stable Cascaded Shadow Maps)提出了使用Sphere来代替AABB的解决方法,因Sphere对Frustum变化的敏感性相对较低,这样一定程度上会解决抖动的问题。更好的解决方法是在Shader X7中的Practical Cascaded Shadow Maps中提到的渐进式变换Frustum的方法,而且也能同时在一定程度上解决Rasterization所带来的抖动问题。
由于Shadow Map在使用时对Z值的精度较为敏感,因而也就需要在生成SM时尽可能提高有效Z值的精度,这就引出了另外一个问题:那就是在生成SM时使用较为优化的远近平面。这里的首要原则是使用保守的方法来尽可能得到相对优化的的远近平面,太过于激进的方法容易过多地裁掉物体进而造成阴影漏洞。不能简单使用光源视锥体所对应的AABB来计算远近平面,这样当场景过大时会使用漏掉近平面以内的一些物体。更为合理的方法是使用两端扩展的光线视锥体来与场景求交计算出合理的远近平面。
一般来说需要将使用CSM时需要将View Frustum分割为若干个Cascades,而且大多数情况下光源的方向与View的方向并不垂直,这样一来就会在渲染每个SM时可能会出现Light所对应的视锥相互交叠的情形,这样就导致了重叠部分的几何体元被重复绘制,如下图所示:
这种情况下可以选择对两者重叠的部分进行剔除,以便减少需要绘制的几何体元的数量来提高SM的生成速度。但是这里要注意:如果使用带有剔除的SM生成方法的话则需要在SM的查找时使用Map-based cascade selection的方法来选择最为合适的SM,而不能使用基于Split索引的方法直接定位当前Pixel到其所在的SM上,否则会出现阴影的漏洞。另外,还需要对每个层级上的光源视锥的远平面进行调整以便其可以包含到下一层级中的一些物体,否则就会出现一些Pixel定位到上一层级的SM 上,但其中并没有生成实际包含在下一层级的视锥体中的物体,这样同样会漏掉一些阴影。
关于CSM,与D3D SDK中的示例相结合的两篇文章很不错:Cascaded Shadow Maps,Common Techniques to Imporve Shadow Depth Maps;另外,还有Shader X6中的Stable Cascade Shadow Map以及X7中的Practical Cascade Shadow Map。最后一篇论文中总结了CSM多个方面的优化问题(比如Shadow Map的Pack等),很不错。