Avowed: A GPU Technical Retrospective | Unreal Fest Orlando 2025

Avowed: A GPU Technical Retrospective | Unreal Fest Orlando 2025

项目与团队概况

游戏简介

  • Avowed 是一款 单人动作 RPG ,设定在 《Pillars of Eternity(永恒之柱)》 宇宙中
  • 支持 第一人称 / 第三人称 切换游玩
  • 战斗系统融合 近战、远程、法术 三种模式,重点围绕 剧情探索跑酷/导航
  • 发售平台: Xbox Series S/X 以及 PC
  • 地图规模:约 5 个大型开放区域 + 约 25 张硬加载(hard-loaded)地图

团队规模

  • 峰值约 100 名内部开发者 + 40 名外部协作开发者
  • 技术团队约 15 人 ,细分为:
    • 1 名技术总监
    • 2 名内部图形工程师 + 2 名外部图形协作开发者
    • 5 名内部引擎 & 工具程序员 + 2 名外部协作
    • 3 名技术美术 + 2 名灯光师
  • 工程人力相对紧张,因此在 引擎修改方向的选择上必须非常精准

项目技术演进与目标

引擎版本迁移

  • 项目最早在 UE4(4.22 / 4.23) 上启动
  • 最终以 Unreal Engine 5.3.2 发售

核心项目目标

  1. 开放或近开放世界 :支撑大型远景(vast vistas)
  2. 全动态时间变化(Time of Day) :不依赖大量预烘焙光照,需要 全局光照(GI) 支持
  3. 高度有机化的世界 :大量植被、洞穴等自然场景

采用的 UE5 核心技术

技术用途
Nanite几何体 LOD & 流送
Lumen全局光照 & 反射
Virtual Shadow Maps (VSM)阴影系统
Virtual Textures纹理流送
Niagara粒子系统

本次演讲重点聚焦前三项(Nanite、Lumen、VSM),附带简要提及 Virtual Textures。


各平台渲染配置详解

Xbox Series S

模式帧率内部分辨率Lumen 模式阴影方案反射方案
Quality30 Hz900p硬件光追 LumenVSM(方向光)+ 硬件光追 (局部光)硬件光追反射 (Lumen + 单层水面)
Balance40 Hz720p硬件光追 LumenVSM(方向光)+ 硬件光追(局部光)光追反射(Lumen)+ 屏幕空间反射 SSR (单层水面)

Xbox Series X

模式帧率内部分辨率Lumen 模式阴影方案反射方案
Quality30 Hz1440p硬件光追 LumenVSM(方向光)+ 硬件光追(局部光)全硬件光追反射
Balance40 Hz1280p硬件光追 LumenVSM(方向光)+ 硬件光追(局部光)同上规格
Performance60 Hz1080p ~ 1800p软件 Lumen全 VSM (方向光 + 局部光)软件 Lumen 反射 (含单层水面)

关键点 :Performance 模式退回软件 Lumen 并非因为 GPU 瓶颈 ,而是 CPU 限制 所致,这决定了他们在该模式下放弃硬件光追。


Nanite 深入分析

Nanite 的核心价值

  • Avowed 几乎对所有资产都启用了 Nanite ,这是项目成功的 最大技术贡献者之一
  • 三大优势:
    1. 远景支持 :巨大视野下几乎 无 LOD 弹跳(LOD popping)
    2. 内存控制 :主机端内存始终是严峻挑战,Nanite 的 mesh 流送(streaming)与裁剪(culling)能力对 内存预算控制至关重要
    3. 自动化 LOD 管理 :减少手工 LOD 链的维护成本

从 UE4 迁移到 Nanite 的挑战

1. 大量 Masked Material 的转换

  • UE4 遗留资产中广泛使用了 Masked 材质 (带 alpha test 的裁切材质),几乎所有资产都有"frilly bits"(透明边缘碎片)
  • 需要将这些 转换为不透明(Opaque)几何体 才能充分发挥 Nanite 性能
  • 这一工作由 美术团队和技术美术团队 联合完成,耗费 大量工时

2. Spline Mesh 的处理

  • 项目大量使用了 Spline Mesh (样条网格,例如道路、藤蔓等)
  • 当时 Nanite 不支持 Spline Mesh ,因此必须将其 烘焙为静态几何体

Nanite 的性能瓶颈分析

