一、Shadow Mapping 的核心思想
阴影的本质是:某个点看得到光源吗?
Shadow Mapping 用两趟渲染来回答这个问题:
Pass 1(Shadow Pass):从光源的视角渲染场景 → 输出:Shadow Map(一张深度纹理) → 每像素存的是"从光源看,最近物体的深度"
Pass 2(Main Pass):从摄像机视角正常渲染 → 对每个像素,计算它在光源空间的坐标 → 查 Shadow Map:如果当前像素比记录的深度更远 → 在阴影中一句话:Shadow Map 是从光源的视角拍的一张”深度照片”。主渲染时,每个像素问一句”我从光源那看,被挡住了吗?“
二、Shadow Map 的经典问题
1 阴影痤疮(Shadow Acne)
光源 │ │ ┌──────────── 表面(Shadow Map 记录的深度) │ │ ▼ │╱╲╱╲╱╲╱╲╱╲ 实际几何体(因为 Shadow Map 分辨率有限,有微小深度差) └────────────Shadow Map 的每个像素覆盖了表面的一小块区域。这块区域内深度有微小变化——导致某些像素错误地被判定为”在阴影中”,出现条纹状的自阴影斑块。
解决方案:Depth Bias(深度偏移)
float shadow = (currentDepth - bias > shadowMapDepth) ? 1.0 : 0.0;Bias 太小 → 还有痤疮。Bias 太大 → 阴影与物体分离(Peter Panning,彼得潘效应)。
UE 的做法:使用 Constant Bias + Slope-Scaled Bias。Slope-Scaled Bias 根据表面法线与光源方向的夹角动态调整——越接近平行(grazing angle),Bias 越大。
2 锯齿(Aliasing)
Shadow Map 分辨率有限(如 2048×2048),投射到场景中,每个 Shadow Map 像素覆盖了场景中的一整块区域——阴影边缘呈现阶梯状锯齿。
解决方案:PCF(Percentage Closer Filtering)
不只是采样一个 Shadow Map 位置,而是采样周围一圈(如 3×3 或 5×5),取平均值:
float shadow = 0.0;for (int x = -2; x <= 2; x++) { for (int y = -2; y <= 2; y++) { float depth = ShadowMap.Sample(Sampler, uv + float2(x,y) * texelSize); shadow += (currentDepth - bias > depth) ? 1.0 : 0.0; }}shadow /= 25.0; // 5×5 PCFPCF 的本质是用更多采样换取更平滑的阴影边缘。代价是每像素 25 次纹理采样——在低端硬件上要注意。
三、级联阴影(CSM):近处清晰,远处模糊
1 单张 Shadow Map 的问题
如果 Shadow Map 要覆盖整个摄像机视锥体(比如最远 500 米),分摊到近处物体时,每个 Shadow Map 像素覆盖的面积巨大——近处阴影锯齿严重。
2 把视锥体切成多层
Cascade 0: 0-10m → Shadow Map 2048×2048(近处,高精度)Cascade 1: 10-40m → Shadow Map 1024×1024Cascade 2: 40-150m → Shadow Map 512×512Cascade 3: 150-500m → Shadow Map 256×256(远处,低精度,但屏幕占比小)每个 Cascade 的 Shadow Map 只覆盖视锥体的一部分,分辨率分配更合理。近处的阴影精致,远处的阴影模糊但远处的物体在屏幕上也小——看不出来。
UE 默认使用 4 级 Cascade(可配)。在 DirectionalLight 的 Cascaded Shadow Maps 设置中可以调整级数和各级覆盖范围。
3 CSM 的额外开销
每一级 Cascade 都需要一次独立的 Shadow Pass(从光源视角渲染一遍场景)。4 级 Cascade = 4 趟 Shadow Pass。这就是为什么 Directional Light 的影子在复杂场景里很贵——每个 Shadow Caster(有 Cast Shadow 标记的网格体)要被渲染 4 遍(每级 Cascade 一遍)。
优化手段:远处的 Cascade 可以降低网格体的 LOD 甚至直接跳过小物体。
四、UE5 Virtual Shadow Maps(VSM)
1 CSM 的局限性
CSM 的阴影质量是由 Shadow Map 的固定像素密度决定的——Cascade 0 的 2048×2048 分给 0-10 米范围,如果场景里有一棵离摄像机 2 米的大树,树干上的阴影细节可能仍然不够。
2 VSM 的核心思想
不要固定分辨率的 Shadow Map。把 Shadow Map 虚拟化——只在需要阴影细节的地方分配更多 Shadow Map 页面(Page):
传统 Shadow Map:一整张固定大小的纹理VSM:一个巨大的虚拟纹理地址空间 → 屏幕上一个像素需要查阴影时,才去请求对应的 Shadow Map Page → 近处物体需要的 Page 多(分辨率高),远处需要的少 → 类似虚拟纹理(Virtual Texture)——按需分配物理页面3 与 Nanite 的配合
Nanite 的高面数模型在传统 Shadow Map 里是灾难——每个三角形都要被光栅化到 Shadow Map。VSM 配合 Nanite,只光栅化实际上在 Shadow Map 里可见的三角形,大幅减少了 Shadow Pass 的几何负担。
五、其他阴影技术简介
| 技术 | 适用场景 | 说明 |
|---|---|---|
| Shadow Map | 方向光、聚光灯 | 最通用,CSM 是其扩展 |
| Point Light Shadows(Cube Shadow Map) | 点光源 | 用 Cubemap 存 6 个面的深度,或者用 Dual-Paraboloid 替代 |
| PCSS(Percentage Closer Soft Shadows) | 需要”近处硬、远处软”的自然阴影 | 根据遮挡物与接收面的距离,动态调整 PCF 采样半径 |
| Ray Traced Shadows | 高质量阴影 | 用 DXR 硬件光追做精确阴影,和 Shadow Map 混合使用 |
| Contact Shadows(Screen Space) | 细节阴影,弥补 Shadow Map 精度不足 | 在屏幕空间做短距离光线步进,只对近处有效 |
UE5 默认使用 VSM + Ray Traced Shadows 的混合方案(如果硬件支持光追),并回退到 CSM(如果不支持)。
六、总结
| 概念 | 一句话 |
|---|---|
| Shadow Mapping | 从光源视角渲染深度图 → 主渲染时查询是否被遮挡 |
| Shadow Acne | Shadow Map 精度不足 → 自阴影斑块 → Bias 解决 |
| Peter Panning | Bias 太大 → 阴影与物体分离 |
| PCF | 多次采样取平均 → 软阴影边缘 → 锯齿变平滑 |
| CSM | 视锥体切片 → 近处高精度、远处低精度 → 方向光标配 |
| VSM | 虚拟化 Shadow Map → 按需分配分辨率 → UE5 的默认方案 |
阴影是所有实时渲染技术里最擅长”用巧妙近似换取画面效果”的领域。理解 Shadow Mapping 的思维——用光源视角、做深度比较、用 Bias 和 PCF 擦屁股——比记住具体的 CSM 级数重要得多。