SZ神庙

从此开始,遁入幻想

2019年9月24日

幻想旅途>

Shadow map总结

这段时间做了一些关于shadow map的工作,这里写一篇文章总结一下。

一. Standard Shadow map(SSM)[1]

最基本的shadow map,即从光源处渲染场景深度到shadow map中,计算shadow时将pixel深度值z与light projection space下的深度值d作比较。即

$$
f(z) = H(d-z) = \begin{cases}
0,&d<z\\
1,&d\geq z
\end{cases}
$$

SSM存在以下几个主要问题:

1.1 Shadow Acne

所谓shadow acne,即完全受光的平面上会出现条纹状的自阴影,如下图所示。

成因见下图:

解决方案通常是对深度值加一个bias。OpenGL和DirectX都已经支持了slope scaled depth bias的功能。其公式为:

$$
bias = units * r + factor * dz
$$

其中r为depth buffer中所能表示的最小的32为浮点数,dz为深度值随屏幕坐标的变化率(即斜率),而units和和factor参数由API指定。GL下为glPolygonOffset函数,DX11下为D3D11_RASTERIZER_DESC1结构下的DepthBias和SlopeScaledDepthBias参数。

1.2 阴影锯齿

由于shadow map是深度值的离散化表达,因此SSM会产生锯齿现象,且无法通过对shadow map进行linear采样来缓解。解决方案是采用下面介绍的各种反走样技术。

二. Percentage Closer Filtering(PCF)[2]

PCF的基本原理是在像素周围区域内多次采样并进行深度值比较,然后对结果进行加权平均,即

$$
f(z) = \sum_i H(d_i-z_i)w_i
$$

可见算出来的阴影值不一定是0或1,还可能是0到1中间的值,因此可实现软阴影效果。事实上所有阴影反走样算法都可以用来实现软阴影。

2.1 硬件PCF

PCF可以由硬件直接提供支持。在OpenGL下,需要在host端将shadow map为depth texture,且GL_TEXTURE_COMPARE_MODE设为GL_COMPARE_R_TO_TEXTURE,并设置好GL_TEXTURE_COMPARE_FUNC。这样,shader中进行采样时,就可以在texture函数中纹理坐标的最后一位传入供比较的深度值:

采样得到的将是比较值(0和1),再打开linear filter就可以得到硬件PCF结果了。

DX上则更简单,DX9直接使用tex2Dproj函数即可,DX11则使用SampleCmp函数。‘

2.2 软件PCF

虽然硬件PCF非常快速,但它通常只会采样2×2=4个pixel,因此通常只能使得阴影边缘有所软化,而难以完全消除锯齿,如图。

为了进一步消除锯齿,需要更多的采样和通常更大的采样范围。因此,实践中常常还是需要在shader中实现PCF算法。采样点的数目、分布和权重通常可以预先选定,然后作为uniform传递给shader。常用的采样可选用泊松圆盘采样,或者《GPU Gems 2》中提到的stratified sampling等[3]。

然而,如果每个pixel都采用一样的预计算的采样pattern的话,还会出现难看的banding现象,即阴影边缘呈现条带状,如图所示。

为了避免banding现象,常见的方法是对每个pixel的采样pattern进行随机旋转。这通常可以通过引入一张噪音贴图来实现。或者也可以使用基于像素坐标的随机函数,比如下面这样:

引入随机旋转可以有效地消除banding现象,但副作用就是会在阴影边缘处引入噪点。减少噪点的最直接方法就是加大采样数量,但这会显著增加渲染开销。《GPU Gems 2》提出通过判断pixel是否在shadow边缘来动态控制采样数量,仅对shadow边缘处的pixel采用大数量采样[3]。但由于该方法需要对pixel shader进行分支计算,因此实际上效果有限。

另一种消除噪点的手段是所谓的screen space shadow,通过将shadow渲染到一张shadow buffer上,然后进行高斯模糊来消除噪点。该方法比较适用于延迟渲染,不太适用于forward rendering。

2.3 PCF的优劣

优点

  • 实现简单
  • 硬件直接提供支持
  • 很容易与PCSS等动态半影算法相结合

缺点

  • 边缘有噪点
  • 采样数增多时,开销较大

三. Variance Shadow Map(VSM)[4]

VSM由Donnelly和Lauritzen于2006年提出[4],它用一种基于统计的方法来计算shadow。核心原理是,将shadow map看成深度的分布\(F\),Donnelly和Lauritzen证明了当shadow receiver为平面时,shadow的值等于\(F\)的切比雪夫不等式的上界

$$
f(z) = \frac{\sigma^2}{\sigma^2+(z-d)^2}
$$

