GPU Gems 1 · Chapter 11. Shadow Map Antialiasing

用 percentage-closer filtering 平均多个 shadow compare,缓解 shadow map 放大造成的锯齿,并在 16 与 4 次采样之间取得质量与性能平衡。

学习目标

  • 能解释 shadow map 投影到表面后为什么会产生放大锯齿,以及为什么单纯提高分辨率并不总能解决问题
  • 能用 percentage-closer filtering 表达“比较多个深度结果,再平均遮挡比例”的含义
  • 能修改实验中的样本数、footprint、放大程度与抖动开关,预测阴影边界和采样成本的变化
  • 能实现 16 样本与 4 样本 PCF,并说明固定 footprint、硬件 PCF 和空间变化采样模式之间的关系

直觉引入:阴影边界不是一条线,而是一片被覆盖的区域

把 shadow map 想成光源拍下的一张深度照片。接收点投影到照片上,比较照片里最近的遮挡物深度和接收点自己的光源深度:如果接收点更远,它就在阴影里。单次比较只能回答 0 或 1。

问题在于,照片上的一个 texel 可能覆盖屏幕上的许多像素。此时一个 0 或 1 会被放大成阶梯边界。真实的软边并不是把深度照片涂模糊,而是询问一片 footprint 里的多个样本,有多少个比较结果认为接收点被遮挡。这个比例就是更有用的阴影值。

1. shadow map 的放大锯齿从哪里来

的基本流程是:先把场景从光源视角渲染到深度纹理,再在正常相机的片元 shader 中把当前点送入 light space。比较接收点深度与 shadow map 深度,得到遮挡结果。

不是简单的“纹理太小”。投影比例由相机、光源方向、表面倾角和深度范围共同决定。尤其当光线几乎平行于受影表面时,邻近屏幕像素映射到 shadow map 的位置变化很小,一个 texel 会对应很大的屏幕区域。

shadow map 放大时,硬边会变成锯齿1 compare / pixel一个深度答案:0 或 1magnified shadow boundaryPCF / multiple compares统计窗口内的遮挡比例0% → 100% 的平滑过渡average
先认识问题:shadow map 被投影到表面后,如果一个 texel 覆盖了很多屏幕像素,单次比较就会把边界放大成锯齿。

设接收点的光源深度为 zrz_r,第 i 个 shadow sample 的深度为 ziz_i。单次硬比较可以写成:

ci=1ifzrisbehindzi,otherwise0c_i = 1 if z_r is behind z_i, otherwise 0

实际 shader 会使用 bias、硬件比较器和投影坐标处理阴影 acne 等工程细节。本章的关键转折是:不要让一个 c_i 代表整块屏幕像素,而是让一组 c_i 代表一个采样区域。

2. percentage-closer filtering:比较后再平均

(PCF)输出的不是平均深度,而是平均“更靠近光源的样本比例”或其阴影互补值。用 n 个样本表示时:

s=(c1+c2+...+cn)/ns = (c_1 + c_2 + ... + c_n) / n

其中 s 是遮挡比例。若 16 个比较中有 6 个认为接收点被挡住,阴影比例就是 6/16;材质可以用它混合亮部和暗部,而不是在 0 与 1 之间突然跳变。

需要和目标边缘宽度匹配。章节采用一个固定的 4×4 texel 区域,区域故意不对齐 texel 边界;这样既能覆盖足够大的范围,又不需要把代表屏幕像素的四边形显式变换到 shadow map 空间。

把“一个答案”换成 shadow-map footprint1 sample比较 depth,再求平均中心 texel成本低,边界硬4 samples比较 depth,再求平均4 个位置抖动取样,成本低4 × 4 = 16比较 depth,再求平均offset: ±1.5, ±0.5固定区域,边界稳
PCF 平均的是比较结果,不是先把深度纹理预过滤;固定 4×4 区域足够抑制走样,又避免随机微多边形采样。

要特别区分两个旋钮:

概念含义变大后的代价
footprint在 shadow map 上覆盖多大的区域边界更软,但可能混入不该混合的遮挡层
samples区域内实际做多少次 compare结果更稳定,但片元 fetch 与比较更多
shadow resolution深度纹理的 texel 数量占用更多显存,不能消除极端投影放大

