Question: Unreal Engine 5 的 Virtual Shadow Maps (VSMs) 是其下一代渲染的基石。请详细解释 VSMs 的核心机制、它们与 Nanite 的内在关系,以及它们如何实现一致的高分辨率阴影。此外,讨论与 VSMs 相关的常见性能陷阱,特别是在涉及非 Nanite 几何体、快速摄像机移动或复杂材质设置的场景中。您将如何在底层诊断和缓解这些问题,并引用相关的引擎控制台变量和潜在的 C++ 考量?

Answer: Unreal Engine 5 的 Virtual Shadow Maps (VSMs) 代表了实时阴影渲染的范式转变,旨在为广阔的、动态照明的开放世界提供一致的、高分辨率的阴影,尤其是在与 Nanite 虚拟化几何体结合使用时。

VSMs 的核心机制: VSMs 在概念上是超高分辨率的阴影贴图,有效提供了 16k x 16k 像素的虚拟分辨率,并通过 clipmaps 进一步扩展了定向光的解析度。为了高效管理这种巨大的分辨率,VSMs 采用了一种 基于页面的 流送系统。虚拟阴影贴图被划分为 128x128 像素的瓦片(页面)。只有根据深度缓冲区分析,屏幕上像素着色所需的页面才会被分配和渲染。这些页面在帧之间进行缓存,并且只有当物体或光源移动时才会被失效并重新渲染,从而显著提高了性能。

与 Nanite 的关系: VSMs 的设计宗旨是与 Nanite 协同工作。Nanite 虚拟化几何体,通过在像素级别流送和渲染必要的细节,实现了前所未有的多边形数量。VSMs 将这种虚拟化扩展到阴影。Nanite 网格固有的自动 Level of Detail (LOD) 和剔除与 VSM 的基于页面的分配完美契合,因为 Nanite 可以高效地提供将精确阴影信息栅格化到虚拟页面所需的高细节几何体。当启用 VSMs 时,它们用统一的路径取代了许多传统的阴影方法(如 Stationary Light 预计算阴影、逐对象阴影和用于可移动光源的 Cascaded Shadow Maps),因为 Nanite 通常不支持这些旧技术。

实现柔和阴影: VSMs 通过一种称为 Shadow Map Ray Tracing (SMRT) 的技术,实现了具有接触硬化的逼真柔和阴影。SMRT 不像均匀模糊(如 Percentage-Closer Filtering)那样,而是产生更符合物理的半影,其中阴影在投射物附近更锐利,而在远处更柔和,模仿了真实世界的光源。柔和度可以受光源半径/角度的影响,并通过控制台变量如 r.Shadow.Virtual.SMRT.SamplesPerRay.Localr.Shadow.Virtual.SMRT.SamplesPerRay.Directional 进行控制。

常见性能陷阱:

  1. 非 Nanite 几何体:在非 Nanite 网格上使用 VSMs 可能会成为一个显著的性能瓶颈。非 Nanite 几何体在 VSM 系统内需要一条不同的、可能更昂贵的栅格化路径,通常会导致更高的 GPU 时间和视觉伪影,尤其是在摄像机移动期间。
  2. 快速摄像机移动 / 大面积遮挡解除 / 摄像机切换:快速的摄像机变化或场景中突然大面积可见可能会导致性能峰值。这是因为大量的 VSM 页面可能需要在单个帧中失效、分配和重新渲染,从而给 GPU 和内存带宽带来压力。
  3. 复杂材质设置:尽管 Nanite 旨在处理细节,但某些材质特性可能会影响 VSM 性能。具有复杂 World Position Offset (WPO)、遮罩混合模式或可编程栅格化的材质在贡献 VSM 深度通道时可能会很昂贵或出现问题。
  4. VRAM 使用:尽管是虚拟化的,VSMs 仍然需要大量的 VRAM 用于其物理页面池,尤其是在激进的分辨率设置或大量光源的情况下。高 r.Shadow.Virtual.ResolutionLodBiasDirectional 值或大量的 clipmap 级别会加剧这种情况。

