The Road to 60 fps in The Witcher 4 Unreal Engine 5 Tech Demo
The Road to 60 fps in The Witcher 4 Unreal Engine 5 Tech Demo | Unreal Fest Orlando 2025
演示背景与合作模式
- 本次演讲由 Epic Games 渲染团队负责人 Kevin 与 CD Projekt Red 引擎核心技术专家 Yoswaf 联合呈现
- 双方作为 一个大团队 协作,共同提出方案、互审代码,既打造了高质量 Demo,也同步 改进了引擎本身
- 合作是 双赢 的:没有 Epic 的引擎核心基础工作,CDPR 无法构建所需的游戏系统;没有 CDPR 的大型开放世界经验,引擎优化也缺乏真实场景驱动
演示目标与技术背景
核心目标
- 在 PS5 上以 60 FPS 运行的 开放世界技术演示 ,启用所有最新 UE5 特性
- 注意:这是当前技术状态的展示, 并非《巫师4》最终游戏画面
为什么选择 60 FPS
- 60 FPS 是一个平衡点:比 30/40 FPS 流畅得多,同时仍为 高品质视觉效果 保留了充裕的预算空间
- 目标是在 当前世代主机 上达成,确保在普通家庭电视和硬件上 切实可行
关键技术目标
| 目标领域 | 具体内容 |
|---|---|
| 硬件光追全局光照 | 漫反射 + 镜面反射的 GI,支持 动态昼夜循环 和运动物体的高质量反射 |
| 密集动态森林 | 构建技术骨干 + 全套管线(渲染 Pass、资产修改、场景组装),重点攻克 云杉树(巫师故乡最常见树种,也是最具挑战性的树种之一) |
| 大规模可扩展 | Demo 展示的只是所建世界的一小部分,森林延伸至地平线,且保证 无卡顿体验 |
| 鲜活的世界 | 村庄中 NPC 拥有日常行为,Ciri 只是访客,与 NPC 路径自然交叉 |
| 更好的硬件利用率 | 从关键路径移除任务、 异步 + 并行 执行 |
| 面向完整游戏设计 | 技术不仅服务于 Demo,必须能 扩展到完整的巫师游戏玩法 ;以主机限制为基准,所有改进同样惠及 PC 及未来硬件 |
CPU 性能与帧预算哲学
帧预算基本概念
-
60 FPS → 每帧仅有 16.66 ms
-
这 16.66 ms 需要覆盖三大阶段:
- 模拟场景(Game Thread)
- 为 GPU 准备数据(Render Thread)
- GPU 渲染
-
工程师的角色:帮助 gameplay 和美术部门理解 他们正在构建的场景,引导他们聚焦于正确的引擎特性,在 不牺牲画质和帧率 的前提下放大艺术愿景
两大场景类型与预算侧重
旷野森林(Wilderness)
- 大量 自然运动的植被 ,细节密集
- 依赖 体积雾效果 营造沉浸感和惊叹感
- 同时承担 远景长镜头 ,邀请玩家探索
- 需要在 手工制作区域 与 离线/运行时 PCG 散布 之间精心平衡
- 旅行过程必须是 流畅体验而非苦差
集市村庄(Market)
- 视距较短,重心在 NPC 上
- 关注 动画质量 和日常行为模拟
- 需要动画质量足以支撑 动态交互场景
CPU 帧执行的详细分解
线程流水线配置
演示采用的 流水线延迟配置 :
| 线程 | 处理的帧 |
|---|---|
| Game Thread | 第 N 帧 |
| Render Thread | 第 N-1 帧(落后 1 帧) |
| GPU | 第 N-2 帧(落后 2 帧) |
- 这种 2 帧延迟 设置是为了应对开放世界中不可避免的 单帧卡顿 ,提供缓冲
Game Thread 执行流程
整个 Game Thread 的执行可以拆分为以下阶段,按时间顺序排列:
阶段 1:QIX 启动
- QIX 负责管理执行顺序,启动当前帧所需的所有游戏系统
- 在演示中管理约 2,500 个 Tick 对象 跨所有线程
- 得益于对 Tick Task Manager 的改进,添加了更好的 Worker Thread Tick 支持
阶段 2:双路并行执行
执行立即分为两条路径 同时运行 :
- 路径 A — 经典 Game Thread 对象更新
- 路径 B — 数十个 Update Game State 任务 ,在 Worker Threads 上并发运行
- 这些任务负责:Demo AI 行为、 Smart Object 交互 、甚至 骨骼网格体(Skeletal Mesh)的 Tick
阶段 3:Mass 系统(状态更新)
- 大量依赖 Mass 系统 ,负责:
- NPC 的 LOD 切换
- 寻路 、Smart Object 交互
- 特别值得关注的是 Mass-to-AnimNext Translator 处理器:
- 不仅管理骨骼网格体更新,还 替代了群众骨骼网格体的 Tick 行为
- 每帧负责 Tick 超过 800 个骨骼网格体
阶段 4:动画更新
- 使用 Unreal Animation Framework (原名 AnimNext )
- 评估 400+ 个动画图 用于骨骼网格体
- 关键数据:
- 54 ms 的并行工作量 (分散在多个 Worker Thread 上)
- 仅 2.6 ms 的 Game Thread 上的 Finalization 任务 (用于将状态同步回 Game Thread,提取 Root Motion)
阶段 5:Mover(移动系统)
- 并行模拟
- 仅 1.3 ms 的 Game Thread Finalization 任务
- 可与寻路信息结合,寻路不需要等待所有动画完成
阶段 6:Game Thread 系统更新
- 仍有一些 绑定在 Game Thread 上的系统 :
- Streaming (流送)
- PCG 生成
- Timer Manager
阶段 7:End of Frame 更新
- 准备所有需要发送给 GPU 的数据
- 启动下一帧
- 实现经过 重写 ,现在能 更好地随 CPU 核心数扩展
Render Thread 执行流程
接收到 Game Thread 的数据后,按顺序:
- 接收 Game Thread 数据 — 所有更新
- 更新可见性 + Lumen 数据
- 更新图元(Primitive)的 GPU 数据
- 构建 Render Dependency Graph(RDG)
- 执行一批光追相关处理任务 — 可并行运行
- 执行 RDG — 生成数十个并行执行任务
关键优化点
| 优化项 | 说明 |
|---|---|
| 骨骼网格体更新 | 处理从关键路径移除,所有群众相关骨骼网格体在 Render Thread 上仅约 3 ms |
| 光追数据收集与处理 | 场景含约 20,000 个图元 和 数十万个实例 ,数据收集执行 从关键路径移除并改为并行 |
| RDG 处理能力增强 | 改进了渲染依赖图的处理能力 |
| 并行 Command List 翻译 | 超过 16 ms 的工作量;如果没有并行化,在主机仅 13 个 CPU 核心的情况下会成为 瓶颈 |
CPU 利用率分析
NPC 市场场景(最高 CPU 负载)
-
13 个可用 CPU 核心达到约 87% 利用率
-
仍有改进空间,存在三个主要瓶颈:
- 帧开头 :无法足够快地用任务填满 Worker Thread
- 物理阶段 :等待动画和移动 Finalization 完成后才能继续
- 提交阶段 :RHI 提交任务期间没有其他工作交错执行
-
Game Thread 上约有 2 ms 的等待时间 (等待动画和移动 Finalization 任务)
-
剩余 13% 的 CPU 利用率可以 重定向为有限的 Gameplay 功能
- 策略:将动画评估推入物理阶段的空隙中
NPC 场景(降低负载后的空间分析)
将 NPC 从 300 降至 200 后:
- Game Frame 时间降至 11.5 ms (大幅减少)
- 减少了需要评估的动画数量
- 移除了真实 NPC,仅模拟所谓的 "Steps" (简化代理)
- 整帧等待时间仅约 1 ms
- 在 16.66 ms 限制前还剩 5 ms 空余
- CPU 利用率降至 60%
- 出现 4 个较大的空闲间隙 ,可用于构建 更复杂的 Gameplay 系统
关键结论
当前架构 有充足的增长空间 ,团队有信心能在此基础上实现更深层次的 Gameplay 系统。这被视为一个 良好的前景信号 ——技术骨干已经就位,后续可以在不突破帧预算的情况下持续扩展功能。
核心设计原则总结
- 从关键路径移除(Remove from Critical Path) :尽可能将工作从 Game Thread / Render Thread 的串行路径中移出
- 异步 + 并行(Async + Parallel) :利用 Worker Thread 并发执行,最大化多核利用率
- 流水线化(Pipelining) :Game Thread / Render Thread / GPU 各处理不同帧,用延迟换取稳定性
- Finalization 最小化 :并行任务完成后,仅注入最小的同步任务回 Game Thread(如动画 2.6 ms、Mover 1.3 ms)
- 预算驱动设计(Budget-Driven Design) :根据场景类型分配预算,引导美术和 Gameplay 团队在正确方向上投入
GPU 性能分析与异步计算(Async Compute)
全新 GPU Profiler 后端(UE 5.6)
- UE 5.6 带来了 全新的 GPU 分析器后端 ,比以往更精确,并可向 Unreal Insights 报告更多数据
- 现在 Insights 中可以看到 两条 GPU 轨道 :
- 上方 :Graphics Queue(图形队列)—— 传统渲染管线
- 下方 :Asynchronous Compute Queue(异步计算队列)—— 之前无法在 Insights 中显示
- 新增 红色箭头 显示事件间的 依赖关系 ,让你清晰理解 GPU 事件在不同队列之间的调度流程
- 改进不止于 Insights:
stat GPU和profileGPU等命令也能获得更好的报告,包括 等待时间 等细节,而不仅仅是原始执行时间
异步计算(Async Compute)核心概念
基本原理
- Async Compute 允许你在 独立的计算队列 上发起 Compute 工作,与 Graphics Queue 并行运行
- GPU 着色器几乎不可能达到 100% 资源利用率 ,因此当图形队列有空闲资源时,计算工作可以 填充这些空隙
- 本质上是通过 "横向扩展" 来缩短整体帧时间
实测效果
- 开启 Async Compute 后,本 Demo 总共 节省约 1.5 ms
- 被移至异步计算队列的 Pass 会在帧中 不同的位置启动 ,且运行时间看起来 更长 ——这是因为它们与其他工作 重叠执行 ,这是正常现象
- 异步计算甚至允许 跨帧重叠 :当前帧的某些工作可以与上一帧的尾部工作并行,从而 充分利用帧尾的 GPU 空闲期
本 Demo 中的具体应用
| 操作 | 说明 |
|---|---|
| GPU Skin Cache 移至 Async Compute | 与上一帧的后处理重叠执行(原本该时段没有重叠工作) |
| 风模拟(Wind Simulation) 移至 Async Compute | 轻量计算工作,适合异步 |
| Lumen 反射移回 Graphics Queue | 因为没有足够的图形工作与之重叠,放在 Async Compute 反而 更慢 |
关键教训
不要把所有东西都丢到 Async Compute 队列上! 如果没有足够的图形工作可以重叠,异步计算反而会 增加开销 (因为队列切换、同步等额外成本)。
- 必须针对 你自己的具体场景和内容 进行分析
- UE5 的大量渲染特性都提供了 CVar 控制 来启用/禁用 Async Compute,你可以自行实验
- 所有新主机、大部分上代主机以及较新的 PC GPU 都支持 Async Compute
使用 Nanite 渲染植被
传统植被渲染方式为何行不通
Alpha Mask 方案的问题
- Nanite 不喜欢 Alpha Mask 几何体 :它引入 过度绘制(Overdraw) ,并且需要执行可能昂贵的 Opacity Mask 函数
- 在拥有 Nanite 能力的今天,也不希望被迫用 平面卡片(Flat Cards) 来渲染植被
纯三角形方案的问题
- 想象一下将数十万根松针建模为几何体:
- 导致 次优的 Cluster 着色 和远距离 简化效果差
- 虽有 Preserve Area 选项可缓解,但并不完美
- 如果每根松针都是唯一三角形,磁盘上的资产体积会极其庞大
World Position Offset (WPO) 的问题
- 传统做法通过材质中的 World Position Offset 驱动树叶和树枝的风动效果
- 但对 Nanite 不友好:
- 增加 逐顶点计算
- 由于无法预知材质如何移动顶点,Cluster Bounds 必须设置得 非常保守 ,导致 次优的 Cluster 包围盒
全新 Nanite 植被渲染管线
整体方案概述
团队经过长期研究,开发了一套 全新的 Nanite 内部渲染管线 ,核心能力包括:
- 渲染 完全建模的树木 ,精细到 单根松针
- 可扩展到大型开放世界
- 内存高效 、 高性能
- 高度动态 (支持风等效果)
- 无需 LOD ——保持 Nanite 的一贯哲学
三大核心技术
Nanite Assemblies(Nanite 组装件)
- 解决 磁盘空间 问题
- 将一棵树拆分为一组 可实例化的树枝部件 ,通过组装来构建完整的树
- 允许以极少的 唯一资产数据 构建出非常大且精细的树木
- 例如 Demo 中的 Hero 云杉树 :
- 4100 万三角形
- 12 个组装部件(Assembly Parts)
- 超过 2,000 个组装部件实例
- 850 根骨骼
远距离体素化(Voxel Rendering)
- 当远处的 Nanite Cluster 足够小时,无缝切换为体素渲染 而非三角形
- 体素在像素级别大小时 肉眼不可察觉
- 保持了树木的 体积感外观 ,无需退化为 Billboard 或烘焙 LOD
- 过渡完全 无缝 :从近处的单根松针到远处的体素,连续不间断
骨骼驱动的风模拟
- 树木是 骨骼网格体(Skeletal Mesh) ,风通过 骨骼层级 模拟
- 使用 Wind Compute Shader 在 GPU 上程序化运行风模拟,直接应用到骨骼
- 不再使用 World Position Offset ,可以使用 Nanite 的 固定功能路径(Fixed Function Path) 并获得 最优 Cluster Bounds
- 草和灌木同样使用骨骼网格体方案
Demo 实际数据
| 指标 | 数值 |
|---|---|
| Vista 镜头渲染树木数 | 约 20,000 棵 |
| 源三角形总数 | 数十亿级 (Nanite 处理,实际不是问题) |
| 整个山谷散布总数 | 超过 100,000 棵 树木和灌木 |
| 物种数 | 28 种 ,跨多个生物群落 |
| 单棵树三角形范围 | 100 万 ~ 1000 万+ |
| 骨骼数 | 每棵树 数百根 ,取决于物种和尺寸 |
| 风模拟骨骼更新量 | 约 100,000 根骨骼 |
| 风模拟 GPU 耗时 | 约 0.1 ms |
风模拟的自动优化
- 低于一定屏幕尺寸的树木 自动停止应用风动画
- 但阈值设置得足够远,保证 远景也有风的沉浸感
可用性说明
- 该技术 不会随 UE 5.6 发布 ,将在之后推送到 UE 主线分支 ,为 5.7 做准备
- 感兴趣的开发者可以在 GitHub 上提前体验
- 后续将配套 高质量示例内容 通过 Fab 提供下载
Nanite 性能优化技巧
减少 Helper Lanes(辅助像素线程)
- Nanite 大量使用 软件光栅化(Compute 实现) ——这是其"秘密武器"
- 软件光栅不受硬件 2×2 像素 Quad 的限制(硬件光栅需要用 helper lane 计算导数)
- Nanite 会判断材质是否能安全使用 解析导数(Analytical Derivatives) ,从而 跳过 Helper Lane 的启动
- 这可以 大幅减少像素着色器调用次数
- 使用 Debug View Mode 检查哪些物体在产生 Helper Lane
- Nanite Stats 也会显示 Helper Lane 数量—— 目标是接近零
尽量使用 Fixed Function Fast Path
- 避免让 Nanite 材质变为 Pixel Programmable (可编程像素路径)
- 以下材质特性会 强制退出 Fast Path :
- Masked 模式
- World Position Offset
- Pixel Depth Offset
- 使用 Pixel Programmable Debug View Mode 检查哪些物体走的是快速路径
谨慎使用 Nanite Tessellation
- Nanite Tessellation 有开销
- 确保材质中的 Displacement Range 设置与实际预期位移匹配
- 如果范围设得过大,会不必要地 膨胀 Cluster Bounds
- 本 Demo 中 Nanite Tessellation 仅用于地形(Landscape)
最大化 Nanite 覆盖率
- 尽可能让网格和材质 支持 Nanite
- 使用 Nanite Mask View Mode 检查:
- 绿色 = 已正确支持 Nanite ✅
- 例如水体、Niagara 粒子鸟等目前不支持
- 注意排查 未正确配置 Nanite 的物体
Lumen 与硬件光线追踪(Hardware Ray Tracing)
光线追踪场景的近场与远场架构
近场(Near Field)
- 光线追踪场景实例加载范围约 150 米,包含:
- 地形(Landscape)
- 植被(Foliage)
- 静态与动态几何体
- 光线追踪几何体内存池 预算约 400 MB,实际峰值约 300 MB
- GPU 端的 光线追踪场景更新 预算约 0.5 ms,运行在 Async Compute 上
远场(Far Field)
- 超过 150 米的部分由 一组独立的远场代理(Far Field Proxies) 处理,用于追踪更长距离的光线
- 构建为 独立的顶层加速结构(Top-Level Acceleration Structure, TLAS)
- 工作流程:先追踪近场,如果光线需要继续传播,再 按需进入远场
- 远场 仅包含静态几何体,因此无需每帧更新
- 典型内容:HLOD(Hierarchical LOD)、地形、大型场景构件
远场的遮挡优化
- Lumen 引入了一种新模式:在远场中 仅追踪遮挡(Occlusion Only),而 不计算命中点的光照
- 这大幅降低了远场追踪的开销
动态角色的光追场景管理
核心挑战
- 村庄场景中有 大量动画角色(Animated Characters),不能让它们在光追场景中保持静态,否则会导致 视觉不匹配
解决方案:分帧交错更新
- 每帧限制更新约 40,000 个动态三角形
- 仅更新 最重要的角色(即距离相机最近的角色)
- 随着更多角色出现在屏幕上,不会突破预算上限
底层优化细节
| 优化措施 | 说明 |
|---|---|
| GPU Skin Cache 运行在 Async Compute | 与其他图形工作重叠执行 |
| GPU Skin Cache 与光追场景更新绑定 | 避免为不使用的网格填充 Skin Cache,减少无用计算 |
| 地形独立的时间切片 LOD 更新 | 地形占据动态实例预算的 很大比例,使用 较低的 LOD 以加速追踪和更新 |
树木的光追代理(RT Proxy for Foliage)
问题
- 动态预算非常紧张,无法在光追场景中对树木做动画
- 但森林必须被正确追踪以保证 光照遮挡 效果
解决方案:三角形云代理
- 为植被专门开发了一条 光追代理构建路径
- 从源网格生成一组 三角形云(Triangle Cloud),匹配间接光照所需的 遮挡密度
- 效果极为显著:
- 4100 万三角形的英雄树 → 光追表示仅需 225 个三角形
- 对间接光照和遮挡效果足够好
- 缺点:镜面反射中会看到三角形形状——但在水面反射中由于法线贴图和波浪运动,视觉上可接受
这是在当前主机上以 60 FPS 渲染大规模森林的 必要妥协。
Lumen 主要渲染 Pass 概览
以下为帧中 Lumen 的关键 Pass(禁用 Async Compute 以显示真实大小):
- Lumen Scene Lighting — 场景光照更新
- Lumen Screen Probe Gather — 屏幕空间探针采集
- Lumen Reflections — 反射计算
- Lumen Card Updates — Lumen 卡片(表面缓存)更新
- Single Layer Water 内部的第二条 Lumen 反射路径
- Ray Tracing Scene Update — 光追场景更新
Lumen 和硬件光追是 帧预算的核心组成部分,对内容设置和优化配置极其敏感。
性能陷阱与优化案例
陷阱 1:反射水坑的过度追踪
问题
- 村庄中大量 反射性水坑,Lumen 会为每个反射像素追踪 专用硬件加速光线
- 在光追性能有限的平台上 非常昂贵
解决方案:粗糙度阈值(Roughness Threshold)
- 设置一个 粗糙度阈值:材质粗糙度高于阈值的像素 不走硬件追踪,而是回退到 Lumen Radiance Cache 获取数据
- Radiance Cache 精度较低但 速度快得多
- 实测:阈值设为 0.4 与设为 1.0(无阈值) 的视觉差异 几乎不可察觉,但速度 快 3 倍
- 植被可以设置 独立的阈值,因为植被的视觉考量可能不同
调试方法
- 使用 Debug View Mode 查看哪些像素在走昂贵的追踪路径
- 在 Unreal Insights 中查看 Lumen Reflection Pass 的耗时
陷阱 2:水下光照的重复计算
问题
- 使用 Single Layer Water 着色模型时,引擎会:
- 先 照亮水下内容
- 再渲染水面并 照亮水面
- 水边的材质通常很 潮湿(低粗糙度),因此水下内容也会触发 昂贵的反射追踪
- 结果是 双重光照成本
解决方案:水深遮罩优化
- 引入一个 深度阈值:超过该深度的水被视为 不透明
- 在该深度以下的所有 Lumen 贡献被剔除(不再计算水下光照)
- 实测:在大部分水体较深的场景中,可 节省超过 1 ms
- 注意:对于浅水(如山间小溪),此优化不适用,需 针对具体内容 Profile
硬件追踪 vs 软件追踪:明确选择
两者的根本差异
| 维度 | 硬件光追(Hardware RT) | 软件光追(Software RT) |
|---|---|---|
| 追踪对象 | 实际几何体的 BVH 加速结构 | 静态距离场(Static Distance Field) |
| 骨骼网格支持 | ✅ 可追踪 Skeletal Mesh | ❌ 不支持 |
| 内存 | 需要 RT 几何体池 | 需要距离场内存(可能更大) |
| 质量 | 更高 | 较低 |
| 内容适配 | 为两条路径同时调整光照效果 非常困难 | — |
官方推荐
- 硬件光追是 Lumen 的未来方向,团队已全力优化使其 可在主机上以 60 FPS 使用
- 切换到硬件追踪后可以 移除静态距离场,节省大量内存
- 实战验证:《堡垒之夜》自 2024 年 12 月起已在当前世代主机上以 60 FPS 发布硬件光追 Lumen
迁移建议
- 无论是新项目还是从软件追踪迁移,都强烈推荐参考官方文档中的 Lumen & Hardware Ray Tracing Performance Guide
UE 5.6 Lumen 优化总结
- 5.6 带来了 极长的 Lumen 优化列表,远超本次演讲能覆盖的范围
- 详细内容请查阅 UE 5.6 Release Notes
虚拟阴影贴图(Virtual Shadow Maps, VSM)优化
VSM 与 Nanite 的天然协同
- VSM 与 Nanite 天然配合:Nanite 的 体素化表示(Voxel Representation) 在阴影贴图中 渲染也更快
- 但对于大型开放世界 + 密集森林 + 动态场景,仍需大量额外优化
关键优化手段
接收者掩码(Receiver Masks)
- 将每个 VSM 页面(Page) 细分为 64 个 Tile,生成更紧密的 剔除区域
- 效果:大幅降低 Nanite VSM 阴影深度渲染开销
- 不影响任何视觉效果,纯粹的性能提升
远距离树木的静态缓存
- 如前所述,远距离树木会 自动停止动画(基于屏幕尺寸)
- 利用这一点:将 不再动画的树木标记为静态缓存(Statically Cached)
- 不再需要每帧重新渲染远距离树木的阴影 → 显著节省开销
景深区域自动降低 VSM 质量
- 自动检测景深模糊(Depth of Field) 区域
- 对失焦区域 降低 VSM 精度
- 非常适合 电影化对话场景:背景大面积虚化时根本不需要高质量阴影
- 为前景的高质量效果 腾出预算
VSM 节流系统(VSM Throttling)
问题
- 开放世界中阴影开销 波动极大,受太阳角度、摄像机与阴影接收者距离等多因素影响
- 传统做法:依赖 动态分辨率(Dynamic Resolution) 吸收峰值开销
- 缺点:阴影变贵 → 整个画面分辨率下降 → 代价不公平
解决方案
- 引入 VSM 专用节流系统:当阴影预算超标时,仅降低 VSM 自身质量,而非降低整帧分辨率
- 好处:在一般情况下保持画面 清晰锐利,仅在需要时 针对性降低阴影质量
非 Nanite 蒙皮网格的包围盒优化
- 针对 非 Nanite 的蒙皮网格(Skin Meshes,如角色)
- 在 GPU Skin Cache Shader 内部 实时重新计算网格包围盒(Mesh Bounds)
- 因为该 Shader 本来就在处理所有顶点,顺便计算包围盒 几乎零额外开销
- 产生 更精确的包围盒 → 更好的剔除 → 更少的不必要阴影渲染
- 不依赖内容设置,自动生效
整体理念转变
VSM 性能优化的核心哲学正在从 "主要依赖静态缓存" 向 "在动态昼夜循环和动态场景中也能开箱即用地高效运行" 转变。
分辨率管线与时域超分辨率(TSR)
分辨率管线的逐步演进
基础情况:无上采样
- 所有 Pass 均以 最终输出分辨率(如 4K) 渲染
- GPU 开销最高,帧中大部分时间花在几何处理上
第一步:引入 TSR + 主分辨率(Primary Resolution)
- 大部分渲染工作在 较低的主分辨率 下执行
- 通过 TSR(Temporal Super Resolution) 上采样到最终分辨率
- Nanite 和 VSM 的开销随主分辨率 线性缩放,因此降低主分辨率可以 显著节省性能
第二步:引入动态分辨率(Dynamic Resolution)
- 开放世界中帧时间 波动极大,不可能优化每一帧到相同开销
- 动态分辨率 根据 历史帧开销 自动调整主分辨率,平滑波动
- 本 Demo 的动态分辨率范围:800p ~ 1080p
- 上限 1080p 的选择理由:
- 决定了所有 屏幕缓冲区的内存占用
- TSR 的理想上采样比例约为 2x(1080p → 4K 正好约 2x)
第三步:引入二级屏幕百分比(Secondary Screen Percentage)
问题
- TSR 上采样到 4K 后,帧尾的 多个后处理 Pass 仍然在 完整 4K 分辨率 下运行,开销很大
解决方案
- TSR 不上采样到 4K,而是上采样到 中间分辨率(如 1440p)
- 帧尾的后处理 Pass 在 1440p 下运行
- 最后通过一个 空间上采样(Spatial Upscale) 从 1440p → 4K
性能对比
| 方案 | TSR 输出 | 后处理分辨率 | 最终输出 | 帧尾开销 |
|---|---|---|---|---|
| 无二级屏幕百分比 | 4K | 4K | 4K | 较高 |
| 有二级屏幕百分比 | 1440p | 1440p | 4K(空间上采样) | 节省约 1 ms |
- 在 16.6 ms 的帧预算中,节省近 1 ms 是非常可观的
- 画质确实有所下降,但 优于 让动态分辨率吸收这部分开销(那样会导致主分辨率降到 2x TSR 理想比例以下,画质反而更差)
本 Demo 的最终分辨率配置
应对卡顿(Hitches)与镜头切换
卡顿的本质
- 动态分辨率 基于 历史帧开销 预测下一帧所需分辨率
- 如果某帧 突然出现异常高的开销,动态分辨率 来不及反应,导致 掉帧
- 如果频繁发生 → 体验 不一致、不流畅 → 这就是 卡顿(Hitching)
GPU 端卡顿的常见来源
| 来源 | 说明 |
|---|---|
| 生成昂贵特效 | 粒子、爆炸等突发大量 GPU 工作 |
| 大型缓冲区重新分配 | 内存管理导致的峰值 |
| 镜头切换(Camera Cut) | 最常见且最难处理 |
镜头切换为何导致卡顿
- Nanite 依赖 上一帧的数据 进行 遮挡剔除(Occlusion Culling)
- 镜头突然跳转到完全不同的视角 → 所有历史遮挡数据失效
- 结果:大量几何体 无法被剔除,产生 严重的过度绘制(Overdraw)
- 典型场景:过场动画、对话 中的多角度切换
解决方案:光追场景数据预填充(Priming)
- 镜头切换后,使用 光线追踪场景中的低分辨率数据 来 预填充(Prime) 丢失的遮挡信息
- 不再是"切换后什么都没有",而是有一个 回退数据源
- 效果对比(切换后第一帧的 Nanite Overdraw 调试视图):
| 状态 | Overdraw 情况 |
|---|---|
| 无预填充 | 大面积严重过度绘制 |
| 有预填充 | 过度绘制大幅减少,虽非完美但显著改善 |
这个系统并非完美解决所有卡顿,但将最坏情况的性能冲击 大幅缓解,是开放世界 60 FPS 体验的重要保障。
总结要点速览
| 技术领域 | 核心收获 |
|---|---|
| VSM | Receiver Masks、远距离静态缓存、景深自动降质、专用节流系统、运行时包围盒重算 |
| 分辨率管线 | 主分辨率 800-1080p → TSR → 1440p → 空间上采样 4K,二级屏幕百分比节省约 1 ms |
| 动态分辨率 | 平滑帧时间波动,但无法处理突发卡顿 |
| 镜头切换抗卡顿 | 利用光追场景低分辨率数据预填充遮挡信息,减少 Overdraw 峰值 |
| 整体哲学 | VSM 从"静态缓存优先"转向"动态场景开箱即用";所有优化以主机为基准设计,同样惠及 PC |