1534 字
8 分钟
阴影技术——从 Shadow Mapping 到级联阴影

一、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 PCF

PCF 的本质是用更多采样换取更平滑的阴影边缘。代价是每像素 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×1024
Cascade 2: 40-150m → Shadow Map 512×512
Cascade 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 AcneShadow Map 精度不足 → 自阴影斑块 → Bias 解决
Peter PanningBias 太大 → 阴影与物体分离
PCF多次采样取平均 → 软阴影边缘 → 锯齿变平滑
CSM视锥体切片 → 近处高精度、远处低精度 → 方向光标配
VSM虚拟化 Shadow Map → 按需分配分辨率 → UE5 的默认方案

阴影是所有实时渲染技术里最擅长”用巧妙近似换取画面效果”的领域。理解 Shadow Mapping 的思维——用光源视角、做深度比较、用 Bias 和 PCF 擦屁股——比记住具体的 CSM 级数重要得多。

阴影技术——从 Shadow Mapping 到级联阴影
https://www.m4doka.xyz/posts/cg/cg-6-shadow/
作者
m4doka
发布于
2026-02-22
许可协议
CC BY-NC-SA 4.0