材质复杂度过高

  • 转换为 Opaque 后,Nanite 的主要瓶颈转移到了 材质(Material) 层面
  • UE4 时代因为几何量较少,习惯在材质中做大量运算(多纹理采样、复杂节点图),迁移后这些 过度复杂的材质 成为负担
  • Nanite 本身的 VGPR(向量通用寄存器)LDS/BLU(带宽/局部存储) 占用已经较高
  • 叠加复杂材质后,Occupancy(占用率,即 GPU 上同时运行的 wavefront 比例)始终偏低 ,无法充分隐藏延迟

Occupancy 是 GPU 性能的关键指标。VGPR 用量越高 → 每个 wavefront 占据的寄存器越多 → 可并发的 wavefront 数量越少 → occupancy 越低 → 延迟隐藏能力越差 → 性能下降。

Virtual Texture 导致纹理地址绑定(Texture Address Bound)

  • 由于大量使用 Virtual Textures(VT) ,Nanite 的 base pass 频繁出现 纹理地址计算瓶颈
  • VT 的间接寻址(indirection)比普通纹理采样更昂贵,加剧了这一问题

缺乏 UE 5.4+ 的优化特性

  • 项目基于 UE 5.3 ,无法使用后续版本引入的:
    • Nanite Compute Shading :用计算着色器替代像素着色器来做材质评估,减少冗余计算
    • VRS(Variable Rate Shading,可变速率着色) :降低非关键区域的着色频率
  • 因此所有像素 逐像素(pixel per pixel)全精度着色 ,没有任何节省手段

空 Draw Call 问题

  • 材质变体主要通过 Material Instances 实现,而非 Custom Primitive Data(CPD)
  • 后果:base pass 中产生大量 被裁剪掉但仍要发出的空 draw call(empty base pass draws)
  • 原因:在 UE 5.3 中,只要 mesh 被加载到内存,Nanite 就必须为其发出 draw call ,即使它完全被裁剪(culled away)
  • 实际表现:经常有 100+ 纳秒甚至更多 的 GPU 时间浪费在空 draw call 上
  • 未来的 Work Graphs 技术有望解决此问题(通过 GPU 驱动的工作调度避免空 dispatch)

Nanite Culling 过载与流送策略调整

Culling Pass 过载

  • 即使有裁剪,很多时候 流入场景的实例数量仍然过多 ,导致 Nanite 的 culling passes 本身开销过大

HLOD 烘焙不足

  • 早期项目没有严格执行 HLOD(Hierarchical LOD,层级 LOD) 烘焙
  • 后期 强制推行 HLOD 后,性能有明显改善
  • HLOD 的作用:将远处的大量小物体合并为少量简化 mesh,减少 Nanite 需要处理的 instance 数量

Streaming Cell 粒度调整

  • 最初的目标:1 个 Streaming Cell = 1 个 World Partition Tile
  • 实际情况:随着开发推进,需要 更多更小的 Streaming Cell ,以便:
    • 更早地将 膝盖高度及以下的小物体 从场景中卸载
    • 减少 LOD 0 级别的实例网格数量 ,降低内存与 culling 压力

Streaming Cell 是 World Partition 中控制资产加载/卸载的空间单元。更小的 cell 粒度意味着更精细的加载控制,但也增加了流送管理的复杂度。

植被(Foliage)渲染优化


从 UE4 到 UE5 的植被问题

UE4 时代遗留问题

  • 所有植被资产使用 非 Nanite 的 Alpha Mask 面片(masked cards)
  • 附带 高开销的风力模拟(wind sims)
  • 存在大量 视觉加载(visual popping/loading)问题
  • Virtual Shadow Maps(VSM) 完全不兼容,性能极差

初次尝试:直接将面片转为 Nanite Alpha Mask

  • 当 Nanite 开始支持植被后,团队第一时间尝试将所有植被转为 Nanite + Alpha Mask
  • 视觉加载问题修复了 ,但 渲染耗时完全没有改善
  • 本质上只是把 VSM 中糟糕的非 Nanite 耗时 转移成了 糟糕的 Nanite 光栅化耗时 ——开销只是换了个地方

核心优化策略:Masked → Opaque

关键决策

  • 最终团队决定 尽一切可能把植被材质从 Masked 转为 Opaque(不透明)
  • 具体做法是将原本依赖 alpha 遮罩的叶片面片,改为 实际建模出不透明的几何形状
  • 这一步 至关重要 ,因为它大幅减少了:
    • 逐像素操作(per-pixel operations) 的数量
    • Nanite 光栅化耗时
    • VSM 阴影深度 Pass 耗时

