GPU Gems 3 · Chapter 8. Summed-Area Variance Shadow Maps

从 PCF 的逐样本比较出发,解释 variance shadow maps 如何用 moments 线性过滤,再用 summed-area tables 支持任意矩形软阴影查询。

学习目标

  • 能解释 PCF、VSM 与 SAVSM 分别过滤什么数据,以及 filter width、纹理分辨率和 setup 成本如何影响方案选择
  • 能修改 Shadow Filtering Lab 的 method、filter width、variance、bleeding reduction 与 shadow resolution,观察阴影边缘和读取成本的变化
  • 能回答:为什么 SAVSM 可以用四角查询任意矩形,又为什么仍然必须检查 moments 的数值精度与 light bleeding

先把“软阴影”拆成两个问题

一块阴影边缘之所以难看,常常不是因为光源不够复杂,而是因为一张有限分辨率的深度图片被放大、缩小或反复采样。你可以逐个问附近的深度,也可以先把附近的深度整理成一份统计摘要,再一次性回答“这一块区域有多大概率被挡住”。

本章解决的问题是:怎样让阴影过滤的范围可以随像素变化,却不让采样次数随软阴影半径一起失控?如果直接平均深度,再做一次比较,平均值可能既不是遮挡物也不是接收面;如果逐样本比较,画面变软的代价会迅速上升。

先决定“过滤什么”:比较结果、深度分布,还是区域和PCF每个 texel 先比较,再平均011011011011soft edge = more samples准确,但 filter width 变贵mipmap 不能跳过比较VSM过滤 depth 与 depth²linear filtering / mipmap一次 lookup 得 moments需处理 light bleedingSAVSMSAT 查询任意矩形区域四角加减filter width 不变查询成本生成 SAT 有 setup 成本

1. PCF 为什么准确却不容易变软

Percentage-closer filtering(PCF)先把当前像素投到 light space,再对滤波区域中的每个 shadow texel 做一次二值深度比较:比 receiver 更近记为 lit,否则记为 shadow,最后平均这些 0/1 结果。扩大区域可以让边缘更柔和,但每扩大一次,就要承担更多比较和纹理读取。

PCF 还继承了 shadow acne 与 bias 的困难:倾斜表面覆盖很宽的深度范围,邻域采样又把需要的 bias 范围扩大。mipmap 只能平均深度,不能跳过每个样本的比较,所以它不能把 PCF 变成一次 lookup。

2. VSM:用 moments 代替不可过滤的比较

VSM 在光源视角渲染时不只写入深度 z,还写入 z²。对某个区域过滤后得到一阶矩 M₁ 和二阶矩 M₂,再计算均值 μ = M₁ 与方差 σ² = M₂ − μ²。因为这两项是可以线性过滤的数值,所以 mipmap、trilinear、anisotropic filtering、MSAA 以及 SAT 都能参与。

关键不是声称“区域里只有一个深度”,而是保存一个足够便宜的分布摘要。线性 depth metric 比投影后的非线性 z 更稳定;variance 还要设置一个很小的 minimum clamp,避免过滤后两个近似相等的大数相减造成数值问题。

VSM 把“不好过滤的比较”改成“可以过滤的分布”shadow texellinear depth zz² 同时写入linear filterM₁ = E[z]M₂ = E[z²]mipmap / blur / SAT都能对 moments 做分布摘要μ = M₁σ² = M₂ − μ²给 Chebyshev一个可计算的上界线性过滤保留的是区域统计量,不是某一个真实遮挡深度;这是 VSM 的近似边界

3. Chebyshev 把分布摘要变成阴影贡献

当 receiver depth t 不大于均值 μ 时,可以直接把它视为 fully lit branch;当 t 大于 μ 时,用单侧 Chebyshev 计算 p_max = σ² / (σ² + (t − μ)²)。这个 p_max 是“被遮挡概率”的上界,渲染时可以用它衰减光照。

上界的好处是不会轻易把本该亮的区域压黑,也解释了 VSM 为什么能缓解传统 shadow bias。代价是上界可能比真实遮挡概率更亮,尤其是同一个 filter region 里混入多个深度层时。