式中\(\sigma^2\)为点\(z\)处shadow map深度值的方差。它可以由如下方式计算得到

$$
\sigma^2 = E(d^2)-E^2(d)
$$

式中\(E\)为数学期望,可以认为就是从shadow map中采样取得的值。可见,VSM需要RG两个通道,分别保存深度和深度的平方,并且需要使用浮点数。因此,典型的VSM常采用RG32f纹理格式。

和PCF相比,VSM的一个很大的优势是它可以对shadow map进行硬件filter,还可以对shadow map进行blur等预处理来得到软阴影的效果。此外,它也不需要进行多次采样,不会有噪点问题,也省下了采样的开销。但如果要对shadow map进行blur预处理的话,则会引入额外开销,因此综合来看VSM和PCF的效率对比并不能确定。通常当shadow map分辨率较大时,blur开销变大,PCF会比较有优势。当shadow map分辨率较小时,VSM比较有优势。

3.1 光渗现象(Light Bleeding)

虽然VSM比起PCF有多种优点,但它有一个很大的问题是所谓的光渗现象。也就是当两个shadow caster的阴影出现重叠时,在阴影的交界处会出现漏光,如下图所示。图片来自[5]。下图中树叶和长凳的阴影交界处发生了明显的漏光。

20190924105641imagepng

光渗现象的成因可以解释如下。假设重叠的两个shadow caster为A和B,shadow receiver为C,他们的深度分别为a,b,c,如下图所示。图片同样来自[5]。

20190924110122imagepng

那么我们可以知道在交界处有

$$
E(z) = \frac{a+b}{2} \\
E(z^2) = \frac{a^2+b^2}{2}
$$

从而

$$
d = \frac{a+b}{2} \\
\sigma^2 = \frac{(a-b)^2}{4}
$$

记\(\Delta x = b – a, \Delta y = c – b\),可以推出

$$
f(z) = \frac{\sigma^2}{\sigma^2+(c-d)^2} = \frac{(\frac{\Delta x}{\Delta y})^2}
{2(\frac{\Delta x}{\Delta y})^2 + 4\frac{\Delta x}{\Delta y} + 4}
$$

可见,当\(\frac{\Delta x}{\Delta y}\)接近0时,阴影值接近0,但\(\frac{\Delta x}{\Delta y}\)不接近0时阴影值就不为0了,就会出现漏光。极端情况下,\(\frac{\Delta x}{\Delta y}\)为无限大,此时阴影值为0.5。漏光随着\(\frac{\Delta x}{\Delta y}\)的增大而变得更严重, 但不会超过0.5。

消除漏光的一种办法是设定一个最小阴影值\(m\),将\([m,1]\)区间重新映射到\([0,1]\)。如下所示:

将最小值设为0.5理论上就可以消除所有的光渗现象,但这种方法带来的副作用是会导致阴影变硬,使得半影区域表现效果变差。因此实际上该参数需要根据场景进行调整。

另一种消除光渗的方法是使用EVSM,我们在下面的章节再进行介绍。

3.2 数值稳定性

前面已经介绍了VSM需要使用两个32位的浮点通道来保存深度和深度的平方,也就是RG32f格式。如果使用16位的浮点格式,会发生数值不稳定的问题[6]。这是由于计算方差时需要对两个非常接近的数做减法,因此方差的计算是数值不稳定的。另外,当shadow caster和shadow receiver距离非常近的时候,\(z-d\)也是不稳定的,因此也会发生漏光现象(与上面的光渗现象不同)。

通常的解决办法就是使用32位浮点纹理,然而在本文写作之时(2019年9月),移动设备尚不能广泛支持32位浮点纹理(准确地说,是不能支持32位浮点framebuffer)。经测试,甚至在搭载了A11的iPhone8上也不能支持。因此,VSM并不适用于移动端。

3.3 其他注意事项

和PCF不同,VSM要求shadow receiver必须同时也是shadow caster(shadow caster不必是shadow receiver)。否则,将产生错误的结果。

四.Exponential Shadow Map(ESM)[7][8]

ESM最早由Salvi在《Shader X6》中提出[7],起初的推导是基于Markov不等式的,随后由Annen等人在不使用Markov不等式的情况下进行了重新推导[8]。

设常数\(c>0\),ESM在shadow map中保存\(F=e^{cd}\),而阴影计算如下

$$
f(z) = e^{-cz}* F
$$

当\(c\)趋向于无穷大时,\(f(z)\)将迅速退化为阶跃函数\(H(d-z)\),因此要用ESM计算shadow,则\(c\)值越大越好。更进一步地,对\(F\)做filter相当于对\(f(z)\)做filter,因此ESM和VSM一样可以利用硬件filter,也可以对shadow map做blur的预处理来得到软阴影。