风力动画(Wind)处理

  • 仍然保留了风力效果,但做了大幅调整
  • 树木使用 VATS(Vertex Animation Texture Sheets) + WPO(World Position Offset)
  • 但发现 VATS 存在明显局限:
    • 顶点数量受限 ——不能有太多顶点
    • 内存开销昂贵
  • 虽然最终发售版本仍使用了 VATS,但团队计划 后续重新评估 ,特别是配合 UE5 新推出的 动态植被(Dynamic Foliage) 系统

WPO 距离裁剪

  • 即使在使用 WPO 的情况下,团队也 非常激进地按距离禁用 WPO
  • 有时候禁用距离被拉得 非常近 ,以确保光栅化耗时可控
  • 大量 地面杂物(clutter) 如三叶草、小花等 完全移除了 WPO ,甚至完全去掉

特殊情况:无法转为 Opaque 的资产

典型案例:掩护草(Cover Grass)

  • 掩护草需要在玩家进入时有 抖动淡出效果(dithering effect) ,因此 必须保留 Masked 材质
  • 针对这种无法避免的 Masked 资产,团队采取以下措施:
    1. 尽可能以不透明方式建模 ——减少 overdraw
    2. 严格限制顶点数量 ——每个草丛大约 3,000 顶点
    3. 保持稀疏分布(sparse placement) ——帮助 Nanite 以最优方式光栅化

优化前后性能对比

场景 1:Paradise 外景(Xbox Series S,30 Hz 模式)

指标Alpha 阶段(优化前)最终版本(优化后)
总帧时间~46.5 ms~30 ms
Nanite Vis Buffer~4–5 ms~2.5 ms
Base Pass~5 ms~3.5 ms
Shadow Depth Pass~13 ms~8 ms
Masked 材质光栅化~1.5 ms(单个材质)几乎消除

额外优化细节:

  • Nanite 的 硬件与软件光栅化重叠执行 改为 顺序执行 ,释放出 GPU 时间线空间
  • 然后将释放出的空间与 光追场景更新(Ray Tracing Scene Update) 重叠执行,进一步节省时间
  • VSM 开销下降也部分得益于 局部光源切换到光追阴影 (后续章节详述)

场景 2:Emerald Stair(Xbox Series S,30 Hz 模式)

指标Alpha 阶段(优化前)最终版本(优化后)
总帧时间~53 ms~28 ms
Nanite Vis Buffer~4–5 ms~2 ms
Base Pass~5 ms~3.5 ms
Shadow Depth Pass~20 ms~3 ms
  • 该场景中的 红色植被 在优化前仅阴影深度 Pass 就消耗了 ~6 ms
  • 优化后阴影深度 Pass 从 20 ms 暴降至 3 ms,降幅 85%
  • 最终甚至出现了 ~2 ms 的 Graphics Queue 空闲等待 ,说明 GPU 主管线已经在等待异步 Pass 完成,瓶颈不再是主管线

关键经验总结

优化手段效果
Masked → Opaque最关键 ,大幅降低光栅化 & 阴影开销
激进禁用远距离 WPO显著降低 Nanite 光栅化耗时
移除地面杂物的 WPO / 动画释放大量 GPU 预算
Masked 资产限制顶点数 + 稀疏放置让无法避免的 Masked 资产尽可能高效
调整 Nanite 硬件/软件光栅化执行顺序创造与其他 Pass 重叠执行的机会

Lumen 全局光照系统深入分析


为什么选择 Lumen

UE5 之前的 GI 方案评估

  • 团队在 UE5 之前评估了多种 全局光照(GI) 方案,发现都存在不足:
    • 质量不达标
    • 需要大量烘焙时间
    • 引擎集成成本巨大 ,拖慢开发节奏
    • 很多方案 三个问题同时存在

Lumen 是迁移到 UE5 的首要驱动力

  • Nanite 虽然效果亮眼且不可或缺,但如果只为 Nanite,团队 可能可以留在 UE4
  • Lumen 才是真正促使迁移到 UE5 的决定性因素
  • 最终证明 Lumen 表现出色:
    • 达成了所有光照设计目标
    • 整个游戏 仅有 2 名灯光师 就完成了全部工作
    • 核心原因:无需烘焙,所见即所得(WYSIWYG) ,极大提升迭代速度

硬件光追 vs 软件 Lumen

选择硬件光追的核心原因

  1. 地形(Landscape)光照反弹 :硬件光追能产生更自然的 光线弹射(light bounce) ,软件 Lumen 在地形反弹上明显不足
  2. 更高精度、更少漏光(light bleeding)
  3. 意外收获 :后期引入 光追阴影(Ray Traced Shadows) 时,硬件光追 Lumen 使过渡变得更容易

各平台异步计算优化

