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 需要覆盖三大阶段:

    1. 模拟场景(Game Thread)
    2. 为 GPU 准备数据(Render Thread)
    3. 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 的数据后,按顺序:

  1. 接收 Game Thread 数据 — 所有更新
  2. 更新可见性 + Lumen 数据
  3. 更新图元(Primitive)的 GPU 数据
  4. 构建 Render Dependency Graph(RDG)
  5. 执行一批光追相关处理任务 — 可并行运行
  6. 执行 RDG — 生成数十个并行执行任务

关键优化点

优化项说明
骨骼网格体更新处理从关键路径移除,所有群众相关骨骼网格体在 Render Thread 上仅约 3 ms
光追数据收集与处理场景含约 20,000 个图元数十万个实例 ,数据收集执行 从关键路径移除并改为并行
RDG 处理能力增强改进了渲染依赖图的处理能力
并行 Command List 翻译超过 16 ms 的工作量;如果没有并行化,在主机仅 13 个 CPU 核心的情况下会成为 瓶颈

CPU 利用率分析

NPC 市场场景(最高 CPU 负载)

  • 13 个可用 CPU 核心达到约 87% 利用率

  • 仍有改进空间,存在三个主要瓶颈:

    1. 帧开头 :无法足够快地用任务填满 Worker Thread
    2. 物理阶段 :等待动画和移动 Finalization 完成后才能继续
    3. 提交阶段 :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 系统。这被视为一个 良好的前景信号 ——技术骨干已经就位,后续可以在不突破帧预算的情况下持续扩展功能。


核心设计原则总结

  1. 从关键路径移除(Remove from Critical Path) :尽可能将工作从 Game Thread / Render Thread 的串行路径中移出
  2. 异步 + 并行(Async + Parallel) :利用 Worker Thread 并发执行,最大化多核利用率
  3. 流水线化(Pipelining) :Game Thread / Render Thread / GPU 各处理不同帧,用延迟换取稳定性
  4. Finalization 最小化 :并行任务完成后,仅注入最小的同步任务回 Game Thread(如动画 2.6 ms、Mover 1.3 ms)
  5. 预算驱动设计(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 GPUprofileGPU 等命令也能获得更好的报告,包括 等待时间 等细节,而不仅仅是原始执行时间

异步计算(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 着色模型时,引擎会:
    1. 照亮水下内容
    2. 再渲染水面并 照亮水面
  • 水边的材质通常很 潮湿(低粗糙度),因此水下内容也会触发 昂贵的反射追踪
  • 结果是 双重光照成本

解决方案:水深遮罩优化

  • 引入一个 深度阈值:超过该深度的水被视为 不透明
  • 在该深度以下的所有 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) 上采样到最终分辨率
  • NaniteVSM 的开销随主分辨率 线性缩放,因此降低主分辨率可以 显著节省性能

第二步:引入动态分辨率(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 输出后处理分辨率最终输出帧尾开销
无二级屏幕百分比4K4K4K较高
有二级屏幕百分比1440p1440p4K(空间上采样)节省约 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 体验的重要保障。


总结要点速览

技术领域核心收获
VSMReceiver Masks、远距离静态缓存、景深自动降质、专用节流系统、运行时包围盒重算
分辨率管线主分辨率 800-1080p → TSR → 1440p → 空间上采样 4K,二级屏幕百分比节省约 1 ms
动态分辨率平滑帧时间波动,但无法处理突发卡顿
镜头切换抗卡顿利用光追场景低分辨率数据预填充遮挡信息,减少 Overdraw 峰值
整体哲学VSM 从"静态缓存优先"转向"动态场景开箱即用";所有优化以主机为基准设计,同样惠及 PC