Chapter 31: Shadowing Techniques
阴影把光照从“表面如何反射光”推进到“光线是否到达表面”。读完本章,读者应能追踪一个像素从相机视角进入片段着色器后,如何被转换到光源视角,如何与光源深度图比较,如何得到阴影系数,并能根据画面伪影判断问题来自投影、深度精度、过滤、级联选择还是几何体闭合性。
本章贯穿一个固定场景:一个方向光照亮地面、墙体和一个移动角色。相机贴近地面观察角色脚下的接触阴影,远处有建筑投影,角色在镜头中移动时阴影边缘会出现锯齿、闪烁、自阴影条纹和阴影漂移。这个场景覆盖实时渲染中最常见的阴影问题:可见性测试、shadow map pass、接收面深度比较、PCF 过滤、级联阴影和 shadow volume 的 stencil 路径。
阴影技术的主问题可以压缩成一句话:同一个世界空间点,从相机看是当前像素,从光源看是否被遮挡。Shadow Mapping 通过“光源视角深度图 + 接收面深度比较”回答这个问题;Shadow Volumes 通过“遮挡体积 + stencil 计数”回答这个问题。两者都在实时管线中使用现有的 rasterization、depth buffer、stencil buffer 和 shader 资源,只是把可见性判断放到了不同的数据结构中。
本章会把阴影结果拆成四层证据:第一层是视觉结果,例如边缘锯齿、Peter Panning、shadow acne、cascade seam;第二层是 shader 输入,例如 world position、normal、light matrix、comparison depth;第三层是 API 和资源状态,例如 depth texture、sampler comparison、stencil op、viewport、bias;第四层是 GPU 成本,例如额外 pass、深度写入、PCF 多次采样、级联数量和填充压力。
31.1 阴影生成的基本问题与需求
阴影生成的核心对象是“光照可见性”。对一个接收阴影的片段,局部光照模型已经知道法线、光照方向和材质参数;阴影项再给出一个权重,表示来自该光源的直接光是否抵达该点。最常见的实时表达是把直接光乘以 shadowFactor:值接近 1 表示接收光照,值接近 0 表示被遮挡,边界区域可以通过过滤得到中间值。
在贯穿场景中,角色脚下的接触阴影来自同一个判断:地面上的某个像素在相机视角可见,同时从方向光视角看,角色鞋底或腿部可能挡住了这条光路。渲染器需要对这个像素回答三个问题:该像素的世界空间位置是什么;这个位置投影到光源视角后对应 shadow map 的哪个 texel;该位置的光源深度是否大于 shadow map 中已记录的最近深度。
这个判断天然分成两个 pass。第一个 pass 从光源视角渲染投影阴影的物体,只写深度,输出一张 shadow map。第二个 pass 从相机视角渲染正常画面,在片段着色器中把当前像素位置转换到 light clip space,再完成深度比较。Lance Williams 在早期 shadow mapping 工作中使用“从光源视角保存深度,再投影回场景比较”的思路,现代实时引擎仍沿用这个主链路;Microsoft 的 Cascaded Shadow Maps 文档也把 CSM 拆成分割视锥、计算每段光源正交投影、渲染每段 shadow map、场景渲染时选择并采样级联这几个阶段。
下面的流程图只描述 directional light 的 shadow mapping 主路径。点光和聚光的投影形态会变化,深度比较这个核心动作保持一致。
图中最关键的路径是 Depth Texture 到 Shadow Factor。shadow map 记录的是光源能看到的最近表面深度;相机 pass 中的当前片段如果在光源视角落到同一个 texel,却具有更远的深度,就说明它处在遮挡物之后。这个比较只依赖光源视角的几何可见性,和相机当前能否看到遮挡物没有直接关系。
阴影质量需求可以分为四类。第一类是空间精度,shadow map 的 texel 必须覆盖接收面上的足够小区域,否则远处建筑边缘或近处脚底阴影会出现块状锯齿。第二类是深度精度,当前片段深度和 shadow map 深度必须能稳定区分,否则同一表面会产生条纹状自阴影。第三类是时间稳定性,相机或光源轻微移动时,shadow texel 对世界空间的覆盖区域要稳定,否则边缘会闪烁。第四类是视觉过滤,硬边界在很多材质和光源尺寸下显得突兀,PCF、VSM 或接触阴影可以让过渡更可控。
一次阴影问题排查应先把画面症状映射回这四类需求。锯齿通常指向空间精度或过滤;斑驳条纹通常指向深度 bias 或法线斜率;角色脚底阴影悬空通常指向 bias 过大;远近切换出现亮暗线通常指向 cascade split、级联选择或级联混合;角色移动时阴影局部缺失通常指向 shadow caster 列表、skinning 深度 pass、裁剪范围或深度格式。
一个最小 shader 结构可以把阴影项的位置固定下来。这里的代码展示关系,不绑定某个 API 的精确语法。
float3 worldPosition = input.worldPosition;
float3 normalWs = normalize(input.normalWs);
float3 lightDirWs = normalize(gDirectionalLight.directionWs);
float4 lightClip = mul(float4(worldPosition, 1.0), gLightViewProj);
float3 shadowCoord = lightClip.xyz / lightClip.w;
shadowCoord.xy = shadowCoord.xy * 0.5 + 0.5;
float ndotl = saturate(dot(normalWs, -lightDirWs));
float shadowFactor = SampleShadowMap(shadowCoord);
float3 directLight = gDirectionalLight.color * ndotl * shadowFactor;
这段代码证明阴影项位于直接光贡献路径上。worldPosition 是接收面材料计算的一部分,gLightViewProj 把它映射到光源裁剪空间,SampleShadowMap 输出可见性权重。后续小节的 shadow map、PCF、CSM、bias 都是在稳定这个 shadowFactor。
31.2 阴影映射(Shadow Mapping)原理与实现
Shadow Mapping 的工作定义是:用一张或多张从光源视角渲染得到的深度纹理表示光源可见表面,再在相机视角对接收面做深度比较。它解决的是实时 rasterization 管线中“无需追踪真实光线也能得到投影阴影”的问题,适用于三角形场景、可栅格化遮挡物和直接光阴影;它的边界是精度受纹理分辨率、投影范围、深度格式和过滤方式约束。
在贯穿场景中,方向光的 shadow map pass 通常使用正交投影。渲染器先确定相机可见区域中需要阴影覆盖的范围,再把这些点包围到光源空间下的正交盒。角色、墙体和建筑作为 shadow caster 写入 depth texture;地面作为 shadow receiver 在相机 pass 中采样这张 depth texture。对聚光灯,light projection 会更接近相机透视投影;对点光源,常见实现会使用 cubemap shadow 或多视图深度。
实现 Shadow Mapping 时,CPU 侧要构建资源和状态,GPU 侧要执行两个管线阶段。CPU 侧的关键对象包括 depth texture、depth view、sampler comparison、light matrix、shadow caster draw list 和可选的 cascade 参数。GPU 侧的第一个阶段只需要 vertex processing、rasterization 和 depth test,通常关闭颜色输出;第二个阶段在 pixel shader 中完成投影坐标和比较采样。OpenGL、Vulkan、Direct3D、Metal 的对象名字不同,数据关系相同:一张可作为 depth attachment 写入、随后作为 sampled texture 读取的深度资源,需要在两个 pass 之间完成可见的状态转换或隐式同步。
shadow map pass 的关键不是“画出阴影”,而是把光源能看到的最近深度写入纹理。深度 pass 的 vertex shader 可以只输出 light clip position,pixel shader 可以为空或极简;alpha cutout 材质需要在深度 pass 中执行相同的透明裁剪逻辑,否则树叶、铁丝网、头发卡片会把整块面写进阴影图。skinned mesh 和 morph target 也需要在 shadow pass 中执行一致的顶点变形,否则角色彩色 pass 的位置和 shadow pass 的位置会错开。
下面的伪代码把一个 frame 中的阴影资源生命周期展开成可检查顺序。
// 简化伪代码:展示 frame 内 shadow map 的生产和消费关系。
BeginPass(shadowMapPass);
SetViewport(shadowMapResolution, shadowMapResolution);
SetDepthTarget(shadowDepthTexture);
SetColorTargets(nullptr);
SetPipeline(shadowDepthOnlyPipeline);
SetMatrix(lightViewProjection);
DrawShadowCasters(scene.shadowCasterList);
EndPass();
Transition(shadowDepthTexture, DepthWrite, ShaderRead);
BeginPass(cameraLightingPass);
SetRenderTarget(hdrColorTarget);
SetDepthTarget(cameraDepthBuffer);
SetPipeline(litMaterialPipeline);
BindTexture("uShadowMap", shadowDepthTexture);
BindSampler("uShadowSampler", comparisonSampler);
BindMatrix("uLightViewProjection", lightViewProjection);
DrawVisibleReceivers(scene.cameraVisibleList);
EndPass();
这段代码回答两个工程问题。第一,shadow map 是 camera lighting pass 的输入纹理,因此 pass 顺序和资源状态必须让深度写入结果对后续 shader 可见。第二,shadow caster list 与 camera visible list 可以不同;屏幕外的高楼可能仍然投下可见阴影,角色的某个武器可能需要投影但当前相机只看到它的影子。
深度比较的核心公式可以写成行内形式:如果 receiverDepth - bias > storedDepth,当前片段处于阴影中;否则接收光照。receiverDepth 来自当前片段的 light space 坐标,storedDepth 来自 shadow map 采样结果,bias 用来抵消同一表面在光源深度图和相机片段计算之间的数值差异。比较方向和 NDC 深度范围会随 API 约定变化,例如 Direct3D 常用 0 到 1 的深度范围,OpenGL 传统裁剪空间是 -1 到 1,Vulkan 采用 0 到 1 的深度范围;工程中应把这一步集中封装,统一检查坐标映射。
Percentage Closer Filtering(PCF)的工作方式是对多个邻近 texel 分别执行深度比较,再平均这些通过/失败结果。它过滤的是“比较结果”,不是直接模糊 depth value。NVIDIA 的 Shadow Map Antialiasing 和 Microsoft 的 CSM 文档都围绕 PCF 讨论过多次比较采样如何缓解边缘锯齿。PCF 核越大,边缘越柔,但 texture fetch、比较采样和 bias 问题都会放大。
一个可迁移的 PCF 采样函数可以写成下面的形式。它保留了最小必要输入:投影后的 shadow coordinate、比较深度、texel size 和 bias。
float SampleShadowMap(float3 shadowCoord)
{
if (shadowCoord.x < 0.0 || shadowCoord.x > 1.0 ||
shadowCoord.y < 0.0 || shadowCoord.y > 1.0)
{
return 1.0;
}
float comparisonDepth = shadowCoord.z - gShadowBias;
float2 texelSize = gShadowTexelSize;
float sum = 0.0;
[unroll]
for (int y = -1; y <= 1; ++y)
{
[unroll]
for (int x = -1; x <= 1; ++x)
{
float2 uv = shadowCoord.xy + float2(x, y) * texelSize;
sum += gShadowMap.SampleCmpLevelZero(gShadowSampler, uv, comparisonDepth);
}
}
return sum / 9.0;
}
这个示例的边界很明确。它适合说明 PCF 如何产生软化边缘,不能推出大型项目应固定使用 3 × 3 核。移动端 tile-based GPU、桌面高端 GPU、VR 双眼渲染和大规模开放世界的预算不同,PCF 核大小、采样模式、级联数量和接触阴影方案都需要按 frame budget 决定。
Cascaded Shadow Map(CSM)的工作定义是:把相机视锥按深度分成多个区间,每个区间使用一张更贴合该区间的方向光 shadow map。它解决近处像素需要更高 shadow texel 密度、远处区域又需要大范围覆盖的矛盾。Microsoft 文档把 CSM 步骤列为分割视锥、为每个 subfrustum 计算正交投影、渲染每段 shadow map、在场景渲染中选择并采样对应 cascade;NVIDIA 的 Parallel-Split Shadow Maps 也围绕类似分割思想组织方向光阴影。
在贯穿场景中,角色脚下的接触阴影应该落入近级联,远处建筑阴影落入远级联。近级联覆盖范围小、texel 密度高,能保留脚底边缘;远级联覆盖范围大、texel 密度低,保证建筑投影不断开。cascade split 的选择影响画面稳定性:split 过少会让近处锯齿明显,split 过多会增加深度 pass 次数、内存占用和采样选择成本。
CSM 的 shader 选择逻辑通常有两类。基于 view depth 的选择用相机空间深度直接定位 cascade index,速度稳定,适合固定 split;基于 shadow coordinate bounds 的选择测试像素是否落入某个 cascade 的 UV 范围,能更充分利用贴合后的投影范围,但 shader 分支和循环更多。实际工程中常把 split、cascade matrix、cascade scale/offset 和 blend band 放入 uniform buffer,让材质 shader 以同一套函数访问阴影。
31.3 阴影体积(Shadow Volumes)与 Z 缓冲的关系
Shadow Volumes 的工作定义是:从遮挡物相对光源的轮廓边延伸出一个体积区域,再用 stencil buffer 判断相机可见像素是否位于该体积内。它解决的是“几何上精确表达硬阴影边界”的问题,适用于封闭网格、硬阴影和需要像素级边界稳定性的场景;它的主要成本来自额外几何体、stencil pass 和填充压力。
在贯穿场景中,角色遮挡方向光时,角色模型相对光源会形成一圈 silhouette edge。把这些边沿光照反方向拉伸,可以得到一个包围阴影区域的几何体。相机 pass 先正常写入 depth buffer;shadow volume pass 再渲染这个体积的正面和背面,用 depth test 与 stencil increment/decrement 记录视线穿过阴影体的次数。最后的 lighting pass 或 fullscreen pass 根据 stencil 值决定哪些像素处在阴影中。
Shadow Volumes 与 Z 缓冲的关系在于:stencil 更新只在 volume polygon 与已有场景深度发生特定关系时执行。Z-pass 方法在 shadow volume 面通过深度测试时更新 stencil,适合相机位于阴影体外且没有近裁剪破坏体积的情况。Z-fail 方法在 shadow volume 面深度测试失败时更新 stencil,也被称为 Carmack's Reverse,能处理相机进入阴影体或近裁剪截断 volume 的场景,但要求体积封闭,远端盖面和近端裁剪条件要处理稳定。Everitt 和 Kilgard 的 Practical and Robust Stenciled Shadow Volumes 讨论了 z-fail、无限远投影、depth clamp 和双面 stencil 对鲁棒性的影响。
下面的流程图展示 shadow volume 路径和 shadow map 路径的差异。二者都依赖 depth buffer,但前者把阴影判断写入 stencil,后者把光源可见性写入 depth texture。
Z-pass 的典型 stencil 规则可以表达为:渲染 shadow volume 正面时 increment,渲染背面时 decrement,最终 stencil 非零表示视线穿过遮挡体后落入阴影。Z-fail 的更新方向相反:深度测试失败的正面 decrement,背面 increment。不同 API 对 front face、back face、depth fail、stencil fail 和 depth pass 的状态命名不同,工程判断应围绕“视线从相机到当前 depth surface 穿过 volume 边界的奇偶关系或计数关系”展开。
Shadow Volumes 对网格提出了更强约束。silhouette edge 要从相邻三角形的光照朝向变化中生成,开洞、非流形边、错误法线和未闭合体都会导致 volume 漏光。对角色这种 skinned mesh,silhouette 需要在变形后计算或用保守网格近似;对 alpha cutout 物体,几何体积很难表达纹理遮挡细节。Shadow Mapping 在这些对象上更灵活,因为它直接栅格化材质深度,但边界精度受 shadow map 分辨率约束。
Shadow Volumes 的优势是硬边界可以非常稳定,和 shadow map texel 密度无关。缺点是软阴影难以自然获得,场景几何越复杂,extruded volume 和 stencil fill cost 越高;当相机近距离看大量投影体积时,stencil pass 可能消耗大量像素带宽。现代实时引擎更常把 Shadow Mapping 作为主方案,把 stencil shadow volume 保留给特殊硬阴影、调试或风格化场景。
对比两条路径时,使用同一组维度更可靠。Shadow Mapping 的输入是光源视角深度图,输出是 shader 中的 shadowFactor;Shadow Volumes 的输入是遮挡物轮廓体积,输出是 stencil mask。Shadow Mapping 的典型伪影是 acne、Peter Panning、锯齿、闪烁和级联接缝;Shadow Volumes 的典型失败是体积未闭合、裁剪破坏、stencil 状态错误和 fill cost 过高。Shadow Mapping 更依赖纹理分辨率和 bias,Shadow Volumes 更依赖几何拓扑和 depth/stencil 状态。
31.4 减少阴影伪影与提升精度
阴影伪影的排查应从可见症状进入资源路径。画面中出现黑色条纹时,先检查 depth bias 和 slope bias;阴影离物体脚底产生间隙时,检查 bias 是否过大、shadow caster 是否使用背面写入、法线偏移是否过强;边缘块状锯齿时,检查 shadow texel density、PCF 核、级联覆盖范围;相机移动时边缘闪烁时,检查 light projection 是否做 texel snapping;级联边界出现亮线时,检查 split、blend band、级联矩阵和过滤半径。
Shadow acne 的成因是接收面和投影到 shadow map 的同一表面在数值上无法保持完全一致。斜坡、低分辨率 shadow map、深度量化、浮点误差和光源投影矩阵都会让 receiverDepth 与 storedDepth 出现微小差异。稳定处理顺序是先收紧 light near/far 和正交投影范围,提高深度有效精度;再设置常量 depth bias;再按表面与光线夹角增加 slope-scaled bias;最后评估法线偏移或 receiver plane depth bias。
Peter Panning 的成因通常是 bias 把阴影从遮挡物下方向远处推开。角色脚底阴影悬空时,画面上会看到鞋底与地面阴影之间有亮边。处理它的顺序应与 acne 相反:先确认 shadow caster 深度 pass 使用了正确变形和缩放;再减小常量 bias;再把 slope bias 限制在高倾角表面;然后使用 contact shadow、屏幕空间短距离遮挡或更高分辨率近级联补足接触区域。接触阴影解决的是近距离视觉粘连感,不能替代方向光远距离投影。
Texel snapping 解决的是时间稳定性。方向光 shadow map 的正交投影如果每帧跟随相机连续移动,世界空间中的同一个点会落入不同 shadow texel,边缘随相机轻微移动而闪烁。常见做法是把 light-space projection center 对齐到 shadow map 的 texel 尺寸:先计算当前级联在 light space 的包围盒,再把中心或偏移量量化到 worldUnitsPerTexel 的整数倍。这样牺牲少量贴合精度,换取相机移动时阴影采样格稳定。
Cascade split 和 cascade blending 解决的是覆盖范围与分辨率的冲突。近级联应覆盖角色、地面接触阴影和相机附近主要交互区域;中远级联覆盖建筑、山体和大面积投影。固定 split 在多数游戏相机中更稳定,动态 split 可以更贴合当前视锥,但容易引入边缘跳变。级联交界处使用一段 blend band,同时对相邻两级执行阴影采样并线性混合,可以压低分辨率差异造成的接缝。
Filtering 的作用是控制边缘过渡。3 × 3 PCF 可以明显减轻硬锯齿,旋转 Poisson disk 或 blue noise 分布可以用较少采样营造更自然的边界,temporal accumulation 可以摊薄单帧噪声。过滤半径应和阴影空间尺度绑定:同样的 5 texel 半径,在近级联是小范围柔化,在远级联可能变成大面积糊边。大核 PCF 还会放大 bias 问题,因为相邻 texel 对应不同接收面位置;Microsoft CSM 文档在 PCF 与 depth bias 部分专门指出,较大过滤核会让邻近 texel 深度与当前片段深度的对应关系变弱。
精度提升还要控制 shadow map 的 light-space 包围盒。方向光场景中,正交盒覆盖范围越大,单个 texel 对应的世界空间面积越大;near/far 范围越松,深度缓冲有效精度越低。对贯穿场景,近级联的正交盒应围绕相机近处视锥段和可能投影到其中的 caster 扩展;远级联可以牺牲部分边缘精度换取覆盖范围。caster 的裁剪范围要包含屏幕外投影来源,例如镜头外的高墙可以把阴影投到当前地面。
下面给出一条可复用的阴影伪影排查顺序。它适合 shadow mapping 主路径,且能与 RenderDoc、Nsight、Xcode GPU tools 或引擎自带 capture 面板配合使用;工具只提供证据,判断仍围绕资源和管线关系完成。
- 固定相机和光源,关闭 TAA、动态分辨率和随机采样,确认伪影是否来自阴影本身。
- 显示 shadow map 深度图,检查 caster 是否写入、skinned mesh 是否变形一致、alpha cutout 是否执行裁剪。
- 显示 light-space UV 和 cascade index,确认接收面坐标是否落在预期级联和有效范围。
- 显示 raw shadow test,不使用 PCF,确认问题来自深度比较还是过滤半径。
- 调整 constant bias 和 slope bias,观察 acne 与 Peter Panning 的变化方向。
- 锁定级联矩阵并开启 texel snapping,观察相机移动时边缘是否稳定。
- 分别增加 shadow resolution、缩小 light projection、调整 split,判断问题受空间精度还是深度精度主导。
- 恢复 PCF、cascade blend 和 temporal filter,检查最终画面与性能预算。
这条顺序的价值在于它先隔离变量,再改变单一参数。若 raw shadow test 已经错误,过滤只会把错误扩散;若 raw test 正确但 PCF 后出现光漏,重点应放到过滤核、比较深度和 bias;若静态画面正确但移动闪烁,重点应放到 projection snapping、cascade split 稳定性和 temporal filter。
阴影优化的最终判断要同时看画面和成本。增加级联数量会提高覆盖精度,同时增加 shadow pass draw 次数、depth bandwidth 和 shader 中的选择开销。提高分辨率会降低锯齿,同时增加显存、深度写入和采样成本。扩大 PCF 核会改善边缘,同时增加 texture fetch 和 bias 难度。缩小 light projection 会提高 texel density,同时可能裁掉屏幕外 caster。稳定方案通常来自一组受控取舍:近处高精度、中远处稳定覆盖、过滤半径受限、bias 按材质和坡度调节、特殊接触区域使用额外短距离技术补足。
最小自检任务
给定一个方向光场景:角色站在地面上,使用 2048 × 2048 的单张 shadow map。画面中角色脚底附近有黑色条纹,稍微增大 bias 后条纹消失,但脚底阴影与鞋底之间出现明显亮缝;相机向前移动时,远处建筑阴影边缘还会闪烁。请判断这些现象分别指向阴影管线中的哪些阶段,并给出一条排查顺序。
答案要点
黑色条纹首先指向 shadow map 深度比较阶段的 shadow acne,重点检查 receiverDepth、storedDepth、constant bias、slope bias、light near/far 和正交投影范围。增大 bias 后出现脚底亮缝,说明 Peter Panning 开始占主导,应减小全局 bias,把偏移更多交给 slope bias、receiver plane bias 或接触阴影补足,并确认角色在 shadow pass 中使用了与 camera pass 一致的 skinning 和缩放。
远处建筑阴影边缘闪烁更可能来自 shadow texel 覆盖不稳定或 texel density 不足。单张 2048 × 2048 shadow map 同时覆盖近处角色和远处建筑时,方向光正交投影范围往往过大;相机移动会让世界空间点落入不同 texel。排查顺序是先关闭 PCF 和 temporal filter,查看 raw shadow test;再显示 shadow map 和 light-space UV;然后收紧 light projection,启用 texel snapping;最后把单张 shadow map 改成 CSM,用近级联服务角色脚底,用远级联服务建筑投影,并在级联边界加入 blend band。
本章知识点总结
- 可见性:阴影项回答当前片段从光源视角是否被遮挡,并作为直接光贡献的权重进入光照模型。
- 双 Pass:Shadow Mapping 先从光源视角写入深度纹理,再从相机视角投影接收面并比较深度。
- 深度比较:
receiverDepth、storedDepth和bias共同决定片段是否处于阴影中。 - 资源状态:shadow depth texture 在一个 pass 中作为深度目标写入,在后续 pass 中作为 shader 采样输入读取。
- PCF:PCF 对多个邻近 texel 分别执行深度比较并平均结果,用比较结果过滤阴影边缘。
- CSM:级联阴影把相机视锥分段,为近处区域提供更高 texel density,为远处区域保留覆盖范围。
- Volume:Shadow Volumes 使用遮挡物轮廓延伸出的体积和 stencil 计数判断像素是否位于阴影区域。
- Stencil:Z-pass 和 Z-fail 的差异来自 stencil 更新发生在 depth pass 还是 depth fail 路径上。
- Acne:shadow acne 多来自同一表面深度比较的数值不一致,处理顺序应从投影范围和深度精度开始。
- Peter Panning:Peter Panning 通常说明 bias 过大或偏移策略过粗,接触区域需要更精细的近距离方案。
- Snapping:texel snapping 把方向光投影对齐到 shadow texel 网格,降低相机移动造成的阴影闪烁。
- Split:cascade split 决定近处精度和远处覆盖的取舍,级联混合负责压低交界处接缝。
- 过滤成本:更大的 PCF 核会增加采样成本,并放大 bias 与邻近 texel 深度不匹配的问题。
- 排查顺序:稳定排查应先隔离 raw shadow test,再检查坐标、caster、bias、projection、级联和过滤。
- 工程取舍:阴影质量来自分辨率、覆盖范围、深度精度、时间稳定性、过滤半径和 frame budget 的共同约束。