3. 16 样本固定区域:先建立质量基线

是最容易验证的版本。对于一张宽度为 W、高度为 H 的 shadow map,texel scale 是 1/W 与 1/H;偏移取 -1.5、-0.5、0.5、1.5,覆盖相邻但不以 texel 边界对齐的 4×4 区域。

下面的伪代码保留算法结构,省略具体图形 API 的 compare sampler 细节:

float pcf16(sampler2DShadow shadowMap,
            vec4 shadowCoord,
            vec2 texelScale) {
  float result = 0.0;
  for (float y = -1.5; y <= 1.5; y += 1.0) {
    for (float x = -1.5; x <= 1.5; x += 1.0) {
      vec2 offset = vec2(x, y) * texelScale * shadowCoord.w;
      result += textureProj(shadowMap,
                            vec4(shadowCoord.xy + offset,
                                 shadowCoord.z, shadowCoord.w));
    }
  }
  return result / 16.0;
}

这里的 sampler2DShadow 假定硬件会先比较深度再返回比较结果;如果平台不支持比较采样器,就需要手写采样深度、比较和平均。无论实现路径如何,先 compare、后 average 是不可替换的语义。

可以把一次采样变成四次深度 compare,再利用坐标的小数部分插值阴影值。单个 texel-size 区域通常不够平滑,但 shader 可以组合硬件 PCF 结果扩大到 4×4 footprint。硬件不是魔法:它仍然受到比较次数、缓存和纹理投影的约束。

4. 用四样本覆盖同一片区域

不是把 4×4 footprint 缩成 2×2。它保持大区域,只改变本像素使用其中哪四个点。相邻屏幕位置使用不同的四点组合后,黑白的单次结果在空间上被分散,最终看起来像连续灰度。

一个确定性的模式可以使用屏幕位置的分数部分选择偏移。下面只展示一个教学版的四点布局:

vec2 cell = floor(fract(screenPosition.xy * 0.5) * 2.0);
float x = cell.y > 0.5 ? 0.5 : -1.5;
float y = cell.x > 0.5 ? 0.5 : -1.5;
 
float result = 0.0;
result += shadowCompare(shadowCoord, vec2(x, y));
result += shadowCompare(shadowCoord, vec2(x + 2.0, y));
result += shadowCompare(shadowCoord, vec2(x, y + 2.0));
result += shadowCompare(shadowCoord, vec2(x + 2.0, y + 2.0));
return result * 0.25;

工程实现需要把 cell 映射成有效的偏移模式,并注意投影坐标的 w、texel scale 和 shadow bias。抖动的目标是让样本分布随屏幕位置变化,而不是引入不可复现的随机数;确定性模式更容易调试,也更容易比较帧间稳定性。

固定 footprint,减少每像素的 compare 次数16 compares覆盖完整 4×4稳定、较贵4 compares每像素选 4 个抖动、便宜hardware PCF再由 shader 扩大区域硬件先做 4 compares目标:边界更平滑,帧时间仍可控
优化不是把采样窗口缩小,而是在相同 4×4 footprint 中改变每个像素取哪四个点;空间变化的模式把黑白结果抖动成平滑灰度。

在 16 samples 与 4 samples 之间做选择时,先比较同一 footprint 下的边界误差,再检查片元成本。四样本通常更快,但在非常高对比度的细节上可能有更多抖动;十六样本更稳定,却不应成为所有灯光和所有像素的默认代价。

5. 为什么固定区域能在放大时工作

是固定 footprint 方案的核心直觉。shadow map 被放大时,相邻屏幕像素映射到纹理上的位置距离更小;虽然每个像素仍使用同样大小的采样区域,但两个区域会大量重叠。

放大越强,相邻像素的 footprint 越重叠screen pixelsP₀P₁projectshadow-map spaceR₀R₁R₀ 与 R₁ 大量重叠 → shadow value 小步变化无需把每个屏幕像素的四边形显式变换到 light space
固定大小的采样窗口听起来粗糙,但放大越强,相邻像素的窗口越重叠;这正是边界平滑而不产生带状断层的原因。

