Avowed: A GPU Technical Retrospective | Unreal Fest Orlando 2025
Avowed: A GPU Technical Retrospective | Unreal Fest Orlando 2025
1. 项目简介与背景
-
演讲者: Matt Campbell (黑曜石娱乐,高级图形工程师 / 《Avowed》首席图形工程师)
-
游戏类型: 单人动作 RPG (设定于“Pillars of Eternity”宇宙),支持第一/三人称。
-
游戏特性: 混合了近战、远程和法术战斗;5个大型开放区域;约25个独立加载的地图。
-
团队规模: 峰值时约100名内部开发 + 40名外部协力。
-
技术团队 (15人):
- 图形工程师: 2名内部 (含演讲者) + 2名外部协力。
-
关键启示: 图形团队规模非常精简,这意味着必须“精确”地进行更改,高度依赖引擎原生功能并进行重点优化。
-
-
项目历程与目标:
-
起点: UE 4.22 / 4.23
-
发售版本: UE 5.3.2
-
核心目标:
-
开放或近乎开放的世界,拥有广阔的远景 (Vistas)。
-
完全动态的日夜循环 (Time of Day)。
-
大量使用全局光照 (GI) 的自然光照。
-
充满植被、洞穴的有机世界。
-
-
-
核心技术栈 (UE5):
-
Nanite
-
Lumen
-
Virtual Shadow Maps (VSMs, 虚拟阴影贴图)
-
Virtual Textures (虚拟纹理)
-
Niagara
-
(本次分享重点关注前三项)
-
2. 主机性能目标 (已发售状态)
讲座详细列出了 Xbox Series S 和 X 的不同模式配置,这对于理解性能权衡至关重要。
Xbox Series S (XSS)
-
质量模式 (Quality @ 30Hz):
-
分辨率: 900p 内部
-
Lumen: 硬件光追 (Hardware Ray Tracing)
-
阴影: VSM (用于方向光) + 硬件光追 (用于本地光)
-
反射: 硬件光追 (用于 Lumen 和水面)
-
-
平衡模式 (Balance @ 40Hz):
-
分辨率: 720p 内部
-
(光照/反射配置与质量模式相同)
-
Xbox Series X (XSX)
-
质量模式 (Quality @ 30Hz):
-
分辨率: 1440p 内部
-
Lumen: 硬件光追
-
阴影: VSM (方向光) + 硬件光追 (本地光)
-
反射: 完全硬件光追
-
-
平衡模式 (Balance @ 40Hz):
-
分辨率: 1280p 内部
-
(光照/反射配置与质量模式相同)
-
-
性能模式 (Performance @ 60Hz):
-
分辨率: 1080p (动态)
-
Lumen: 软件光追 (Software Lumen)
-
阴影: 完全 VSM (用于方向光和本地光)
-
反射: 软件 Lumen 反射
-
关键备注: 切换到软件光追和 VSM 并非因为 GPU 限制,而是因为 60Hz 模式下遇到了 CPU 瓶颈。
-
3. 深入探讨:Nanite
Nanite 被认为是《Avowed》项目成功的最大贡献者之一。
核心贡献
-
实现广阔远景: 提供了极少的 LOD (Level of Detail) 突变。
-
内存控制: 对主机内存管理“至关重要”,所有网格物体都进入了 Nanite 池。
从 UE4 迁移的挑战
-
需要 Art (美术) 和 TechArt (技术美术) 团队付出巨大努力。
-
Masked Materials (遮罩材质): UE4 时期遗留了大量使用遮罩材质(例如带Alpha测试的植被)的资产。为了性能,这些都必须转换为不透明 (Opaque) 几何体。
-
Splines (样条线): 当时的 Nanite 不支持样条线,团队必须将其烘焙 (Bake) 为静态几何体。
Nanite 性能瓶颈
当几何体问题解决后,瓶颈转移到了材质上。
-
复杂材质:
-
问题: 最大的瓶颈。这是 UE4 时代的遗留问题——当时几何面数受限,因此大量逻辑被塞进材质中。
-
影响: 导致过高的 VGPR (向量通用寄存器) 和 ALU (算术逻辑单元) 使用率。
-
结果: GPU Occupancy (占用率) 过低。
-
-
虚拟纹理 (VT): 大量使用 VT 导致频繁受限于 Texture Address Bound (纹理寻址)。
-
UE 5.3 的局限:
-
没有 Nanite Compute Shading (计算着色)。
-
没有 VRS (Variable Rate Shading, 可变速率着色)。
-
结果: 必须支付“Pixel-per-Pixel (逐像素)”的全部渲染成本。
-
-
Draw Call (绘制调用) 问题:
-
问题: 团队主要通过 Material Instances (材质实例) 来实现资产变体,而非 Custom Primitive Data (自定义图元数据)。
-
影响: Nanite 必须为每一个加载的实例发出 Draw Call,即使该实例后续被剔除。这导致了大量“空的 Base Pass 绘制调用”,耗时约 100-200 纳秒。
-
未来方案: Work Graphs (工作图) (当时他们没有)。
-
-
剔除与流式传输 (Culling & Streaming):
-
问题: Nanite 的剔除通道 (Culling Pass) 负载过高(流式加载了过多数据)。
-
优化 1: 在开发后期,强制执行 HLoDs (Hierarchical LODs) 的烘焙。
-
优化 2: 调整流式传输大小。放弃了“一个 World Partition 区块 = 一个流式单元”的理想化设定,改为使用更细粒度的单元,以便更早地剔除“膝盖高度及以下”的小型杂物。
-
4. 深入探讨:Nanite 植被 (Foliage)
植被是另一个需要美术团队投入巨大精力的领域。
初始问题 (UE4 状态)
-
资产是非 Nanite 的遮罩卡片 (Masked Cards)。
-
使用了昂贵的风力模拟 (Wind Sims)。
-
存在严重的视觉 LOD 问题。
-
在 VSM 下的性能极差。
失败的尝试:直接转换
-
尝试: 简单地将 Alpha Masked 卡片直接转为 Nanite 资产。
-
结果:
-
[赢] 修复了 LOD 突变问题。
-
[输] 完全没有解决渲染时间问题。
-
结论: “只是把糟糕的‘非 Nanite VSM 耗时’变成了糟糕的‘Nanite 栅格化耗时’。”
-
关键优化:转向 Opaque
-
核心方案: 将几乎所有植被从 Masked (遮罩) 材质改为 Opaque (不透明) 材质。
-
原因: 这是“至关重要的”,它极大地减少了逐像素操作 (Per-Pixel Operations),使 Nanite 栅格化尽可能快。
风力效果 (Wind Effects)
-
树木: 仍然使用了风效,但改用 VATS (Vertex Animation Textures, 顶点动画纹理) 和 WPO (World Position Offset, 世界位置偏移)。
- 限制: VATS / WPO 内存开销昂贵,限制了顶点数量。
-
关键优化:
-
在远处积极地禁用 WPO: “我们把禁用距离拉得非常近”,这对保持栅格化速度至关重要。
-
移除 WPO: 最终,许多地面杂物(如三叶草、小花)完全移除了 WPO。
-
特殊情况:遮蔽草 (Cover Grass)
-
问题: 某些资产无法设为 Opaque(例如,角色进入时需要 Dithering 抖动效果的草)。
-
缓解措施:
-
像 Opaque 一样建模: 尽量减少 Alpha Overdraw(过绘制),使用更贴合形状的几何体,而不是简单的透明卡片。
-
限制顶点数: 约 3000 顶点。
-
保持稀疏 (Sparse): 几何体保持稀疏似乎有助于 Nanite 更好地进行栅格化。
-
5. 性能对比:优化前后 (XSS @ 30Hz)
以下数据清晰地展示了上述 Nanite 和植被优化的惊人效果。
场景 1: Paradise
-
Alpha (优化前):
-
总帧时: 46.5 ms
-
Nanite Vis Buffer: 4.0 ms
-
Base Pass: 5.0 ms
-
Shadow Depth Pass (阴影深度): 13.0 ms
-
(其中一个遮罩材质的叶子就占了 1.5 ms)
-
-
Shipped (优化后):
-
总帧时: 30.0 ms
-
Nanite Vis Buffer: 2.5 ms (时序被调整,与光追场景更新重叠)
-
Base Pass: 3.5 ms
-
VSM Cost: 8.0 ms (部分降低也是因为本地光转为了 HWRT)
-
场景 2: Emerald Stair
-
Alpha (优化前):
-
总帧时: 53.0 ms
-
Shadow Depth Pass: 20.0 ms (!!!)
-
(其中红色植被的阴影耗时 6.0 ms)
-
-
Shipped (优化后):
-
总帧时: < 28.0 ms
-
Vis Buffer: 2.0 ms
-
Base Pass: 3.5 ms
-
Shadow Depth Pass: 3.0 ms
-
(优化后的新问题:出现了 2ms 的 Graphics Q Stall,等待 Async Pass)
-
6. 深入探讨:Lumen
Lumen 是团队决定从 UE4 迁移到 UE5 的首要驱动力 (primary driver)。虽然 Nanite 很棒,但 Lumen 提供的动态全局光照 (GI) 是实现项目艺术目标不可或缺的。
为什么选择 Lumen?
-
在 UE5 之前,团队评估了多种 GI 解决方案,但它们都存在严重缺陷:
-
质量 (Quality) 不足。
-
需要漫长的烘焙 (Baking) 时间。
-
引擎集成成本 (Integration Costs) 巨大。
-
-
Lumen 的出现解决了所有这些问题,是项目转向 UE5 的决定性因素。
Lumen 的巨大成功:小团队实现大目标
-
Lumen 完美达成了所有光照目标。
-
WYSYWYG (所见即所得): 无需烘焙的特性,极大地提升了美术的迭代速度 (Iteration times)。
-
这使得仅有 2 名光照师 (Lighters) 的精简团队得以完成整个游戏的庞大光照工作。
核心决策:硬件光追 (HWRT)
团队从项目早期就决定专注于硬件光追 (HWRT),而不是软件 Lumen。
-
主要原因: 来自地貌的间接光反弹 (Light bounce from landscapes)。
- HWRT 在这方面提供了远超软件 Lumen 的自然感和准确性。
-
其他优势:
-
更少的 漏光 (Light bleeding)。
-
意外的胜利: 为项目后期转向使用光追阴影(用于本地光)奠定了基础。
-
平台实现差异 (HWRT)
-
Xbox (Series S/X):
-
Lumen HWRT 完全运行在 Async Compute (异步计算) 队列上,在测试场景中节省了 2-4 毫秒 (XSS)。
-
在 XSS 上大量使用了 Inline Raytracing (内联光追),这极大地节省了性能和内存(因为它不需要单独的光追着色器)。
-
-
PC:
-
Lumen HWRT 运行在 Graphics Queue (图形队列) 上。
-
原因: Unreal 5.3 中 D3D12 的 Inline Raytracing 支持不完整。
-
(但 PC 上的软件 Lumen 确实会切回 Async Compute)
-
关键技术:Lumen Emissives (自发光)
这是 Lumen 的一个巨大胜利,被光照师戏称为 "Emissive Mushies" (自发光蘑菇)。
-
广泛应用: 团队在地图上放置了大量自发光网格体,在很多情况下将其作为光源的完全替代品。
-
核心技巧 (Trick):
-
使用了 Raytracing Material Node (光追材质节点)。
-
这允许为资产的栅格化视图 (Diffuse) 和 Lumen 场景 (GI) 指定不同的自发光值。
-
示例: 栅格化场景中
Emissive = 1(视觉上不刺眼),但在光追(Lumen)场景中Emissive = 5或10。 -
结果: 极大地增强了 GI 亮度,而不会导致资产本身过度曝光 (Blowout)。
-
-
隐藏的自发光体 (Hidden Emissive Mesh Cards):
-
团队也使用了大量隐藏的自发光片来补光。
-
性能影响: 极小,只会占用 Surface Cache (表面缓存) 空间。
-
引擎修改: 必须修改引擎以将这些隐藏卡片从反射通道 (Reflection passes) 中移除。
-
HWRT: 很容易,只需添加一个新标记并调整光追掩码 (Raytracing Masks)。
-
Software Lumen: 更复杂,需要为 Mesh Distance Fields 添加额外的剔除通道。
-
-
-
后备方案 (Fallback):
- 当 Lumen 场景噪点过多或丢失高光时,团队会回退使用无阴影的填充光 (Non-shadow casting fill lights) 来补充。
Lumen 的挑战 (1): 复杂性与调试
Lumen 是一个“极其复杂的猛兽”。
-
海量的通道 (Passes):
-
Lumen 自身有非常多的渲染通道。
-
当需要同时维护硬件 (HWRT) 和软件 (Software) 两条管线时,通道数量会翻倍,虽然有重叠,但差异也很大。
-
-
极难调试 (Difficult to debug):
-
间接性 (Indirection): Lumen 严重依赖“一个通道生成工作列表 (Worklists),另一个通道来消费”的模式。
-
核心问题: 这种模式打破了 GPU 线程和屏幕像素之间的直接关联。
-
结果: 当出现噪点或颜色错误时,极难反向追踪到是哪一个 GPU 线程导致了问题。
-
Lumen 的挑战 (2): 内存与性能
-
内存集约 (Memory Intensive): Lumen 使用了海量的 Render Targets (渲染目标) 和 Workgroups,导致内存占用激增,这在 Series S 上是永恒的痛点。
-
关键权衡 (XSS):
-
团队无法同时负担 HWRT 的 BLAS (Bottom-Level Acceleration Structure) 和软件 Lumen 的 Mesh Distance Fields (MDFs)。
-
决策: 完全丢弃了 Mesh Distance Fields,全力押注 HWRT。
-
连锁反应: 这一决策导致很多 VFX(视觉特效)无法使用基于距离场(MDF)的节点,影响了特效表现。
-
-
时间稳定性 (Temporal Stability) 噩梦:
-
Lumen 是一个时间累积 (Temporally accumulated) 效果,但《Avowed》的机制频繁破坏这种稳定性。
-
日夜循环跳跃: 当玩家从白天瞬间切换到夜晚营地时,团队的解决方案是“字面上”丢弃整个 Lumen 场景 (throw away the entire lumen scene),然后在淡入淡出效果后重新构建。
-
闪烁的灯光: 本身就不是时间稳定的,会引入噪点。
-
明亮的 VFX: 经常会干扰 Lumen 的屏幕空间效果。
-
Lumen 的挑战 (3): 表面缓存 (Surface Cache)
-
极易浪费空间:
-
大型网格体: 游戏使用了很多巨型网格体(如山脉)。只要这些网格体“稍微触及”可玩空间,它就可能被载入 Surface Cache,导致一个不影响间接光照的山脉“浪费掉 1/5 的缓存”。这会导致缓存内容被频繁换出,增加噪点。
-
水密网格体 (Watertight Meshes): 包含背面 (Back faces),这些背面也会生成 Mesh Cards 并挤占缓存空间。
-
解决方案: 只能“手动”进入场景,标记这些网格体不进入缓存。
-
-
Mesh Card 优化:
-
默认情况下,Lumen 为每个物体生成约 12 个 Mesh Cards。
-
对于一面墙,可能 1-2 个就够了。团队尝试调低这个值。
-
新问题: 有时,Lumen 会“智能”地选择了背面的卡片(因为背面在室内)。
-
引擎修改: 增加了一个偏移 (Bias),以强制优先选择正面 (front faces)。
-
Lumen 的挑战 (4): 繁杂的设置与 CVar 陷阱
-
Lumen 有海量的“旋钮” (Knobs):CVars (控制台变量) 和 Post-Process (后处理) 设置。
-
危险: 团队发现某些设置调整后会导致严重问题:
-
浪费内存:(本应节省内存的设置反而导致了浪费)。
-
传播 NaNs: 在渲染管线中传播“非数值 (Not a Number)”,导致画面撕裂。
-
GPU 内存践踏 (Stomps): 导致 LDS (Local Data Share) 越界写入。
-
-
具体 Bug 示例:
RadianceCacheScreenProbes-
Xbox 默认值为 96。
-
引擎会分配一个 96x96 的方块纹理。
-
但索引逻辑 (Indexing logic) 却是对数的 (logarithmic),它会将 96 向下钳制 (Clamps) 到最近的 2 的幂(即 64)。
-
结果: 浪费了 1/3 的纹理内存。
-
修复: 团队修改了引擎中的纹理创建逻辑。
-
深入探讨:Lumen 反射
-
优化: 默认在图形队列,但团队“非常简单”地将其移动到了 Async Queue (异步队列)。
-
Hit Lighting: 为 PC 添加了此功能(用于动态物体),但未能在 Xbox 上实现。影响不大,因为大多数光追命中的是已在 Surface Cache 中的静态物体。
-
美术工作流问题:
-
光照师很难理解“什么东西会触发 Lumen 反射”。
-
美术习惯于处理纹理和渐变,而 Lumen 的反射是基于一个硬编码的粗糙度阈值(例如 0.4),这让美术非常困惑。
-
-
双重反射成本:
-
Lumen 反射“无视”单层水体 (Single Layer Water)。
-
问题场景: 在 Bingham's Domain 地图中,水下有反射性的冰块。
-
结果: 系统会为水面支付一次反射成本,然后再次为水下的冰块支付一次反射成本。
-
引擎修改: 团队将水体的深度缓冲区传递给 Lumen 反射通道,并进行深度测试来剔除水下的无效反射。
-
深入探讨:单层水体 (Single Layer Water) 反射
-
UE 5.3 默认状态: 一个全屏通道,首先做全屏屏幕空间反射 (SSR),未命中的像素再做全屏 HWRT。
- 问题: 效果好,但性能开销巨大。
-
备选方案:
-
软件 Lumen 反射: 更便宜,但团队已经丢弃了 MDFs,所以此路不通。
-
直接降采样: SSR 和 HWRT 绑定在一起降采样,导致 SSR 部分的质量急剧下降。
-
仅 SSR: 用于低配,但有漏光、曝光问题,且物体移出屏幕时反射消失。
-
-
最终方案:混合反射 (Hybrid Approach) (引擎修改)
-
第 1 步: 保持全分辨率的 Screen-Space Reflections (SSR)。
-
第 2 步: 运行一个降采样 (Coarse) 的 Lumen HWRT 通道,并输出一个掩码。
-
第 3 步: 仅在 SSR 未命中的像素上,才使用降采样后的(更粗糙的)Lumen 反射结果来填充。
-
结果: 效果非常好。在平静水面上几乎看不出差异;在粗糙水面上也难以察觉。
-
性能提升: 在 XSS 上的特定场景中节省了 0.8 毫秒。
-
-
水体反射的最后陷阱:
-
问题: 默认情况下,单层水体始终是完美镜面 (perfect mirror),无论其表面材质如何。
-
美术困惑: 美术师会在水面上绘制“淤泥”或“苔藓”(高粗糙度)来试图“遮挡”反射,但他们不知道系统仍在为下方的“完美镜面”执行完整的 Lumen 反射追踪。
-
引擎修改: 增加了一个基于粗糙度的最大追踪开关 (Max Roughness Trace),并将其绑定到可伸缩性设置 (Scalability) 中。
-
7. 深入探讨:阴影 (Shadows)
阴影是《Avowed》团队在性能上最大的痛点和挣扎点 (bulk of our performance struggles)。它们包含了团队最多的引擎修改和优化。
-
技术组合:
-
方向光 (Directional Map): Virtual Shadow Maps (VSMs, 虚拟阴影贴图)。
-
本地光 (Local Lights): 硬件光追 (Ray Tracing) (在开启 HWRT 的模式下)。
-
小型/地面物体: Contact Shadows (接触阴影)。
-
Virtual Shadow Maps (VSMs)
-
基础: VSM 毫无疑问优于传统的 Cascade Shadow Maps (CSM),尤其是配合 Nanite 时(两者几乎是强制绑定)。
-
优势: 完美实现了广阔远景中的远距离阴影。
-
性能陷阱:
-
非 Nanite 物体: 任何非 Nanite 的物体(如当时的蒙皮网格体 Skinned Meshes)都会造成巨大性能冲击。
-
逐像素操作 (Per-Pixel): VSM 对 Nanite 资产的逐像素操作极其敏感。
-
Masked Materials (遮罩材质)
-
WPO (世界位置偏移)
-
Pixel Depth Offset (像素深度偏移)
-
-
关键启示: 这就是为什么团队要“拼命”移除遮罩材质和尽早关闭 WPO 的核心原因之一。
- 半影 (Penumbra): VSM 的软阴影是通过多次采样实现的,这会带来额外成本,需要注意。
-
-
VSM 引擎修改 (关键优化):
-
减少远距离阴影光线 (Easy Mod #1):
-
实现: “大概就一行代码”,基于
SceneDepth(场景深度) 取对数,然后相应地减少远处的阴影光线数量。 -
收益: 在 XSS 上节省了约 0.5 毫秒。
-
-
基于像素阈值剔除阴影 (Easy Mod #2):
-
实现: 在 Nanite 实例剔除 (Instance Culling) 阶段,剔除掉小于特定像素阈值 (Pixel Threshold) 的物体的阴影。
-
效果: 演讲者展示了“剔除半径为 24 像素”与“不过滤”的对比,视觉差异“极难分辨”。
-
收益: “一到两行代码”带来了“巨大的节省”——在 XSS 的特定场景中节省了超过 3 毫秒。
-
-
复用光追数据 (HWRT Mod):
-
背景: 团队发现 HWRT 的负载 (Loads) 远低于栅格化 (Rasterization) 负载。
-
实现: 在 VSM 通道中,使用光追的蒙皮网格体 (Ray-traced skinned meshes) 来投射方向光阴影,而不是使用栅格化的版本。
-
收益: 在特定场景中节省了近 1 毫秒,且视觉差异极小。
-
-
调整光源半径 (Light Source Radius):
-
在项目后期添加的小型设置,允许调整 VSM 的光源半径来降低半影计算成本。
-
应用: 用于 XSS / XSX 60Hz 模式和低配 PC。
-
-
硬件光追阴影 (Ray Traced Shadows)
-
采用背景 (为什么?):
-
VSM 的核心问题: 本地光 (Local Light) 的 VSM 失效 (Invalidation) 开销极其昂贵。
-
当一个动态物体(如角色)穿过一个本地光时,会导致该灯光的 VSM 全部失效并需要重绘。
-
UE 的
StaticSeparate(动静分离)选项可以缓解此问题,但它会“加倍你的内存 (doubles your memory)”,这在主机上是不可接受的。
-
-
HWRT 的理论优势:
-
按需追踪: 只追踪屏幕上可见的像素,不关心被遮挡的或背后的物体。
-
免费的半影 (Free Shadow Penumbras): 光追阴影天然带软边,无需额外采样成本。
-
复用 HWRT Lumen: 团队已经为 Lumen 支付了 HWRT 的场景建立 (Scene) 和 BLAS/TLAS 成本,因此阴影的额外开销很小。
-
选择性阴影: 可以通过光追掩码实现“部分阴影”,例如让树木在 VSM(方向光)中投射阴影,但在 HWRT(本地光)中不投射阴影(以节省性能)。
-
-
HWRT 阴影实现 (引擎修改):
-
时机: 项目后期 (UE 5.3 升级后),距离发售仅约 1 年。
-
应用: 在 HWRT 开启时,用于所有本地光。
-
核心: 团队编写了自定义的渲染通道 (Custom Pass) 和降噪器 (Denoiser)。
-
原因: UE 5.3 原生的光追阴影 (Stock Unreal RTS) 性能极差。
-
原版实现是 “每个灯光一个全屏 Dispatch + 一个全屏 Render Target”。
-
“如果你有 8 个灯光,那就是 8 个全屏通道和 8 个 RT”,团队无法负担。
-
-
体积雾 (Volumetric Fog):
-
原生体积雾阴影依赖被团队删除的
Raytracing Shader。 -
团队不得不为其创建了一个内联 (Inline) 版本。
-
意外的胜利: 团队重构了体积雾函数,使其可以提早 (early) 启动阴影计算,然后将这部分成本完全隐藏 (hide their cost) 在 Async Lumen 通道的后面。
-
结果: 在许多没有方向光(只有本地光)的室内场景(例如洞穴),Async Lumen 的执行时间很长,这导致光追体积雾阴影的成本接近于“免费”。
-
-
-
自定义光追阴影通道 (Custom Pass) 详解:
-
理论: 类似于 VSM 的“单通道投影 (One Pass Projection)”。
-
实现: 使用剔除后的灯光网格 (Culled Light Grid),追踪阴影,然后将结果打包 (Packed) 写回一个阴影遮罩 (Shadow Mask)。
-
(该方案在 UE 的 Megalights 出现前提出了)
-
限制: 每个像素支持 8 个本地光(为了约束美术师,效果很好)。
-
双模式 (Two Modes):
-
全屏 (Full Screen): 每像素每灯光 >= 1 个采样 (SPP)。用于高端 PC。
-
棋盘格 (Checkerboard): (用于主机) 渲染棋盘格,然后升采样 (Upscale) + 重建 (Reconstruct),最后对“不自信”的像素进行重新追踪 (Retrace)。
-
实现了远低于 1 SPP 的平均成本。
-
在 Xbox 上平均节省了 1 到 1.5 毫秒。
-
-
-
性能对比:VSM vs HWRT (XSS @ 30Hz)
-
场景 1: Fior (室外,多动态物体)
-
Alpha (全 VSM): 40.0 ms
- (VSM Depth: 11.0 ms)
-
Shipped (HWRT 本地光): 25.0 ms (!!!)
- (VSM Depth: 3.0 ms; RTS: 3.0 ms)
-
对比 (HWRT vs VSM): 团队发现,如果强制切回 VSM,HWRT 方案比纯 VSM 方案快 4 毫秒。
-
对比 (HWRT vs UE 原生 RTS): 如果使用 UE 5.3 原生光追阴影,帧时高达 34.0 ms (光追+降噪占 18ms)。
-
结论: 自定义 HWRT 方案取得了巨大成功。
-
-
场景 2: Nakukubell (室内,无方向光)
-
Alpha (全 VSM): 37.5 ms
- (VSM Depth: 13.0 ms)
-
Shipped (全 HWRT 本地光): 23.0 ms
-
(RTS: 3.5 ms)
-
(Volumetric Shadows: 2.0 ms,但成本被 Async Lumen 隐藏,算作“免费”)
-
-
对比 (HWRT vs VSM): 切换回 VSM 会使帧时上升到 25.5 ms (VSM Depth 占 9.3 ms)。
-
最终结论: 场景中的角色和动态物体越多,HWRT 阴影方案的性能优势就越大。
-
光追的最后优化
-
问题: UE 5.3 中并非所有的光追通道都在 Async 队列上,有些仍在 Graphics 队列。
-
修复: 团队“非常容易地”将所有光追相关的通道都移动到了 Async Compute,效果很好。
8. 光追 (Ray Tracing) 的最终优化与成本
-
时序调整:
-
默认: 光追场景更新 (Ray Tracing Scene Update) 默认在 Base Pass 期间运行。
-
修改: 团队将其提早 (earlier) 到 Pre-pass 和 V-Buffer 期间运行,以重叠更多的计算,节省了时间。
-
-
禁用 Landscape 重建:
-
默认: UE 默认每帧 (every frame) 重建 Landscape 的光追数据,以保持与流式加载同步。
-
修改: 团队禁用了此行为。
-
结果: 在极端的流式性能问题下,光追数据(BLAS)确实会与地貌“不同步”,但“似乎没人关心或报告这个问题”,所以被认为是一个可接受的妥协。
-
成本权衡:HWRT vs. Mesh Distance Fields (MDFs)
-
内存 (Memory): [HWRT 巨大胜利]
-
HWRT 节省了大量内存。
-
在 XSS 上,团队的 HWRT 峰值内存占用约 300 MB。
-
如果使用 MDFs,预估总内存占用会达到 500-600 MB。
-
结论: HWRT 方案节省了 ~200-300 MB 内存。
-
附带收益: HWRT 避免了在加载新的 World Partition 流式单元时,因 MDF 更新而导致的巨大 GPU 尖峰 (Spikes)。
-
-
CPU: [HWRT 的主要瓶颈]
-
HWRT (至少在 UE 5.3 中) 具有高昂的 CPU 成本。
-
收集光追场景 (Collecting the Ray Tracing Scene) 的 CPU 耗时过高。
-
最终结论: 这就是团队无法在 Xbox 60Hz 性能模式下负担 HWRT 的根本原因。CPU 成本将其推高到了预算之上,迫使其在 60Hz 模式下回退到 Software Lumen 和 VSM。
-
9. 虚拟纹理 (Virtual Textures - VTs)
-
应用范围: 游戏中的几乎所有纹理都使用了 VT 系统。
-
例外: VFX (视觉特效) 和 RVT (运行时虚拟纹理) 相关的纹理,因为它们需要立即反馈,不能等待 VT 流式加载。
-
优势:
-
允许使用更多、更高分辨率的纹理。
-
降低并固定 (Lowered and fixed) 了内存使用量。
-
-
劣势与挑战:
-
性能瓶颈: 导致团队“几乎在所有地方”都受限于 Texture Address Bound (纹理寻址)。
-
Shader Pre-caching: 无法从顶点/像素着色器预缓存中受益。
-
纹理池 (Pools) 平衡:
-
问题: 团队发现,即使在内存充足时,环境 (Environment) 纹理(DXT1, 2K)也经常会踢出 (kicking out) 角色 (Character) 纹理(BC7, 1K)。
-
影响: 这导致了从游戏切换到对话特写时,角色身上出现纹理流式加载 (streaming) 的瑕疵。
-
妥协: 团队不得不将环境和角色纹理分离 (Segregating) 到不同的池中,尽管他们更希望未来能有“同池但可分离”的管理方式。
-
-
纹理池创建陷阱:
-
限制: 默认最多 15 个池,否则硬崩溃 (hard crash)。(可增加到 31 个,但会使管理开销加倍)。
-
问题: 非常容易意外创建出不想要的池。
-
示例: 美术师连接了引擎默认的 1x1 法线贴图,这是一个
RGBA 8格式的纹理,导致系统创建了一个团队不想要的 RGBA 池。 -
修复: 团队创建了一个 2x2 并可进行块压缩 (Block Compress) 的纹理来代替它,以确保其能放入正确的池中。
-
-
材质分层 (Material Layering) Bug:
-
问题: 当在材质函数 (Material Function) 中使用 VT,并将其用于材质分层系统时,该纹理不会被正确添加到材质加载前所需的“预缓存纹理数组”中。
-
结果: 导致纹理加载和材质加载之间的竞态条件 (Race Condition)。
-
影响: 如果材质先加载,会导致无效的 VT 绑定 (Invalid virtual texture binding),直到材质被重新加载(例如角色法线贴图丢失)。
-
-
10. 最终总结与核心建议
💡 Nanite
-
宁要更多几何体,不要复杂材质 (More Geo, Simpler Materials): Nanite 在这种组合下表现最好。
-
不惜一切代价避免逐像素操作: 尽你所能摆脱 Masked Materials、PDO (Pixel Depth Offset),并尽早在远处关闭 WPO。
-
减少材质差异 (Material Variances): Nanite 按材质批次 (Material-wide) 进行绘制。材质实例越少 = Draw Call 越少。
-
使用 CPD: 尽可能通过 Custom Primitive Data (CPD) 来实现变体,而不是材质实例。
-
控制加载内容: Nanite 很快,但如果你同时加载太多“不需要被渲染的小东西”,它依然会被拖垮。
💡 Lumen
-
盯紧你的 Surface Cache: “极易浪费空间”,必须手动清理那些不该进入缓存的大型网格体。
-
谨慎调整 CVar: Lumen 的设置项非常多。调整时要小心,一次只改一个,并充分理解其后果,否则“会让你陷入大麻烦”。
-
从硬件光追 (HWRT) 开始: 团队的建议是,从 HWRT 降级到 Software 远比从 Software 升级到 HWRT 要容易。新项目应优先考虑 HWRT。
💡 阴影 (Shadows)
-
不要迷信单一方案: VSM 非常适合方向光,但在本地光上表现挣扎。
-
HWRT 阴影是本地光的绝佳搭档: HWRT 阴影(点光源、聚光灯)效果很好,如果你的项目已经启用了 HWRT (例如为了 Lumen),强烈建议研究它。
-
别小看接触阴影 (Contact Shadows): 团队发现它们“被误解为昂贵”,但实际上它们比其他方案(VSM/HWRT)便宜得多。
💡 虚拟纹理 (VTs)
-
“它们解放了你 (Liberating),但它们不是免费的 (Not Free)。”
-
时刻监控你的纹理池。
11. 跨领域的开发建议 (Agnostic Advice)
-
尽早确定基准硬件 (Benchmark Hardware):
-
主机 (Consoles) 是最理想的,因为它们配置固定,没有驱动或后台进程的干扰。
-
《Avowed》团队选择了 Xbox Series S (XSS) 作为他们的性能基准,并发现这个选择“非常非常好”。
-
在多平台发布时,在 XSX 上找到与 XSS 相匹配的性能设置,有助于保持开发步调一致。
-
-
开发期间必须关闭动态分辨率 (Disable Dynamic Resolution):
-
动态分辨率“应该用于覆盖游戏中的性能尖峰,而不是掩盖持续的性能不足 (cover up non-performance)”。
-
在开发时开启它,无异于“把头埋在沙子里”,你根本不知道 GPU 的真实状态。
-
-
优化时关闭异步计算 (Turn off Async):
-
在分析特定通道(Pass)的性能时,必须关闭 Async Compute。
-
否则,工作负载的重叠 (Overlap) 会“扭曲 (skew)” 你的计时,让你误以为自己取得了进展。
-
-
永远在分析 (Always Be Profiling):
-
每周性能会议: 团队每周开会,让 QA 玩游戏,工程师实时观看、评论并抓取性能快照 (Captures)。这能让你时刻了解游戏的真实状态。
-
自动化播放器 (Autoplayer System): 强烈推荐。它能持续追踪性能进展。
-
保留并重访旧的快照 (Old Captures):
-
关键用途: 在进行大的引擎升级或内容合入后,检查是否有性能退步 (Regression)。
-
真实案例 (FSR): 团队在从 FSR 2 升级到 FSR 3 时,发生了 1 毫秒的性能退步,但几个月都未察觉。直到团队回头翻看旧的 FSR 2 快照,才发现了这个问题。
-
结果: 团队对比后发现 FSR 3 并未带来明显优势,于是回退到了 FSR 2,白白拿回了 1 毫秒的性能。
-
-