从两个 moment 得到一个可用的遮挡上界depth distributionμtone-tailed Chebyshevpₘₐₓ = σ² / (σ² + (t − μ)²)t ≤ μ → fully lit brancht > μ → use variance to bound occlusionsmall variance + far receiver → darkerpₘₐₓ 是上界,不是任意多遮挡物分布的精确可见率;这正是 light bleeding 的来源
float ChebyshevUpperBound(float2 moments, float t)
{
  float fullyLit = t <= moments.x ? 1.0 : 0.0;
  float variance = moments.y - moments.x * moments.x;
  variance = max(variance, g_MinVariance);
  float delta = t - moments.x;
  float bound = variance / (variance + delta * delta);
  return max(fullyLit, bound);
}
 
float ShadowContribution(float2 lightUv, float distanceToLight)
{
  float2 moments = texShadow.Sample(ShadowSampler, lightUv).xy;
  return ChebyshevUpperBound(moments, distanceToLight);
}

这段伪代码只保留渲染阶段的契约:取 moments、做 variance clamp、得到上界。它没有隐藏两个前提:光源 pass 必须写稳定的 linear depth,且过滤前后的 moments 必须使用兼容的格式。

4. light bleeding:上界的可见代价

当第一接收面已经产生软边,而第二接收面应该完全处于阴影中时,Chebyshev 上界仍可能给第二面一小段非零光照。这个误差不能被一个固定公式对任意 N 个遮挡层完全消除;精确重建最终会退化为更密集的采样。

工程上更实用的方案是对 p_max 做 linstep(Amount, 1, p_max):把低于 Amount 的尾部映射为 0,再把剩余范围重新拉伸。Amount 越高,bleeding 越少,但 penumbra 也会变暗、阴影细节会丢失。

light bleeding:上界把“不该亮”的区域也放亮了raw pₘₐₓfirst receiver · soft penumbrasecond receiver · should be darktail leaks lightlinstep reductionAmount保留细节的前提下砍掉低强度尾部Amount 太高会让 penumbra 变暗、细节丢失;它是艺术与误差之间的旋钮,不是免费真值

5. SAT:任意矩形四角求和

对源纹理建立 SAT 后,矩形区域的总和可以写成 A − B − C + D,再除以区域像素数得到平均值。把 VSM 的两个 moments 分别建立前缀和,就能让每个像素自由选择矩形 filter width,不再靠固定 mip level 近似一个动态范围。

这里有一个容易漏掉的边界:SAT 的坐标约定必须明确 min/max 是否包含,角点查询还要考虑边界外的零值与双线性插值。错误的坐标约定会把阴影边缘整体推开一格,表现为稳定但很难定位的 halo。

SAT 的核心:任意矩形都只查四个角prefix sum gridDBCA高亮区域的 moments suminclusion–exclusionsum = A − B − C + D平均值 = sum / rectangle area四角采样 + 双线性插值SAT 给每像素动态选择 filter width;它不承诺任意形状区域都只需四次查询

6. SAT 的生成与精度账本

GPU 上生成 SAT 可以按 line-by-line 做 running sum,也可以用 recursive doubling 把不同 stride 的前缀部分并行合并。前者更直接但需要约 width + height 次 pass;后者减少串行依赖,但实现复杂度和同步方式要单独评估。

SAT 的查询很便宜,生成与存储却不能被忽略。前缀和会快速消耗浮点 mantissa 的有效位;更大的纹理意味着更多累积项,variance 的差分也更容易放大误差。可选的补救包括元素偏置、origin-centered 布局、把 moments 分散到四个分量、使用整数累积,或在硬件允许时使用更高精度格式。

生成 SAT:串行依赖换成 GPU 可并行的前缀和line-by-line每条线把当前值加到前一个 sum约 width + height 次 passrecursive doubling距离 1、2、4… 的值并行合并stride 1 → partial prefixstride 2 → partial prefixstride 4 → partial prefix更少依赖,复杂度由实现决定SAVSM 的运行时优势来自查询;SAT 的生成仍是 setup 成本,不能从性能账本中删掉

7. 用 filter width 选择 VSM 还是 SAVSM