平台光追队列节省开销备注
Xbox完全运行在 Async Compute2–4 ms(Series S 测试场景)大量使用 Inline Ray Tracing ,节省性能与内存,无需单独的光追 Shader
PC使用 Graphics Queue原因:UE 的 D3D12 版本对 Inline Ray Tracing 支持不完整 ;但软件 Lumen 模式下仍使用 Async

画质对比

  • 演讲展示了三组软件 vs 硬件 Lumen 对比场景:
    • Obsidian Temple :硬件版本光照反弹更饱满
    • Shatterscarp :桥洞下方光照弹射质量明显更好
    • Emerald Stair 沼泽 :硬件光追整体光照准确性更高

Emissive(自发光)的巧妙应用

"发光蘑菇"系统

  • 团队大量使用 自发光资产(emissive meshes) ——灯光师昵称它们为 "Emissive Mushies(发光蘑菇)"
  • 在许多场景中,自发光资产 直接替代了传统灯光 ,节省灯光放置工作量

核心技巧:Ray Tracing Material Node 分离

  • 使用 Ray Tracing 材质节点 ,为 光栅化场景Lumen 光追场景 分别设定不同的 emissive 值:
    • 光栅化场景(屏幕显示):emissive 强度约 1
    • Lumen 光追场景:emissive 强度设为 5–10
  • 效果:大幅增强间接光照贡献,同时避免屏幕过曝(blowout)

隐藏 Emissive Mesh Cards

  • 团队仍然大量使用 隐藏的 emissive mesh cards 来补充光照
  • 性能影响极小,仅占用 Surface Cache 空间
  • 但需要 修改引擎 ,将它们从反射 Pass 中剔除:
    • 硬件光追反射 :简单——调整 Ray Tracing Mask ,新增一个标志位即可
    • 软件 Lumen 反射 :更复杂——需要为 Mesh Distance Fields 添加额外的剔除 Pass

补光手段

  • 当 Lumen 场景中噪声过多或高光缺失时,会退回使用 不投射阴影的补光灯(non-shadow casting fill lights) 作为最终手段

Lumen 面临的挑战

1. Pass 数量极多且调试困难

  • Lumen 内部的 渲染 Pass 非常多 ,且硬件与软件两套管线存在大量 分歧路径
  • 同时维护两套管线 工作量极大
  • 调试极其困难
    • Lumen 使用大量 间接调度(indirection) ——一个 Pass 生成 工作列表(worklists) ,另一个 Pass 消费
    • 这导致 GPU 线程与屏幕像素之间的关联被打断 ,无法简单地回溯某个像素的计算来源
    • 排查颜色错误或噪声问题变得非常困难

2. 内存压力(尤其是 Series S)

  • Lumen 使用大量 Render Targets 和工作缓冲区,显著增加显存占用
  • Xbox Series S 内存问题尤为严重
  • 关键妥协:在主机上完全放弃 Mesh Distance Fields(MDF) ,仅保留光追 BVH
    • 这带来了连锁负面影响:
      • 反射系统 在软件模式下受损
      • VFX 无法使用 Distance Field 节点 (如距离场碰撞等)
    • 结果:团队持续在 性能、质量和内存 三者之间反复权衡

3. 时间积累效果与游戏设计冲突

  • Lumen 是 时间累积(temporally accumulated) 的效果,需要多帧稳定才能收敛
  • 但 Avowed 有 时间跳跃(Time of Day jumps) ——例如从白天切到夜间营地
    • 解决方案:切换时 完全丢弃整个 Lumen 场景 ,在 黑屏淡入淡出(fade) 期间尽快重建
  • 闪烁灯光(flickering lights)本身 时间稳定性差 ,会引入噪声
  • 大量明亮 VFX 会干扰 Lumen 的屏幕空间效果

Surface Cache 管理问题

大型网格体浪费缓存

  • 项目大量使用 巨型单体网格(large monolithic meshes) 以营造宏大感
  • 问题:一座大山可能只有一小部分接触到游玩空间,却 占据了约五分之一的 Surface Cache
  • 这类浪费 往往难以及时发现 ,直到引发页面频繁驱逐(page eviction)和噪声问题时才暴露

水密网格的背面问题

  • 许多网格是 水密(watertight) 的,即有背面几何
  • 背面会生成 额外的 Mesh Cards ,进一步浪费 Surface Cache 空间
  • 最终只能靠 手动逐个检查和移除 ,耗时费力

