虚拟阴影贴图
Virtual Shadow Maps
虚拟阴影贴图 (Virtual Shadow Maps, VSM) 是 UE5 默认的阴影方法:概念上就是分辨率极高(虚拟 16k×16k)的阴影贴图,切成 128×128 的页 (Page),只为屏幕上需要着色的像素分配和渲染页,并跨帧缓存;方向光用 clipmap 覆盖大范围,软阴影由阴影贴图光线追踪 (Shadow Map Ray Tracing, SMRT) 生成。它为 Nanite 设计,与 Nanite 一起使用时最高效。
原理:把阴影贴图"虚拟化"
传统 阴影贴图 的核心矛盾是分辨率:一张贴图要覆盖整个可见范围,近处像素不够用。级联阴影贴图 用几级级联缓解。VSM 的做法与 大场景渲染与虚拟纹理 中的虚拟纹理同构:(Virtual Shadow Maps)
- 每个光源拥有一张虚拟 16k×16k 的阴影贴图,切成 128×128 的页;
- 分析深度缓冲,标记"屏幕上着色确实需要"的页,只为这些页分配物理内存并渲染;
- 页跨帧缓存,除非被移动的物体或光源失效 (Invalidate)。
下图表示一帧的 VSM 流程:
flowchart LR
A["深度缓冲"] --> B["页标记<br/>需要哪些页/哪级 mip"]
B --> C{"页在缓存且未失效?"}
C -- "是" --> D["复用缓存页"]
C -- "否" --> E["渲染该页<br/>Nanite 一次/非 Nanite 逐光源绘制"]
D --> F["物理页池"]
E --> F
F --> G["阴影投影 SMRT 采样"]不同光源的组织方式:(Virtual Shadow Maps)
| 光源 | 结构 | 细节层次选择 |
|---|---|---|
| 方向光 | clipmap:每级一张 16K VSM,每级覆盖前一级两倍半径;默认第 6–22 级,即约 64 cm 到 40 km | 按到相机距离;r.Shadow.Virtual.ResolutionLodBiasDirectional,-1 使分辨率加倍 |
| 聚光灯 | 一张 16k VSM + mip 链 | 把屏幕像素大小投影到阴影空间选 mip |
| 点光源 | 立方体贴图,每面一张 16k VSM | 同上 |
clipmap 和级联本质上都是"按距离分级的分辨率",但 VSM 每级都是稀疏分配的虚拟贴图,空级几乎没有成本。(Virtual Shadow Maps) 选页级别的思路也和 细节层次 / mip 选择一致。官方也坦言:VSM 仍有透视走样 (Projective Aliasing)(光线几乎平行于表面时),"用阴影贴图解决透视走样没有简单方案"。(Virtual Shadow Maps)
VSM 启用后会取代:固定光源 (Stationary) 预计算阴影、预阴影 (Pre-shadow)、逐物体阴影、CSM 以及可移动局部光源的动态阴影;距离场阴影不被取代,可在动态阴影距离外为非 Nanite 几何接管;光追阴影仍优先。(Virtual Shadow Maps)
软阴影:SMRT
SMRT 向光源发出若干条光线,但不与几何求交,而是把沿光线的若干采样点投影到 VSM 上做测试,从而得到软阴影和接触硬化 (Contact Hardening)——遮挡体离接收面越远阴影越软。官方指出过去基于 PCF 的方法会均匀地过度模糊、削弱高分辨率几何的细节。(Virtual Shadow Maps) 这与 软阴影 PCF 与 PCSS 中 PCSS 追求的"按遮挡距离变化的半影"目标相同,只是实现换成了在阴影贴图上的光线步进。
- 光线分布由局部光的 Source Radius 或方向光的 Source Angle 决定;Epic 级默认 8 条光线;
r.Shadow.Virtual.SMRT.RayCountLocal/Directional设为 0 则退化为单次采样硬阴影;每条光线采样数 4–8 效果较好。(Virtual Shadow Maps) - 局限:VSM 只存第一层深度,遮挡体重叠处会漏光,用"填缝启发式"外推;半影大小被钳制,Source Radius/Angle 过大会变形且变慢。(Virtual Shadow Maps)
缓存:性能的关键
官方说复用前几帧的页是保持高性能的关键。失效来源:(Virtual Shadow Maps)
- 光源移动或旋转 → 该光源所有缓存页失效;
- 投影几何移动、添加、删除 → 从光源视角看与其包围盒重叠的页失效(包括蓝图设置了属性导致渲染状态失效但实际未动的情况);
- 使用 WPO(世界位置偏移)或 PDO(像素深度偏移)的材质、骨骼动画 → 每帧失效。
应对:
- 用 Cached Page 可视化(绿=缓存,红=新/失效)和 ShadowCasters 视图找出元凶;包围盒要紧,宽松包围盒在低光照角度下会造成大量无谓失效。
- 完全处于 Nanite 物体阴影中的失效可以跳过,非 Nanite 不行——所以建筑等大投影体应使用 Nanite(见 Nanite 虚拟化几何体)。
- 按图元设置 Shadow Cache Invalidation Behavior(Auto/Always/Rigid/Static);用 World Position Offset Disable Distance 让远处植被停止摆动。
- 静态/动态分离缓存:VSM 实际保存两份深度(静态与动态),动态物体移动时不必重画昂贵的静态几何;理想情况下静态页失效数接近 0,频繁失效的物体应改为 Movable。
性能分析
VSM 成本落在两个桶:(Virtual Shadow Maps)
| 桶 | 内容 | 主要影响因素 | 调优 |
|---|---|---|---|
| Shadow Depths | 把几何渲染进阴影页 | 需渲染的页数 × 要渲染的几何量 | 提高缓存命中;全面启用 Nanite;非 Nanite 配完整 LOD、r.Shadow.RadiusThreshold、远处切距离场阴影、关闭粗糙页中的非 Nanite |
| Shadow Projection | SMRT 采样 | 每像素平均光源数、每像素光线数、每光线采样数 | 先减小 Source Radius/Angle,再降光线/采样数 |
非 Nanite 几何渲染进 VSM 要贵得多:方向光一次 Nanite 通道画完所有方向光,非 Nanite 则每个光源一个通道、逐物体绘制调用;一个静态网格实例对点光源可能变成 48 个剔除实例(8 级 mip × 6 面),对方向光 17 个(17 级 clipmap)。(Virtual Shadow Maps) 统计用 r.ShaderPrintEnable 1 + r.Shadow.Virtual.Stats 1,也可在 Unreal Insights 中 trace.enable counters,vsm;测量时用 r.RDG.AsyncCompute 0 获得更有代表性的分通道时间(仅限分析,切勿用于发行)。(Virtual Shadow Maps) 详见 UE5 性能分析。
坑与限制
- 低多边形高曲率模型出现"阴影明暗交界线问题 (Shadow Terminator)":首选加面(Nanite 加面便宜),实在不行再调
r.Shadow.Virtual.NormalBias(默认 0.5),后者会损失全场景细节。(Virtual Shadow Maps) - 物理页池溢出会导致棋盘格或阴影缺失,需调
r.Shadow.Virtual.MaxPhysicalPages或降分辨率/光源数。(Virtual Shadow Maps) - 大量小局部光源的性能仍在改进中;Subsurface Profile 与透射尚未支持。(Virtual Shadow Maps)
- 不用 Nanite 的项目或平台,VSM "很可能不合适";平台需要 DX12 SM6.6 原子操作或 Vulkan 64 位原子。(Virtual Shadow Maps)
阴影基础概念(自阴影、偏移、走样)见 阴影基础与阴影体。
出处
- Virtual Shadow Maps UE 5.8 文档
- Nanite Virtualized Geometry Overview UE 5.8 文档