阴影技术(2)——CSM
背景
在传统的shadow map方法下,如果场景很大,一眼过去可能几百米都零散分布着阴影,若shadow map贴图太小,则锯齿很明显,想提高精度就要增大shadow map贴图,然而内存开销就会过大。
Cascade Shadow Map(CSM) 借鉴了 LOD 的思想,对主摄像机的视锥体范围进行了划分,然后对每个划分区域采用相同分辨率大小的 Shadow Map,这意味着:离主摄像机比较近的地方往往区域面积是比较小的,这样 Shadow Map 能够表示的深度信息就更精确些,看到的阴影效果也更真实;离主摄像机比较远的地方往往区域面积是比较大的,虽然使用相同分辨率的 Shadow Map 能够表示的深度信息比较不精确,但对于远处的物体来说,这种不准确是可以接受的。
视锥分割
把视锥体分为若干层的方法主要有两种:
- 一种是项目组自己设置一个比例
- 另一种方法是利用对数比例分割,能够减少 Shadow Perspective Alias 问题的出现,同时可以保证各级阴影看起来质量差异不大
下面我们来介绍这个方法:
n为主摄像机近平面距离,f 为主摄像机远平面距离, θ为物体平面法线的角度参数
我们期望各级阴影看起来质量差异不大,因此需要保证摄像机近平面上的一小段距离$dp$ 和 ShadowMap 平面上的一小段距离$ds$之比应该是一个固定值$C_0$。我们可以假设如果有一个物体在z处,如上图,dp也可以从这一点导出,算出$\tan\theta dz$代表这一点在截面上的长度,根据比例$z/n$可以反算出$dp = \frac {n\tan\theta dz} {z}$
可以把$\tan\theta dz$看作截面上一段距离,我们把这个整体当作新的$dz$, 又因为n是常量,移动到$C_0$那边即可,可得:
这是一个微分方程,其中 z=n,s=0 和 z=f, s=1,可解:
即保证这个式子可以获得更好的阴影效果,每一个s和z相对应着,然而CSM级联次数有限,通常做3-4级。
我们希望知道均匀的分布s下,z的分布,因此把S平均分成N份时,对应每段z为:
这就是对数分割。
对数分割的问题在于第一层的shadowmap覆盖内容很小,会造成一定浪费,因此可以将对数分割出的结果z和线性做一个混合:
$\lambda$是一个 $[0,1]$区间的控制参数,用以在对数和线性之间进行插值。
计算包围盒
分割好各层视锥体后,我们需要选择对应的 Shadow Map 来恰好包围住视锥体(即各层的 Light Camera 的光锥体刚好包围住视锥体)。
OBB(Oriented Bounding Box):这种看起来shadowmap利用率更高,但是考虑当视锥体变化时,容易阴影闪烁
AABB(Axis-Aligned Bounding Box):利用率较低,但是层级之间有重合,方便插值过度。
以上两种都会有抖动问题,实际上应用时,会选择Stable Cascaded Shadow Maps,最后一节会介绍
层级选择
有了若干层 Shadow Map 后,渲染某个shading poing时该如何判断点在哪个层级:
- 直接通过视锥分割的z值范围来判断所在是哪个层级
- 通过各层 Light Camera 的 View 变换和 Projection 变换,得到点在该层 Shadow Map 的 UV 坐标,当 UV 坐标在 [0,1] 范围内时则说明在该层级内。选择更底层的层级
前面我们在视锥分割已经确定了z值划分范围,直接简单根据shading point的z值来判断层级不是更好吗?这是因为每一层的 Shadow Map 其实多多少少包含了更远一层的部分阴影信息,但是它的精准度明显要比更远层的 Shadow Map 要好,因此通过uv坐标判断点所属层,就可以尽量命中较近层的 Shadow Map。
如何处理层级间的过度
如果仅选择单个层级,会容易出现各层级阴影交界处出现阴影效果剧变的问题,这时候也可以混合上下两层 Shadow Map 来让交界可以过渡变化。
分帧策略
常见的做法是越远的cascade更新速度越慢,保证8帧更新完一轮。
Stablize CSM
由于 CSM 的阴影矩阵是随视锥体变化而变化的,而矩阵的数值精度问题可能会导致阴影计算的结果产生抖动。Stablize CSM 会将相机的变化拆解为旋转和平移。
视锥体旋转时,stablize CSM 会对视锥切割部分建立一个固定大小的圆形包围盒(如图 c),这样无论视锥体如何旋转,切割部分都不会超出其所在的包围盒;接着,我们就对圆形包围盒建立 AABB,该 AABB 将作为 light camera 的远平面。因为该 AABB 的大小也同样是固定大小,并且 AABB 并不会和 OBB 那样随意旋转,因此 stablize CSM 的阴影矩阵就不会每帧产生新的旋转,而只会转化成平移并叠加上相机的平移。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!