Mesh Card 数量优化

  • 默认情况下 Lumen 为每个物体生成约 12 个 Mesh Cards
  • 对于简单物体(如一面墙),1–2 个就够了
  • 团队尝试减少 Mesh Card 数量,但有时会导致 只保留了背面的 Card
  • 最终解决方案:添加了一个 偏向修改器(bias mod)强制优先选择正面

Lumen 参数调优陷阱

CVar 和后处理参数的潜在风险

  • Lumen 有 大量 CVar 可调 ,外加上层的 Post Process 设置
  • 并非所有参数调整都是安全的,团队发现了以下问题:
    1. 内存浪费 :某些本应节省内存的设置反而浪费内存
    2. NaN 传播 :某些设置组合会在渲染管线中 产生并传播 NaN 值
    3. GPU 内存踩踏 :最严重的情况下,某些设置会导致 写越 LDS(Local Data Store)边界 ,造成内存踩踏

典型案例:Radiance Cache Screen Probes 内存浪费

  • 在 Xbox 默认设置( Screen Probe 分辨率设为 96 )下:
    • 纹理按 完整正方形 尺寸分配
    • 但实际索引使用 对数映射(logarithmic indexing) ,非 2 的幂次的部分会被钳制到最小值
    • 结果:约三分之一的纹理空间被浪费
  • 修复方式:修改纹理创建逻辑,按实际需要分配

Lumen 反射系统

异步计算迁移

  • UE 5.3 默认将 Lumen 反射放在 Graphics Queue
  • 团队发现迁移到 Async Queue 非常简单,强烈推荐执行此操作

Hit Lighting(命中点光照)

  • PC 端 成功添加了 Hit Lighting
  • Xbox 端 未能实现
  • 实际影响不大,因为绝大多数光追命中都落在 Surface Cache 中,Hit Lighting 仅影响 动态物体上的缺失光照

艺术家理解困难

  • Lumen 反射中 何种材质会触发反射追踪 对美术来说非常不直观
  • 例如:"粗糙度低于 0.4 的表面会触发追踪"——习惯用 Photoshop 的美术难以理解这意味着什么

单层水面(Single Layer Water)反射优化

默认行为的性能问题

  • UE 5.3 默认的单层水面反射是 全屏 Pass
    1. 先做一次 全屏 Screen Space Reflection(SSR)
    2. SSR 未命中的部分再做一次 全屏硬件光追反射
  • 画质好,但 性能开销极大

水下反射表面的双重开销

  • 如果 单层水面下方 存在反射表面(例如水下的反光冰面),系统会 对反射追踪支付两次费用
  • 团队在 Bingham's Domain 地图中发现了此问题
  • 修复方式:将 单层水面深度缓冲(depth buffer) 传递到反射 Pass,通过 深度测试 剔除水下反射表面

水面反射的默认"完美镜面"陷阱

  • 单层水面反射 默认总是完美镜面反射
  • 美术在水面上绘制泥垢纹理试图降低反射强度(例如沼泽场景),但 底层仍会执行完整的 Lumen 反射追踪
  • 修复方式:添加了一个 最大粗糙度追踪阈值(max roughness trace) 修改,并绑定到 Scalability 设置

软件 Lumen 无法使用(因丢弃了 Distance Fields)

  • 软件 Lumen 反射本来是更经济的替代方案
  • 但由于前述内存限制,团队在主机端 丢弃了 Mesh Distance Fields ,导致软件反射不可用

最终混合方案

  • 团队开发了一个 混合反射方案(Hybrid Approach)
    1. 保持 全分辨率 SSR
    2. Lumen 硬件光追反射改为 降采样执行(downsampled) ,以四像素为单位写出 mask
    3. SSR 未覆盖的区域,填入 低分辨率 Lumen 反射
  • 效果:
    • 静水或平坦水面 上几乎看不出差异
    • 粗糙表面 上也几乎不明显
    • Xbox Series S 上某场景节省了约 0.8 ms ,是一个可观的胜利

阴影系统详解


阴影技术组合概览

Avowed 使用了 三种阴影技术 的组合:

技术用途
Virtual Shadow Maps(VSM)方向光(Directional Light) 阴影
光追阴影(Ray Traced Shadows)局部光源(Local Lights) 阴影(在启用光追的模式下)
Contact Shadows(接触阴影)地面小物件细节阴影 + 对话场景光照

核心事实 :阴影是整个项目中 性能斗争最激烈的部分 ,甚至在引入光追阴影(实际上反而 节省了 性能)之前就已如此。阴影系统包含了 最多的引擎修改与新增代码


Virtual Shadow Maps(VSM)

