UE 知识卡片 Rendering Nanite · VSM · SMRT

Virtual Shadow Maps

UE5 下一代阴影:页面化流送、与 Nanite 协同,以及真实项目里的性能陷阱与诊断路径。

Question

请详细解释 Virtual Shadow Maps(VSMs)的核心机制、它们与 Nanite 的内在关系,以及如何实现一致的高分辨率阴影。同时讨论常见性能陷阱(非 Nanite 几何、快速相机运动、复杂材质),并说明如何在底层诊断与缓解——引用相关控制台变量与 C++ 考量。

一句话结论

VSMs 把阴影贴图像 Nanite 一样「虚拟化」:超高分辨率阴影被切成可缓存的页面,按屏幕需求按需分配与失效,从而在开放世界动态光照下仍能保持一致的高分辨率阴影。

虚拟分辨率概念上可达 16k×16k,定向光再用 clipmap 扩展
页面大小128×128 像素瓦片,按需分配 / 跨帧缓存
柔和阴影SMRT:接触硬化半影,非均匀模糊
最佳拍档Nanite 几何路径成本最低、质量最稳

核心机制

VSMs 在概念上是超高分辨率阴影贴图。为避免全量渲染,它采用基于页面的流送

  1. 根据深度缓冲分析,只分配屏幕着色真正需要的页面;
  2. 页面跨帧缓存;物体或光源移动时才失效并重绘;
  3. 定向光通过 clipmap 在不同距离层维持合适解析度。
设计直觉:不是「永远画一张巨图」,而是「只画当前帧用得到的阴影碎片,并尽量复用」。

与 Nanite 的关系

VSMs 把 Nanite 的虚拟化思路延伸到阴影。Nanite 的自动 LOD / 剔除与 VSM 的页面分配天然契合:Nanite 能高效提供栅格化虚拟页面所需的高细节几何。

启用 VSMs 后,许多传统路径会被统一替换(Stationary 预计算阴影、逐对象阴影、可移动光的 CSM 等),因为 Nanite 往往不兼容这些旧技术。

柔和阴影:SMRT

VSMs 通过 Shadow Map Ray Tracing(SMRT) 做接触硬化半影:近处更锐、远处更软,比均匀 PCF 更符合物理。柔和度受光源半径/角度影响,也可由 CVar 控制采样数。

r.Shadow.Virtual.SMRT.SamplesPerRay.Local
r.Shadow.Virtual.SMRT.SamplesPerRay.Directional

常见性能陷阱

场景为什么疼优先动作
非 Nanite 几何 走更贵的栅格化路径,相机移动时易尖峰 / 伪影 能 Nanite 就 Nanite;否则评估是否必须投 VSM
快速相机 / 大面积露出 大量页面在同一帧失效、分配、重绘 降分辨率与物理页上限;减少动态投射物
复杂材质 WPO、Masked、可编程栅格化抬高深度通道成本 简化投射阴影材质;必要时换阴影方案
VRAM 压力 物理页面池仍占显存,高 clipmap / 多光源更糟 MaxPhysicalPages、clipmap 最大分辨率

诊断

  • Unreal Insights / stat gpu / profilegpu:盯 Shadow Depths,尤其是 Render Virtual Shadow Maps Non-Nanite
  • 编辑器可视化:Show → Visualize → Virtual Shadow Maps(页面分配、失效、非 Nanite 贡献)

缓解策略

  1. 优先 Nanite:静态与高模动态网格尽量开 Nanite。蒙皮 / 复杂 WPO 无法 Nanite 时,评估胶囊体阴影或预计算阴影。
  2. 调质量档:用下表 CVar 按预算下调分辨率、页面池、SMRT 采样。
  3. 简化投射材质:去掉不必要的 WPO / 复杂 Masked / 自定义可编程栅格化。
  4. 管动态:减少高频移动的 VSM 投射物与高质可移动光数量。
  5. 借 World Partition:减少任一时刻需要考虑的潜在阴影投射物总量。

CVar 速查

控制台变量用途
r.Shadow.Virtual.Enable 0最后手段:整站关掉 VSM
r.Shadow.Virtual.ResolutionLodBiasDirectional定向光整体分辨率偏置(约 -3~3,降低可减负载)
r.Shadow.Virtual.Clipmap.MaxResolution定向光 clipmap 最大分辨率(如 2048)
r.Shadow.Virtual.MaxPhysicalPages物理页面上限(过低会伪影)
r.Shadow.Virtual.SMRT.SamplesPerRay.*柔和阴影采样(1–8);降采样换性能
注意:把 MaxPhysicalPages 压太低会直接出阴影破洞 / 闪烁。先用可视化确认是「分配过多」还是「失效风暴」。

C++ 考量(进阶)

  • 对问题 Actor 关闭阴影 / VSM:UPrimitiveComponent::SetCastShadow(false),或 SetCastVirtualShadowMap(false)(若已暴露)
  • 按场景动态改 VSM CVar / 光源设置(例如动作镜头临时降质)
  • 极端情况再下钻引擎源码:Engine/Source/Runtime/Renderer/Private/VirtualShadowMaps/