设相邻屏幕像素分别得到 s0 和 s1。如果它们的 sample footprint 只移动很小距离,两个区域共享大部分比较结果,s0 到 s1 的变化也会很小。于是放大并不会制造新的跳变,反而让边界的 shadow coefficient 更连续。硬件使用有限精度表示这个比例时,仍然需要注意高对比度边界和输出格式的量化。

这也解释了为什么直接提高分辨率不是完整答案:分辨率试图让 texel 变小,PCF 则让一个像素拥有一个区域统计。两者可以一起用,但解决的是不同层次的问题。

6. 用实验把质量、成本和稳定性分开

猜一猜:先把 sampleMode 切到 16,再逐渐提高 shadow map 放大程度;然后切到 4 samples 并启用抖动。你应该看到 footprint 仍然覆盖边界,但每像素 compare 数量下降。最后把 footprint 调小,观察边界变硬,而不是把深度值算错。

Shadow Map Antialiasing Lab

先猜:把 footprint 固定住,为什么放大越强反而越容易平滑?

可交互
screen-space shadow preview4 compares · soft edgelightcastershadow coefficientedgeshadow map footprint4 fetches / pixel

观察:16 samples 最稳但最贵;4 samples 保留较大的 footprint,再用随位置变化的模式降低成本。

实验中的 magnification 改变投影放大的程度,radius 改变采样窗口的覆盖范围,1/4/16 三档改变实际比较次数。dither 是确定性的屏幕位置模式,showSamples 用来把抽象的 compare 变成可检查的采样点。把这几个控制分开,才能判断问题来自投影、过滤范围还是样本预算。

小结:PCF 平均的是遮挡答案

  • shadow map aliasing 来自投影放大;单次 depth compare 只能给出跳变的 0/1。
  • PCF 对多个深度比较结果求平均,得到一个 footprint 内的遮挡比例。
  • 固定 4×4 footprint 可以避开复杂的屏幕像素四边形变换,并在放大时利用相邻区域重叠。
  • 16-sample PCF 是质量基线;dithered 4-sample PCF 保留大 footprint、减少每像素比较。
  • hardware PCF 可以先在硬件中完成比较,shader 仍需管理更大的 footprint、投影坐标和 bias。

练习

问题 1|改 Demo 代码 把实验分别切换到 1、4、16 samples,在相同的 footprint 和 magnification 下记录边界观感与每像素 fetch 数。解释为什么 4 samples 仍然能比 1 sample 更平滑。

问题 2|推导题 一个 4-sample PCF 的比较结果为 1, 1, 0, 1,其中 1 表示接收点被遮挡。阴影比例是多少?如果只使用第一个样本,结果会丢失什么信息?

问题 3|独立实现题 实现一个固定 4×4 footprint 的 dithered 4-sample PCF。除了平均帧时间,你会记录哪些指标来判断它是否真的接近 16 samples?

名词解释

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

shadow map
从光源视角生成的深度纹理,用接收点的 light-space 深度比较遮挡。
shadow map aliasing
shadow texel 被投影放大到多个屏幕像素后产生的锯齿、跳变或闪烁。
percentage-closer filtering
先逐样本比较深度,再平均比较结果以估计遮挡比例的过滤方法。
sampling footprint
屏幕像素对应到 shadow map 上的一片采样覆盖区域。
brute-force 16-sample PCF
对固定 4×4 区域的十六个偏移点逐一比较并平均的 PCF 基线。
hardware PCF
由硬件完成多次 shadow depth compare 并插值的比较采样路径。
dithered 4-sample PCF
保持大 footprint、随屏幕位置选择不同四点的低成本 PCF 近似。
sample-region overlap
相邻屏幕像素的 PCF 采样区域彼此重叠的关系。

资料与写作方式声明

本章以GPU Gems 1 · Chapter 11. Shadow Map Antialiasing公开完整正文核定章节范围、事实坐标与时代语境,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

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

讨论

评论区加载中…