基本评价

  • 相比传统 Cascade Shadow Maps ,VSM 毫无疑问更好
  • 搭配 Nanite 使用时 几乎是必选项
    • VSM 依赖 Nanite 才能获得合理性能
    • 反过来,使用 Nanite 时 也应该使用 VSM (除非走全光追阴影路线)
  • 方向光 效果极佳,支持 超远距离阴影 ,完美服务于开阔场景的远景需求

性能痛点

即便使用了 Nanite + VSM 组合,性能仍然存在挑战:

  • 非 Nanite 资产蒙皮网格(skinned meshes) 在 VSM 中开销很大
  • 逐像素运算类材质 对 VSM 性能打击严重:
    • Masked 材质
    • WPO(World Position Offset)
    • Pixel Depth Offset
  • 这也是团队 极力消除 Masked 材质尽早关闭 WPO 的另一个重要原因
  • Shadow Penumbra(阴影半影/软阴影) 有额外开销——需要对阴影贴图 多次采样 以获得柔和阴影边缘

关键优化修改(简单但高效)

1. 按距离减少阴影采样射线数

  • 做法极其简单,大约 一行代码
    • 场景深度(scene depth) ,对其取 log₁₀ ,据此调整采样数
  • Xbox Series S 在测试场景中节省约 0.5 ms

2. Nanite 实例级阴影剔除(按像素阈值)

  • Nanite 实例 Culling 阶段,对 低于特定像素阈值 的实例 直接剔除其阴影投射
  • 测试使用 24 像素 作为剔除阈值
  • 视觉差异 几乎不可察觉 (仅在远处柱子等处可能看出微小差别)
  • Xbox Series S 在测试场景中节省 超过 3 ms ——仅用 1–2 行代码
  • 这是 投入产出比极高 的优化

3. 蒙皮网格使用光追替代 VSM 光栅化

  • 由于光追管线的负载通常 远低于 光栅化管线
  • 将蒙皮网格的阴影从 VSM 光栅化切换到 光追 ,在特定场景中节省约 1 ms
  • 画质差异 几乎看不出

4. 调整光源半径以降低投影开销

  • 项目后期新增了一个设置项,可以 调整 VSM 的光源半径(light source radius) 以降低投影计算成本
  • 用于 Xbox Series SSeries X 60 Hz 模式 以及 低优先级 NPC

光追阴影(Ray Traced Shadows)

为什么探索光追阴影

VSM 局部光的核心问题:失效重建(Invalidation)

  • 动态物体穿过局部光源 时,VSM 会 失效(invalidate)重新渲染所有相关阴影贴图
  • 开销 极其高昂
  • UE 提供了 Static Separate(静态分离) 机制来缓解,但会 翻倍内存占用 ——主机平台无法承受

光追阴影的天然优势

  1. 仅追踪屏幕可见像素 ——不追踪被遮挡或屏幕外的内容
  2. 软阴影/半影免费获得 ——无需额外多次采样
  3. 无额外 Pass 成本 ——因为已经在使用硬件 Lumen,光追场景已经构建完毕,直接复用
  4. 灵活的部分阴影方案
    • 可以让某些物体 在 VSM 中投射阴影但不在光追中投射 ,或反之
    • 典型用例:洞穴顶部 可以接收阴影但不在局部光上投射阴影
    • 树木 也经常这样处理以节省性能

实现时间线与使用范围

  • 实现时间较晚 :在 5.3 升级之后 ,距离发售约 一年左右
  • 所有启用光追的平台上,所有局部光源均使用光追阴影

自定义光追阴影 Pass

为什么不用 UE 内置光追阴影

UE 5.3 内置的光追阴影方案 性能极差

  • 每个光源使用 一个全屏 Dispatch + 一个全屏 Render Target
  • 8 个光源 = 8 次全屏 Pass + 8 个 Render Target ,再加上各自的去噪
  • 完全无法承受

自定义 Pass 的设计

  • 设计思路类似 VSM 的 One Pass Projection
  • 使用 Clustered Light Grid追踪阴影写回到打包的阴影遮罩(Packed Shadow Mask)
  • 该方案 先于 MegaLights 出现,且 MegaLights 在 5.3 时仍为实验性功能

两种模式:

模式适用平台采样策略特点
全屏 Pass高端 PC每光源 ≥ 1 sample/pixel最高质量
棋盘格模式(Checkerboard)主机 & 低配渲染棋盘格 → 上采样重建 → 对低置信区域重新追踪实际 < 1 sample/pixel/light
  • 默认支持每像素 最多 8 个光源 ——这也是给灯光师的限制("别放太多灯")
  • Xbox Series S 上棋盘格模式平均节省约 1–1.5 ms

