各种Soft Shadow Mapping算法及推导过程
Shadow Mapping已经成为当前3D游戏的标配。传统SM+PCF可以很好地实现日光环境下的Hard Shadow,但如果要实现由昏暗灯光所产生的Soft Shadow,则要么效果过于生硬(sample次数少)要么效率低下(sample次数多)。因此,越来越多的游戏开始使用能够充分利用硬件特性的软阴影算法。
这里主要总结各种Soft Shadow Mapping的算法思想和推导过程,不提及实现上的细节和具体代码,等有空再写写Demo。
本文为原创内容,转载请注明出处。
Standard Shadow Maps
Shadow Map的基本思想:Light View画一遍Depth,然后Camera View渲场景的时候,把Pixel坐标变换到Light Space,比较Depth即可(Pixel的Depth大于Shadow Map的Depth即在阴影区)。也可以直接是Screen Space对Depth Map做Post-processing。
传统方法可以用Percentage Closer Filtering解决锯齿问题,也很简单,Pixel坐标对应过去之后,比较周围的多个点,计算出阴影区的百分比。此外,还可以通过Bilinear Filter让阴影更平滑,跟PCF差不多,比较相邻的4个点,然后根据Texel的Offset做插值。值得注意的是,现在的GPU都支持Hardware Shadow Map,可以直接通过tex2Dproj( u, v, z, p )这条指令实现2x2PCF+BF。
Perspective Shadow Maps & Trapezoidal Shadow Maps
这两种方法沿用Shadow Map的基本思想。为了解决Shadow Map精度问题,对Depth Pass的变换矩阵做做手脚。
Variance Shadow Maps
VSM的想法很棒,可以说Soft Shadow是从VSM开始才往利用硬件Filter的方向发展。首先,也是Light View的Depth Pass,但是这次写入2个值,一个通道写入Depth,另一个通道写入Depth2。接着对出来的这张 [Depth, Depth2] 的Texture做Blur。直观上来讲,做Blur的结果就是取周围一定范围内的点的平均值(期望值),也就是Blur完之后得到一张 [E(Depth), E(Depth2)] 的Texture。有了这张Texture,后面便可以根据方差公式 V(Depth)=E(Depth2)-E(Depth)2 计算得到方差。
最后,根据Chebychev’s inequality(切比雪夫不等式)
P(x≥t) ≤ σ2/(σ2+(t-μ)2) 式中σ2为方差V(Depth),μ为期望E(Depth)
将Pixel坐标变换到Light Space之后的Depth代入t,即得到某个点周围一定范围内大于深度t的点的比率(注意切比雪夫不等式得到的只是一个范围≤,而这里近似的取不等式右侧的结果Pmax)。其实就是一个微分级别的PCF值,于是可以非常高效地得到平滑阴影。
很可惜的是,在Depth变化较大的区域(方差较大),VSM会有缺陷(Light bleeding)。因为根据切比雪夫不等式,在方差较大的情况下,(t-μ)必须足够大,才能让不等式右边趋于0。于是又有了LVSM来解决这个问题。
Layered Variance Shadow Maps
为了解决VSM的Light bleeding,对VSM的Shadow Map进行分层,将每一层次里面的Depth区间控制在一定范围内,也就是降低可能出现的方差值。
Convolution Shadow Maps
CSM提出了一种非常好的图形学算法思路。其推导过程比较华丽,用到了几个数学上的概念:卷积,傅里叶级数。(注意还有另外一种CSM – Cascaded Shadow Maps,两者作用是不一样的)
首先,假设x⊆R3为某一点的世界坐标,d(x)为该点到Light Source的距离(也就是Light Space的Depth)。p⊆R2为该点对应到Shadow Map上的坐标,z(p)为该点对应的Shadow Map上的Depth。于是可以定义一个Shadow Test函数:
s(x) = f(d(x), z(p)) 当d>z时,s为0,反之为1
为了得到软阴影,我们希望能对s进行卷积,即希望得到:
sf(x) = ∑w(q)f(d(x),z(p-q)) = [w*f(d(x), z)](p)(亮点1)
如果能对上式运用卷积定理,得到如下式的结果,则表明可以通过对z(p)做卷积而得到软阴影。(即可以利用Blur、硬件Bilinear Filter和Mipmap来达到软阴影)
[w*f(d(x), z)](p) = f(d(x), [w*z](p))
可惜的是,f(d(x), z(p))并不是线性函数(而是阶跃函数),无法对其运用卷积定理。为此,CSM转而采用数值分析方法,用傅立叶级数来逼近函数f。(亮点2)
虽然CSM的推导过程相当华丽,但是本身缺点太多(比如:内存开销较大,Shadow Map Pass需要输出的数据量太大,带宽内存都吃得太紧),导致其实用价值很低。
Exponential Shadow Maps
明白CSM的原理之后,ESM就显得相当简单了。