诊断和缓解: 诊断:

  • Unreal Insights / stat gpu / profilegpu:这些是您的主要工具。在 profilegpu 中查找 Shadow Depths 或特别是 Render Virtual Shadow Maps Non-Nanite 的峰值。
  • VSM 可视化工具:使用编辑器内的可视化工具(例如,Show > Visualize > Virtual Shadow Maps)来查看页面分配、失效和非 Nanite 贡献。

缓解(底层):

  1. 优先采用 Nanite:对于大多数静态和高多边形动态网格,请确保它们已启用 Nanite。这是对 VSMs 影响最大的优化。如果网格无法使用 Nanite(例如,蒙皮网格、复杂 WPO),请评估它是否确实需要投射 VSMs,或者是否可以使用更简单的阴影方法(例如,胶囊体阴影、静态部分的预计算阴影)。
  2. 调整 VSM 质量设置:通过控制台变量:
    • r.Shadow.Virtual.Enable 0:作为最后手段,如果 VSMs 根本不适合您项目的性能目标,请禁用它们。
    • r.Shadow.Virtual.ResolutionLodBiasDirectional [-3 to 3]:降低定向光的此值可以降低 VSMs 的整体分辨率,从而减少 GPU 负载和 VRAM。
    • r.Shadow.Virtual.Clipmap.MaxResolution [例如,2048]:控制定向光 clipmap 级别的最大分辨率。降低此值可以节省 VRAM 和渲染时间。
    • r.Shadow.Virtual.MaxPhysicalPages [例如,4096]:限制 VSMs 可以分配的物理页面总数。请谨慎,过低可能会导致伪影。
    • r.Shadow.Virtual.SMRT.SamplesPerRay.Local/Directional [1-8]:降低这些值会使柔和阴影不那么平滑,但可以显著提高性能。
  3. 优化材质复杂度:对于投射 VSMs 的网格,请简化材质。避免不必要的 WPO、复杂的遮罩不透明度或自定义可编程栅格化阶段。如果这些功能至关重要,请考虑网格是否可以使用非 VSM 阴影方法。
  4. 管理动态对象和光源:尽量减少投射 VSMs 的高度动态对象的数量,特别是那些频繁移动或改变可见性的对象,以减少缓存失效。同样,限制具有高质量 VSM 设置的可移动光源的数量。
  5. 利用 World Partition:对于超大型世界,World Partition 有助于管理几何体的加载/卸载,通过减少任何给定时间需要考虑的潜在阴影投射物总数,间接帮助 VSMs。

C++ 考量(高级): 尽管 VSMs 主要是一个引擎级别的系统,但 C++ 程序员可能需要:

  • 自定义阴影投射:对于特定的问题 Actor,可以通过 UPrimitiveComponent::SetCastShadow(false)UPrimitiveComponent::SetCastVirtualShadowMap(false)(如果暴露)实现自定义阴影渲染路径或完全禁用 VSM 投射。
  • 动态 LOD/VSM 设置:可能需要编写 C++ 逻辑,根据性能预算或特定的游戏场景(例如,在快节奏的动作序列中降低 VSM 质量)动态调整 VSM 控制台变量或光源设置。
  • 调试引擎源代码:在极端性能情况下,可能需要深入研究引擎的 VSM 渲染代码 (Engine/Source/Runtime/Renderer/Private/VirtualShadowMaps/),以了解特定的瓶颈,甚至提出引擎级别的优化,尽管这是一项高度专业化的任务。

理解 Nanite、VSMs 及其底层基于页面的渲染之间的相互作用,对于优化 UE5 项目以实现视觉保真度和高性能至关重要。

Context: 候选人应展示对 Unreal Engine 5 核心渲染技术,特别是 Nanite 和 Virtual Shadow Maps 的深入理解。该问题深入探讨了它们的内部工作原理、依赖关系、性能特征和实际优化策略,要求候选人具备超越简单使用的知识。