体积雾阴影适配

  • UE 内置的体积雾使用常规光追 Shader,但团队已经移除了那些 Shader(全部改为 Inline)
  • 因此需要 为体积雾编写 Inline 版本的光追阴影
  • 巧妙优化:重构体积雾函数,使阴影计算 提前启动 ,然后 隐藏在 Async Lumen 后面
  • 在没有方向光的室内关卡(如洞穴)中,Async Lumen 运行时间较长,体积雾光追阴影 完全免费

性能对比数据

场景 1:Fior(Xbox Series S,30 Hz)

状态总帧时间VSM Shadow Depth光追阴影VSM Projection
Alpha 版本(全 VSM)~40 ms~11 ms~8 ms
最终版本(光追替代局部光 VSM)~25 ms~3 ms~3 ms极小
假设关闭光追、仅用 VSM~29 ms~9.5 ms~2 ms
假设使用 UE 内置光追阴影~34 ms~18 ms(含去噪)

自定义光追 Pass 相比 UE 内置方案节省约 15 ms ,编写完全值得。

场景 2:Nakukubell(Xbox Series S,30 Hz)

状态总帧时间VSM Shadow Depth光追阴影体积雾阴影
Alpha 版本~37.5 ms~13 ms顺序执行,有开销
最终版本~23 ms0(无方向光)~3.5 ms~2 ms(免费,隐藏在 Async Lumen 后)
假设关闭光追、仅用 VSM~25.5 ms~9.3 ms

核心规律 :场景中 动态物体和角色越多 ,光追阴影相比 VSM 节省越多


光追管线的其他优化与注意事项

Async 优化

  • UE 5.3 中并非所有光追 Pass 都在 Async Compute 上运行,部分仍在 Graphics Queue
  • 团队 将所有光追 Pass 移到 Async ,实施简单且效果良好

光追场景更新(RT Scene Update)时序调整

  • UE 默认将光追场景更新安排在 Base Pass 期间
  • 团队将其 提前到 PreZ 和 Nanite Vis Buffer 阶段 ,节省了时间

地形(Landscape)每帧重建问题

  • UE 默认行为:地形 每帧都重建光追加速结构 ,以保持与 Landscape Streaming 同步
  • 团队 禁止了这一行为
  • 在极端流送性能下降时可能导致 地形与光追不同步 ,但实际上 没有人注意到或报告过问题

内存优势

方案Xbox Series S 峰值内存
光追(BVH/BLAS)~300 MB
Distance Fields(估算)~500–600 MB
  • 光追方案节省约 200–300 MB 内存
  • 避免了 加载新 World Partition 流送单元时 Distance Field 更新引起的 GPU 大尖峰(spikes)

光追的局限:CPU 成本

  • 光追在 UE 5.3 中有 较高的 CPU 开销 ,主要来自 收集光追场景(collecting the RT scene)
  • 这是 Xbox Series X 60 Hz 模式Series S无法使用光追 的根本原因
  • CPU 时间已经吃紧,光追场景收集会将其推过预算上限
  • 因此这些模式退回 全 VSM + 软件 Lumen

Virtual Textures(虚拟纹理)


使用范围与收益

  • 游戏中 几乎所有纹理 都使用了 Virtual Texture 系统
  • 例外情况
    • VFX 纹理 ——需要即时可用,无法等待反馈加载
    • RVT(Runtime Virtual Texture) 纹理——同理
  • 核心收益:
    • 在更高分辨率下使用 更多纹理
    • 降低并稳定内存占用 ——对主机平台尤为重要

性能代价:纹理寻址瓶颈

Texture Address Bound 问题

  • Virtual Textures 导致 几乎所有使用它的地方都变成纹理寻址瓶颈(texture address bound)
  • 无法从 顶点/像素着色器预缓存(vertex pixel shader pre-caching) 中获益
  • 纹理寻址压力 非常大

纹理池管理(Pool Management)

池隔离策略

  • 团队发现即使纹理池 没有超额订阅(over-subscribed) ,环境纹理也会 踢掉角色纹理
  • 这导致从 游戏画面切换到对话场景 时出现明显的 纹理流送跳变
  • 解决方案——按用途隔离纹理池
纹理类型压缩格式分辨率
角色纹理BC71K
环境纹理DXT12K
  • 未来希望能找到 在同一池内仍能做优先级隔离 的方案