如果滤波宽度固定,模糊 VSM 往往更简单:分离 blur 和硬件过滤可以把成本控制在可预测范围。如果滤波宽度需要逐像素变化,SAVSM 能用相同数量的角点查询支持任意矩形区域,但要支付 SAT setup、额外存储带宽和精度管理。

SAVSM 也不是 projection warping 的替代品。过滤主要处理 minification 与阴影边缘,无法单独解决 magnification;实际方案可以把 VSM/SAVSM 与 perspective、light-space 或 frustum partitioning 结合。把“过滤”和“投影”分成两个决策,才能在场景变化时解释性能与画质。

SAVSM 的一条可审计管线light passlinear depthVSMz, z² → momentsfilter resourcemipmap / SATrect query四角 / one lookupshadow contributionpₘₐₓ → lightbleeding reductionclamp tail渲染时只做区域统计的恢复;质量边界由 moments、variance clamp 和 reduction 一起决定

猜一猜:如果把 filter width 从 4 调到 28,PCF、VSM、SAVSM 的画面软化和工作量会分别怎样变化?打开 Lab,先只切 method,再分别改变 variance 与 bleeding reduction,观察哪些旋钮影响的是形状,哪些影响的是近似误差。

Shadow filtering Lab

比较三种方法的阴影边缘、采样次数、setup 成本和 light bleeding;数值是教学趋势,不替代真实 GPU profile。

SAVSM · filter width 18 · resolution 512caster edge → penumbra → lit receiveredge band ≈ 14读数texture reads4SAT setup16bleed tail6%softness22variance 28 · bleeding reduction 24降低 bleedAmount 会保留更多细节,但可能留下更亮的尾部

SAVSM 的矩形查询成本稳定,代价转移到 SAT 生成与数值精度管理。

三步验收:从深度记录到矩形软阴影

分步1 / 3

第一步:写出可过滤的深度统计

从光源视角使用稳定的 linear depth,同时写入 z 和 z²。过滤后恢复 M₁、M₂,再做 variance clamp;不要先把深度平均后再做一次二值比较。

先决定“过滤什么”:比较结果、深度分布,还是区域和PCF每个 texel 先比较,再平均011011011011soft edge = more samples准确,但 filter width 变贵mipmap 不能跳过比较VSM过滤 depth 与 depth²linear filtering / mipmap一次 lookup 得 moments需处理 light bleedingSAVSMSAT 查询任意矩形区域四角加减filter width 不变查询成本生成 SAT 有 setup 成本
VSM 把“不好过滤的比较”改成“可以过滤的分布”shadow texellinear depth zz² 同时写入linear filterM₁ = E[z]M₂ = E[z²]mipmap / blur / SAT都能对 moments 做分布摘要μ = M₁σ² = M₂ − μ²给 Chebyshev一个可计算的上界线性过滤保留的是区域统计量,不是某一个真实遮挡深度;这是 VSM 的近似边界

本章小结

  • PCF 过滤比较结果,准确但 filter width 越大,样本成本越高。
  • VSM 保存 moments,让 depth distribution 可以线性过滤并交给 Chebyshev。
  • Chebyshev upper bound 便宜而稳健,但会带来 light bleeding 近似误差。
  • SAT 用四角前缀和支持逐像素矩形滤波,代价转移到生成、带宽与精度。
  • 固定宽度优先考虑模糊 VSM;动态宽度再评估 SAVSM 的 setup 与查询收益。

练习

问题 1|解释 moments。 为什么 VSM 要同时写入 z 与 z²?如果只保存一个过滤后的平均深度,为什么不能得到同样的 Chebyshev 上界?

问题 2|修改 Demo 代码。 给 Shadow Filtering Lab 增加一个 minimumVariance 控件,并把它接入 Chebyshev 计算。你会如何避免近似相等的大数相减后出现闪烁?

问题 3|场景选型。 一个固定 filter width 的平台阴影和一个需要按 blocker distance 动态改变软硬度的软阴影,分别更适合模糊 VSM 还是 SAVSM?请把 setup 成本也写入理由。

名词解释

本章出现的专业名词,用大白话再讲一遍。

variance shadow map
moments
Chebyshev upper bound
summed-area table
light bleeding

资料与写作方式声明

本章以GPU Gems 3 · Chapter 8. Summed-Area Variance Shadow Maps权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…