和VSM相比,ESM只需占用一个通道,因此带宽可以减半。然而,ESM有一个严重的问题:要使得计算更精确,则\(c\)值越大越好。否则,若\(c\)值不够大,则当shadow caster和shadow receiver之间距离较小时会产生漏光问题。不幸的是,\(c\)值增大会使得\(F\)指数增大,即使是32位的浮点数目前最多也只能支持80左右的\(c\)值,而这远远不足以消除漏光。更不用提移动设备只能支持16位浮点纹理了。

龚大在他的博客中提出了一种改进方法,通过在预blur阶段的log space filtering来解决浮点数溢出问题,并声称通过该方法可以在16位浮点数上支持300的\(c\)值。然而,经过我的实验,该方法对于深度变化小的区域有效,而深度变化大的区域仍然会发生溢出问题。不幸的是,阴影边缘的半影区域基本上都属于深度变化大的区域,因此该方法对于阴影边缘仍然会产生明显的artifact。

目前综合来看,ESM的浮点数溢出问题仍然没有什么很好的解法,这使得该技术很难实用。

五.Exponential Variance Shadow Map(EVSM)

虽然ESM在实践中被认为难以实用,但ESM的提出仍然启发了人们。Lauritzen在此之后提出了将ESM和VSM结合的EVSM[5],EVSM可以非常好地消除VSM的光渗问题,从而产生非常高质量的阴影。

Lauritzen注意到,shadow计算的本质是比较\(z\)和\(d\)。但事实上我们可以不用直接比较\(z\)和\(d\)的值,而是套一个warp function \(g\),比较\(g(z)\)和\(g(d)\),也能得到一样的结果。在此情况下,仍然可以通过计算切比雪夫不等式上限来得到阴影的值。

warp function可以任选,而EVSM选取的warp function正是ESM中采用的\(e^{cd}\)。因此,EVSM的步骤和VSM非常类似,区别在于现在shadow map中存放\(e^{cd}\)和\(e^{2cd}\)而不是\(d\)和\(d^2\),以及在计算切比雪夫不等式上限时使用\(e^{cz}\)而不是\(z\)。

EVSM仍然可以采用VSM中提到的remapping技术来消除光渗,这样它就有了双重手段,有两个参数可以调节:\(c\)和最小阴影值\(m\)。通过适当调节参数可以得到非常高质量的阴影。

综合来看,EVSM在以上介绍的这些阴影技术中综合表现是最优秀的。

总结

本文介绍了一些shadow map的形态。这些技术聚焦于两个问题:抗锯齿和软阴影。shadow map还存在一些其他的需要解决的问题,例如方向光的shadow map是一个正交矩阵,如何确定它的投影范围以达到最高效shadow map利用率,这衍生出了lispSM、CSM等技术等等。本文并没有介绍这方面的技术,后续如果有空的话再考虑吧。

另外,本文介绍的这些shadow map技术实际上也都不完美,有各自的优劣和适用场景。经过这么多年的发展,我总体上感觉shadow map已经走到了一个瓶颈,近年来也少有作出重大突破的paper问世。而Epic Games 在Siggraph 2015上提出了基于有向距离场的阴影计算技术[9],这是一种完全不同于shadow map的全新的阴影计算技术,相关研究近年来也比较活跃,不知道有没有可能取代shadow map成为新一代阴影技术呢?

参考文献

[1]Williams, Lance. "Casting curved shadows on curved surfaces." ACM Siggraph Computer Graphics. Vol. 12. No. 3. ACM, 1978.

[2]Reeves, William T., David H. Salesin, and Robert L. Cook. "Rendering antialiased shadows with depth maps." ACM Siggraph Computer Graphics. Vol. 21. No. 4. ACM, 1987.

[3]Uralsky, Yury. "Efficient soft-edged shadows using pixel shader branching." GPU Gems 2 (2005): 269-282.

[4]Donnelly, William, and Andrew Lauritzen. "Variance shadow maps." Proceedings of the 2006 symposium on Interactive 3D graphics and games. ACM, 2006.

[5]Lauritzen, Andrew Timothy. Rendering antialiased shadows using warped variance shadow maps. MS thesis. University of Waterloo, 2008.

[6]Lauritzen, Andrew, and Michael Mccool. "Summed-area variance shadow maps." GPU Gems. 2007.

[7]Salvi, Marco. "Rendering filtered shadows with exponential shadow maps." ShaderX 6 (2008): 257-274.

[8]Annen, Thomas, et al. "Exponential shadow maps." Graphics Interface. ACM Press, 2008.

[9]Wright, Daniel. "Dynamic occlusion with signed distance fields." ACM SIGGRAPH. 2015.