GPU Gems 1 · Chapter 14. Perspective Shadow Maps: Care and Feeding
在 post-projective space 中重新分配 shadow-map texel,配合 light-camera clipping、位置相关 bias、PCF 与 color-mask blur 缓解透视阴影图的 aliasing。
学习目标
- 能解释标准 shadow map 的 screen-space aliasing,以及 PSM 为什么把深度图放到 post-projective space
- 能识别 virtual camera、light-camera 视野、near/far 分布和 constant bias 带来的质量问题
- 能根据 receiver bounding volumes 选择 unit cube clipping 或 cube-map 方案,并理解 displaced center 的纹理坐标
- 能实现位置相关的 world-space bias、PCF 采样和只对 color shadow mask 做 blur 的质量补丁
标准 shadow map 的痛点不是“有没有一张深度图”,而是同一个 texel 在屏幕上覆盖了多大的区域。远处物体可能拥有过多 texel,近处物体却让一个 texel 覆盖很多像素,于是阴影边缘出现 stair-stepping 和 shimmering。GPU Gems 1 第 14 章从 PSM 出发,把资源分配问题放到 post-projective space,再讨论它并不自动解决的 camera、bias 和 filtering 问题。
为什么 shadow map 会 alias
↡把场景变换到透视投影之后、深度和坐标已被重新分布的空间 是 PSM 的关键工作域。普通阴影图在光源空间里投影,再把结果投回相机画面;这种双重投影会让靠近相机的屏幕像素在阴影图里变得稀疏。PSM 先用相机的投影把近处对象放大,再在这个空间里构造 shadow map,希望把更多 texel 给真正占据屏幕面积的区域。
↡在 post-projective space 中生成、用相机投影重新分配 texel 密度的阴影图方法 可以看作一次资源重排,而不是一种新的遮挡判定。它仍然渲染深度、再把接收点投到光源视角做比较;变化发生在 shadow-map 的坐标空间和光源 camera 的选择。
阴影体积是在 object space 构造遮挡多边形,几何精确但 fill rate 昂贵,而且不适合没有常规多边形结构的 alpha-test 或 displacement 几何。shadow map 是 image space 方法:只要对象能被渲染就能产生阴影,但必须正视 aliasing。PSM 选择后者的速度,并把 aliasing 当成投影分配问题处理。
1. post-projective space 与 virtual camera
原始 PSM 在特定相机和光源关系下效果很好,但有三个需要先暴露出来的问题:
- 当光源在相机后方时,潜在 caster 可能位于真实视锥之外;把相机虚拟地向后滑,虽能覆盖它们,却牺牲有效分辨率。
- 阴影质量依赖 light 在 camera space 的位置。对着相机的光方向更容易让整个 unit cube 挤进很大的视场,近处 texel 变得不够。
- PSM 的 z 值和 texel 面积分布不均匀,统一 constant bias 不再适用;近处应小、远处应大的误差尺度不能用一个常数概括。
↡为覆盖真实相机后方的潜在遮挡物而把视点向后滑动的替代相机 会把问题从“漏掉 caster”变成“纹理空间被虚拟平移吞掉”。更糟的是,如果某个对象刚好成为或不再成为潜在 caster,所需的 slideback distance 会突然改变,阴影质量就会跳变。
一个替代方向是构造特殊的 inverse projection:让 post-projective ray 穿过负 near 与正 far 的范围,以匹配“穿过 infinity 仍继续”的几何关系。代价是 z 值跨度更大,需要更谨慎的 depth precision;在合理场景中,章节指出 24-bit fixed-point depth 可能足够,但应通过目标场景验证。
2. light camera:把 texel 放在 receiver 上
↡根据接收者和 caster 的粗略包围体收窄 light camera 视野、避免把整个 unit cube 塞进纹理的优化 利用一个事实:shadow map 只需要覆盖真实对象,而不是覆盖 post-projective unit cube 的每一处空白。可以在 view-camera space 中收集 receiver 的 bounding volumes,再把它们变换到 post-projective space,最后选择能包住这些点的 light camera。
实用算法可以拆成四步:
- 收集当前帧真正可见的 receiver,以及可能投射到它们上的 caster bounding volumes。
- 把这些包围体的顶点变换到 post-projective space,得到需要被 light camera 看见的点集。
- 用包含点集的最小锥近似 light camera 的最佳方向;位置、up vector 和投影参数由光源与 view matrix 约束。
- 用足够覆盖 caster 的 near/far 范围渲染 shadow map,但不要让 far-away 空间无谓地扩大接收者的视场。
粗略包围体通常已经有收益:户外场景可以包住地形上的对象高度,室内场景可以用当前房间的体积。目标是给出“关注哪一块空间”,而不是为每个三角形支付一次 CPU 几何分析。
当光源位置靠近 unit cube 或几乎正对相机时,单张纹理可能仍难以容纳整个区域。更一般的做法是使用 cube map:对位于 unit cube 外的 direct light,通常只需 3 到 5 个背向 unit cube 的 face;若 point light 位于 unit cube 内,则使用全部 6 个 face。
↡把 post-projective 顶点位置减去 light position 作为 cube-map 方向、并把 unit cube 面作为纹理平台的采样方案 的关键坐标是 texture coordinate = vertex position - light position。它不是普通世界空间 cubemap:face 仍然对准 unit cube 的面,目的是让纹理面积更接近屏幕投影面积。总纹理尺寸相同时,fill rate 可以接近单张图,但会增加 render-target 切换和坐标计算成本。
vec3 shifted = postProjectivePosition - lightPosition;
vec3 cubeFacePoint = intersectUnitCube(shifted);
vec3 cubeDirection = cubeFacePoint;
float shadowDepth = projectDepth(cubeFacePoint);深度不能直接依赖所有硬件的 cubemap depth texture;一种实现是把深度打包到颜色纹理,另一种是把每个 face 作为独立纹理并管理各自的坐标集。选择方案时要把资源切换和每像素指令纳入预算。
3. bias 必须跟着 texel 位置变化
在 uniform shadow map 中,常量 depth bias 常常足以压住 self-shadowing 的数值误差。PSM 中,透视投影让 near object 被放大、far object 被压缩,texel 在 world space 的大小随位置变化;因此 constant bias 会在一端造成 acne,在另一端造成 peter-panning 或漏光。
↡先在世界空间估计几何误差,再通过双投影转换成当前 PSM 深度比较所需偏移的 bias 策略 可以按 texel 尺寸估计:把点投进 shadow-map space,沿不改变深度的方向移动一个 texel,再变回 world space,差向量的长度平方给出局部尺度。然后把这个世界空间误差转换到 post-projective depth,供比较阶段使用。
不要把 bias 的调参结果只看作一个全局滑杆:要分别观察近处接触边缘、远处平面和光线几乎平行表面的情况。一个位置有效的常数,在另一处可能已远大于 texel 的实际误差。
4. PCF 与 color-mask blur:两种不同的软化
↡对相邻 shadow-map texel 执行多次深度比较并平均结果,以产生软边的过滤方法 先做 depth compare,再平均 compare 结果。硬件 depth texture 可以在比较后执行 PCF;否则需要采样邻近 texel 并手动累加。四次采样是常见起点,但要知道它只是 soften aliasing 的边缘,不会增加原始深度信息。
真正的 blur 不能直接作用于 depth map:深度值不是黑白遮罩,把相邻深度做高斯平均会混合不同遮挡面的几何距离。对于地面、墙面等不需要自阴影的对象,可以在生成 shadow map 时额外写一个 color mask:目标对象写 1,其他对象写 0;之后对这个 color mask 做多 pass blur,渲染时仅对这些对象使用模糊结果。
PCF 的最小逻辑可以写成:
float shadow = 0.0;
for (int i = 0; i < sampleCount; i++) {
float sampleDepth = sampleShadowMap(uv + offsets[i]);
shadow += currentDepth > sampleDepth + bias ? 1.0 : 0.0;
}
shadow /= sampleCount;工程上要分开记录三类证据:aliasing 是否来自 texel 过稀、acne 是否来自 bias 过小、边缘是否只是需要更多 PCF。若把三者混成一个 blur 滑杆,调参会掩盖投影错误而不是解决它。
5. 动手实验:把投影预算拨给真正的 receiver
先预测:关闭 unit cube clipping 时,哪些格子会变成 unused texel?把 bias 切到 constant 后,近处和远处哪个区域更容易出现错误?增加 PCF samples 会让 aliasing 消失,还是只让它变软?
Perspective Shadow Map Lab
先猜:把 light camera 聚焦到 receiver,为什么能减少 aliasing?
观察:clipping 提高目标区域 texel density;world-space bias 减少近处 acne 和远处漏光的统一取舍;PCF 只能缓解,不会消除严重 aliasing。
实验中的绿色区域代表 receiver bounds,网格代表 shadow-map texel 的预算。near plane 与 light angle 改变 post-projective 的密度,clipping 决定是否把预算聚焦到 receiver,bias 模式影响位置相关误差,PCF 只改变比较的平均程度。用同一个场景逐项切换,才能把 aliasing、bias 和 filtering 的症状分开。
小结:PSM 是空间分配加质量补丁
- PSM 在 post-projective space 中重新分配 texel,让近处高屏幕占比区域得到更多阴影采样。
- virtual camera 能覆盖真实相机后方的 caster,却会减少有效分辨率并引入质量跳变;inverse projection 和 receiver clipping 是更可控的方向。
- light camera 应围绕 receiver bounding volumes 选择;unit cube clipping 用粗略几何换取更集中的纹理预算。
- PSM 的 depth 与 texel 尺度不均匀,bias 要在 world space 估计并随位置转换,不能迷信一个 constant。
- PCF 负责比较结果的边缘平均;不需要自阴影的 ground/wall 可以另写 color mask,再对 mask blur,不能直接 blur depth。
练习
问题 1|推导题 一个远处 receiver 的 world-space texel 尺寸约为近处 receiver 的 4 倍。若两处都使用相同的 constant bias,哪一处更可能出现 acne,哪一处更可能出现漏光?为什么?
问题 2|改 Demo 代码 把实验中的 unit cube clipping 关闭,并把 light angle 调到接近正对相机。请记录 texel 密度、unused 区域、PCF 采样数和边缘 aliasing,说明哪个变化属于投影问题。
问题 3|独立实现题 为一个室外地面、建筑物和移动角色设计混合方案:哪些对象可以使用 color-mask blur,哪些对象必须保留 depth + PCF?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- post-projective space
- 透视投影后的坐标空间,PSM 在此重新分配 shadow-map texel。
- perspective shadow map
- 利用相机投影后的空间分布来缓解阴影图 aliasing 的方法。
- virtual camera
- 为覆盖真实相机后方 caster 而向后滑动的替代视点。
- unit cube clipping
- 依据 receiver/caster 包围体收窄 light camera 视野的优化。
- displaced-center cube map
- 以顶点位置减去光源位置构造方向、并以 unit cube 面为平台的 cubemap 方案。
- world-space bias
- 在世界空间按局部 texel 误差估计、再转换到 PSM 深度的偏移策略。
- percentage-closer filtering
- 对多个深度比较结果平均以软化阴影边缘的过滤方法。