池数量限制与陷阱

  • 默认上限 15 个池 ,超出会 硬崩溃(hard crash)
  • 可以提升到 31 个 ,但会 翻倍管理开销 ——在内存受限的主机上不理想
  • 非常容易意外创建不需要的池
    • 典型案例:美术人员连接了引擎默认的 1×1 RGBA8 法线贴图 ,导致系统创建一个 不需要的 RGBA 池
    • 解决方案:创建一个 2×2 的替代纹理 ,使其可以被 块压缩(block compress) ,归入已有的目标池

材质分层系统的竞态条件 Bug

  • Material Layering(材质分层) 系统中发现一个严重问题:
    • 如果美术在 Base Material Function 中使用了某个纹理,并在分层系统中依赖它
    • 该纹理 不会被正确添加到 材质加载前需要预缓存的 纹理数组
    • 导致 纹理加载与材质加载之间的竞态条件(race condition)
    • 如果材质先加载完成 → 无效的 Virtual Texture 绑定(invalid VT binding)持续到材质被重新加载
  • 最常见表现: 角色法线贴图丢失 ——因为法线纹理是最容易被错过的

总结与最佳实践


Nanite 最佳实践

  • 偏好更多几何体 + 更简单材质 ——Nanite 更擅长处理高几何复杂度,而非高材质复杂度
  • 尽一切可能避免逐像素评估开销
    • 消除 Masked 材质
    • 关闭 Pixel Depth Offset(PDO)
    • 尽早禁用 WPO(World Position Offset)
  • 减少材质变体(material variances)
    • Nanite 倾向于按 材质批次 绘制——材质种类越少,Draw Call 越少
    • 尽可能通过 Custom Primitive Data(CPD) 实现材质变化,而非创建新材质
  • 配合 World Partition 控制加载内容——Nanite 光栅化虽快,但 大量无意义的小物件仍会拖慢速度

Lumen 最佳实践

  • 密切关注 Surface Cache 中的内容 ——非常容易浪费空间
  • 谨慎调整 CVars
    • 需要了解每个 CVar 的具体作用
    • 逐个调整 ,不要一次性批量修改——否则容易陷入无法排查的问题
  • 从硬件光追开始,再降级到软件 ,比反方向容易得多:
    • 如果项目刚启动,建议 默认选择硬件光追 Lumen
    • 后续需要时降级到软件模式远比从软件升级到硬件简单

阴影最佳实践

  • 不要固守单一阴影方案 ——混合使用效果最好:
    • VSM → 方向光(表现优秀)
    • 光追阴影 → 局部光(点光源、聚光灯、矩形光)
    • Contact Shadows → 不要忽视它们!团队内部曾对其有"昂贵"的错误印象,但实际上 比其他方案便宜得多

Virtual Textures 最佳实践

  • Virtual Textures 解放了纹理使用自由度,但并非零成本
  • 时刻关注纹理池状态

通用工程实践建议

基准硬件选择

  • 尽早确定基准测试硬件 ,团队强烈推荐 使用主机
    • 硬件固定,无其他后台进程干扰
    • 不受显卡驱动更新影响
  • Avowed 选择 Xbox Series S 作为基准——效果非常好
  • 在多平台发售时,找到 Series X 上与 Series S 性能表现对等的设置 ,以此为基线逐步调整

开发期间禁用动态分辨率

  • 开发阶段必须关闭 Dynamic Resolution
  • 动态分辨率应仅用于 应对发售版的瞬时帧率尖峰 ,而非掩盖性能问题
  • 开着它相当于 把头埋进沙子 ——完全不知道 GPU 真实负载状态

优化时关闭异步工作

  • 在分析和优化特定渲染 Pass 时, 务必关闭 Async Compute
  • 异步重叠会 扭曲计时结果 ——你可能以为取得了进展,实际只是工作重叠方式变了

持续性能监控

  • 每周性能例会
    • 让 QA 人员玩游戏,工程师旁观并记录
    • 即使不立即修复,也至少 了解当前性能状况 和变化趋势
  • 自动化回放系统(Autoplayer)
    • 强烈推荐——帮助跟踪优化进展
    • 覆盖手动游玩无法触及的场景区域

保留并定期回顾旧的 GPU 抓帧

  • 在开发过程中保留大量 PIX 等工具的 GPU 抓帧
  • 每次 引擎升级大量内容提交 后,对比旧抓帧检查 性能回退(regression)
  • 实际案例:
    • FSR 2 升级到 FSR 3 后,出现了 约 1–3 ms 的性能回退
    • 长时间未被发现,直到回顾旧抓帧才注意到
    • 对比后发现 FSR 3 相对 FSR 2 画质提升不大 ,于是 回退到 FSR 2 ,直接省下这部分开销