Shadow Mapping 算法总结
本文将总结常用 Shadow Mapping 算法的流程和公式。
记光源位置为 \(\boldsymbol x\)。对每个着色点 \(\boldsymbol p\),记其与光源的距离为 \(z_{\boldsymbol p} = ||\boldsymbol x - s(\boldsymbol p)||\),定义
- 其在 shadow map 中的对应像素点为 \(s(\boldsymbol p)\),其深度(与光源的距离)为 \(z_{s(\boldsymbol p)} = ||\boldsymbol x - s(\boldsymbol p)||\)
- 函数
\[ \chi^+(\alpha) = (\alpha \geq 0) = \begin{cases} 1, \alpha \geq 0, \\ 0, \text{otherwise}. \end{cases} \]
朴素 Shadow Mapping
着色点 \(\boldsymbol p\) 的可见性为
\[ V(\boldsymbol p) = \chi^+(z_{s(\boldsymbol p)} - z_{\boldsymbol p}). \]
Bias 改进
设定一个很小的 \(\varepsilon > 0\),缓解因 shadow map 分辨率不足引起的自遮挡而引发的走样
\[ V(\boldsymbol p) = \chi^+(z_{s(\boldsymbol p)} + \varepsilon - z_{\boldsymbol p}). \]
\(\varepsilon\) 可以是一个常量,但从几何的角度分析,它最好应该与相机与模型表面的夹角有关系。但由于这样会引入额外计算量,所以一般都设为常量。
bias 调高会让该出现阴影的地方的阴影消失,所以要注意调参。
PCF 公式都可以做 bias 改进,但没有具体写出来。
PCF (Percentage Closer Filtering)
考虑在着色点 \(\boldsymbol p\) 的邻域,或者计算量稍小的 \(s(\boldsymbol p)\) 的邻域处做平均操作,即使用一个卷积核。在 shadow map 中,定义以 \(s(\boldsymbol p)\) 为中心的宽度为 \(d\) 的邻域为 \(\mathcal N(s(\boldsymbol p),d)\),那么 $p $ 的可见性变为
\[ V(\boldsymbol p) = \sum_{\boldsymbol q \in \mathcal N(\boldsymbol s(\boldsymbol p), d)} w(\boldsymbol p, \boldsymbol q) \cdot \chi^+(z_{\boldsymbol q} - z_{\boldsymbol p}). \]
其中:
- \(w\) 为某个权重函数,可以是均匀权重(常量),也可以是中心权重高,四周权重低,等等。
- \(d\) 为一正整数,为 PCF 卷积核宽度参数,一般不需要太大,否则计算量大。\(d\) 设定较大会得到更软的阴影。
PCSS (Percentage Closer Soft Shadows)
真实世界的光源具有宽度,因此会产生半影区域和软阴影。PCSS 改进 PCF 的阴影软硬程度均匀的问题,在半影较宽的区域生成软阴影,半影较窄的区域生成硬阴影。
如果 \(\boldsymbol p\) 位于阴影边缘(可以通过前一帧的阴影结果加速判断),定义:
- 遮挡物(blocker)到光源的平均距离为 \(z_b\)
- 光源的宽度为 \(w_l\)
那么,由相似三角形关系,半影(penumbra)的宽度为
\[ w_p = \frac{z_{\boldsymbol p} - z_b}{z_b} \cdot w_l. \]
据此动态设计 PCF 的卷积核宽度参数正比于 \(w_p\)
\[ d = k w_p \]
即可在半影区域大的地方出现软阴影,半影区域小的地方出现硬阴影,达到更真实的阴影观感。
Blocker search:如何获取 \(z_b\)
在 shadow map 上采样 \(\boldsymbol p\) 对应像素点的附近一定区域的像素,若该像素的深度小于 \(z_{\boldsymbol p}\),即为遮挡物,累加其深度,最后取平均,即可得到 \(z_b\)。
实现上,这个搜索遮挡物区域的范围可以是恒定的,也可以使用启发法(heuristics),根据光源的尺寸和着色点与光源之间的距离确定搜索遮挡物区域的范围。
但无论如何,都需要一定的计算量,该计算量可能与 PCF 卷积过程相当。
VSM/VSSM (Variance Soft Shadow Mapping)
VSM 和 VSSM 是同一项技术。它在生成 shadow map pass 时做一次预计算,进而可以在 shading pass 直接得到软阴影,或者在 PCSS 的 shading pass 中,优化获取 \(z_b\) 和 PCF 卷积过程的计算量。
预计算矩统计量
对于 shadow map 的每个像素 \(s(\boldsymbol p)\),记宽为 \(d\) 的邻域 \(\mathcal N(s(\boldsymbol p),d)\) 内的平均深度为 \(E(z_{s(\boldsymbol p)}, d)\),方差为 \(D(z_{s(\boldsymbol p)}, d)\),满足
\[ D(z_{s(\boldsymbol p)}, d) = E(z_{s(\boldsymbol p)}^2, d) - E^2(z_{s(\boldsymbol p)}, d), \]
于是,我们可以在 shadow map 中预计算并额外存储每个像素附近的 \(\mu = E(z_{s(\boldsymbol p)}, d)\) 和 \(\sigma^2 = D(z_{s(\boldsymbol p)}, d)\)。
在实现上,可以预预计算 \(z_{s(\boldsymbol p)}\) 和 \(z_{s(\boldsymbol p)}^2\) 的 SAT,加快每个像素的 \(E(z_{s(\boldsymbol p)}, d)\) 和 \(D(z_{s(\boldsymbol p)}, d)\) 的预计算。当然,在 shader 里面做这件事情需要注意同步。
切比雪夫不等式
根据切比雪夫不等式,有
\[ P(z_{s(\boldsymbol p)} \geq z_{\boldsymbol p}) \leq \frac{\sigma^2}{\sigma^2 + (z_{\boldsymbol p} - \mu)^2}. \]
即,着色点 \(\boldsymbol p\) 附近区域内,未被遮挡的像素的比例至多是不等号右边的量。
或
\[ P(z_{s(\boldsymbol p)} \leq z_{\boldsymbol p}) \geq 1 - \frac{\sigma^2}{\sigma^2 + (z_{\boldsymbol p} - \mu)^2}. \]
即,着色点 \(\boldsymbol p\) 附近区域内,被遮挡的像素的比例至少是不等号右边的量。
VSM 直接近似上两式等号成立。
计算可见性
注意到 PCF 的结果正好可以解释为“着色点 \(\boldsymbol p\) 附近区域内,未被遮挡的像素的比例”,于是
\[ V(\boldsymbol p) = P(z_{s(\boldsymbol p)} \geq z_{\boldsymbol p}) = \frac{\sigma^2}{\sigma^2 + (z_{\boldsymbol p} - \mu)^2}. \]
我们的阴影就这么算完了,不再需要经过麻烦的 blocker search、卷积等过程。
加速 Blocker search
VSM 还可以加速 \(z_b\) 的计算。
我们认为每个着色点 \(\boldsymbol p\) 要么被遮挡,要么不被遮挡。那么,shadow map 的每个像素深度的平均值,就可以由其附近每个被遮挡的像素的深度平均,和每个不被遮挡的像素的深度平均的加权平均得到,即
\[ E(z_{s(\boldsymbol p)}, d) = P(z_{s(\boldsymbol p)} \leq z_{\boldsymbol p}) \cdot z_b + P(z_{s(\boldsymbol p)} \geq z_{\boldsymbol p}) \cdot z_{unooc} \]
其中 \(z_b\) 为遮挡物到光源的平均距离,\(z_{unooc}\) 为着色点 \(\boldsymbol p\) 附近的未被遮挡的像素到光源的平均距离。
我们近似 \(z_{unooc} = z_{\boldsymbol p}\) 总是成立(继承了 PCF 的同款近似)。于是
\[ z_b = \frac{E(z_{s(\boldsymbol p)}, d) - P(z_{s(\boldsymbol p)} \geq z_{\boldsymbol p}) \cdot z_{\boldsymbol p}}{P(z_{s(\boldsymbol p)} \leq z_{\boldsymbol p})}. \]
即可用来估计 \(z_b\) 的值。
用 VSM 完整加速 PCSS
如果直接指定一个常量 \(d\),那 VSM 确实能得到软阴影,但与 PCSS 的流程很不一样,因此只会得到一直软的阴影。如果用 VSM 加速 PCSS 的流程,就会是这样的:
shadow map pass:
- 预计算 \(z_{s(\boldsymbol p)}\) 和 \(z_{s(\boldsymbol p)}^2\) 的 SAT
- 将 \(z_{s(\boldsymbol p)}\) 存入每个 shadow map 像素 \(s(\boldsymbol p)\)
shading pass:
- 用 VSM 加速 \(z_b\) 的计算
- 计算半影宽度 \(w_p = \frac{z_{\boldsymbol p} - z_b}{z_b} \cdot w_l\)
- 用 VSM 加速 PCF,此时根据 \(d = k w_p\) 计算矩统计量,并用切比雪夫不等式的近似计算 \(V(\boldsymbol p)\)。由于我们有 SAT,所以 \(E(z_{s(\boldsymbol p)}, d)\) 和 \(D(z_{s(\boldsymbol p)}, d)\) 的计算是常量时间复杂度的。
Moment Shadow Mapping
VSM 之后,有一些成果通过计算更多,更高阶的矩统计量或其变体,实现了更精确的 CDF 求值 \(P(z_{s(\boldsymbol p)} \leq z_{\boldsymbol p})\) 的估计,这些方法可总称为 Moment Shadow Mapping。当然,它们的计算量会更大一些。
下面是一些 Moment Shadow Mapping 方法在 shadow map pass 会预计算的 SAT 量,可称之为 depth vector。
- VSM/VSSM:\(z, z^2\)
- Exponential Shadow Mapping (ESM):\(\mathrm e^{cz}\),其中 \(c\) 为衰减参数
- Exponential Variance Shadow Mapping (EVSM):\(\mathrm e^{cz}, \mathrm e^{2cz}\)
- (vanilla) Moment Shadow Mapping: \(z, z^2, z^3, z^4\)