UE5.8 时域超分辨率技术:TSR、DLSS、FSR 的取舍与平台实战

📑 目录

UE5.8 时域超分辨率技术:TSR、DLSS、FSR 的取舍与平台实战

老周是我带过的主程里最不爱坐办公室的一位。前年冬天,他抱着一杯已经凉透的美式站在我面前,说和平哥,我们那个开放世界项目,PS5 画质模式跑不到 30 帧。我盯着他,他又补了一句,Lumen 关不得,Nanite 也不能动,唯一能松的口子就是分辨率。我问他打算怎么办,他说,上 TSR。我说行,但上了 TSR 之后,树叶、头发、快速横移的金属反光,你准备怎么交代?他愣了一下,说那就一个个调。这就是我们与时域超分辨率技术打交道的真实开场:它不是救命稻草,而是一笔需要逐项对账的买卖。

时域超分辨率到底在做什么

在展开具体方案之前,我想先把最朴素的逻辑讲清楚。我们眼睛看到的画面,本质上是三维场景在二维屏幕上的一次投影。要提高清晰度,最直接的办法是提高渲染分辨率,让光线穿过更多像素。代价也最直接:像素数量按平方增长,Fragment Shader 的负担跟着暴涨。

时域超分辨率走了一条不同的路。它不再要求当前帧一次画够所有细节,而是把过去若干帧的信息借过来,和低分辨率渲染的新帧拼在一起。只要拼得好,眼睛就会误以为这是一张高分辨率图像。这个思路听起来简单,真要做到产品里却需要解决一连串麻烦:历史帧的像素对应到当前帧的哪个位置?被遮挡的物体怎么处理?运动物体会不会拖影?高频细节会不会闪烁?

UE5.8 里主要提供了三条技术路线:内置的 TSR、NVIDIA 的 DLSS、AMD 的 FSR。它们都基于“时间换空间”的核心思想,但实现方式差异很大。

下面这张图描述了时域超分辨率的基本数据流。

flowchart LR
    A[低分辨率渲染管线] --> B[颜色缓冲]
    A --> C[深度缓冲]
    A --> D[运动向量缓冲]
    A --> E[曝光与相机信息]
    B --> F[时域累积与上采样]
    C --> F
    D --> F
    E --> F
    G[历史帧颜色缓冲] --> F
    F --> H[锐化/后处理]
    H --> I[最终高分辨率画面]

数据从低分辨率渲染管线出来,分成颜色、深度、运动向量、曝光几条路,和历史帧一起送进时域累积模块,最后再做锐化。这个流程在 TSR、DLSS、FSR 里大同小异,但每一环的实现精度天差地别。

TSR:UE5 的默认答案

TSR 是 Temporal Super Resolution 的缩写,从 UE5.0 开始就是默认的抗锯齿与上采样方案。它最大的优势是不挑硬件,PC、PS5、Xbox Series X|S 都能跑,而且和 Lumen、Nanite、Virtual Shadow Maps 的耦合做得很好。很多团队第一次打开 UE5 项目时,根本意识不到 TSR 已经在后台工作了。

TSR 的核心可以拆成几步:

第一步,低分辨率渲染。UE5 默认的 ScreenPercentage 会根据目标分辨率自动调整,比如 4K 输出时内部可能以 1080p 或 1440p 渲染。这个比例不是死的,你可以在控制台或者项目设置里改。

第二步,抖动采样。每一帧的投影矩阵会做一个亚像素级别的偏移,让连续几帧采样到略微不同的位置。把这些偏移后的结果累积起来,就等价于在一个更高分辨率网格上做了超采样。 jitter 的偏移量通常遵循 Halton 序列,保证采样点在时域上分布均匀。

第三步,运动向量驱动的历史帧重投影。引擎会根据上一帧的颜色缓冲和运动向量,把历史像素挪到当前帧的位置。如果重投影后发现历史像素被遮挡或者走出了屏幕,就降低它的权重。

第四步,上采样与混合。TSR 会结合当前低分辨率帧和历史帧,输出一个高分辨率图像。这里用到大量启发式规则,比如颜色裁剪、深度一致性、局部对比度,来抑制 ghosting 和闪烁。

第五步,锐化。TSR 自带一个锐化核,用来补偿上采样带来的轻微模糊。这个锐化强度可以调,调得太高会出现白边和颗粒感。

下面这张图用 architecture-beta 展示 TSR 在 UE5.8 渲染管线中的位置。

architecture-beta
    group ue_engine(UE5.8 渲染管线)
        service basepass[Base Pass] in ue_engine
        service lumen[Lumen GI] in ue_engine
        service vsm[VSM 阴影] in ue_engine
        service gbuffer[GBuffer] in ue_engine
        service velocity[Velocity Pass] in ue_engine
        service tsr[TSR 模块] in ue_engine
        service postprocess[后处理 / 色调映射] in ue_engine
        service rhi[RHI 抽象层] in ue_engine

    basepass:B --> T:lumen
    basepass:B --> T:vsm
    basepass:R --> L:gbuffer
    gbuffer:R --> L:velocity
    gbuffer:B --> T:tsr
    velocity:T --> B:tsr
    lumen:B --> T:tsr
    tsr:R --> L:postprocess
    postprocess:B --> T:rhi

TSR 放在后处理之前,GBuffer、Velocity、Lumen 的结果都喂给它。它输出高分辨率场景颜色,再交给色调映射和后期处理。

TSR 的成本大概在每个像素 1.0 到 1.5 毫秒之间,视分辨率和硬件而定。这个开销听起来不高,但它对运动向量的质量极度敏感。我见过不止一个项目,角色快速转身时头发会拉丝,草地会变成绿色糊糊,最后查出来都是 Velocity Buffer 写错了。UE5.8 里,Velocity Pass 默认在 Base Pass 之后写,但某些使用了 World Position Offset 动画的物体需要改成 Write During Base Pass,否则运动向量会滞后一帧。

DLSS:NVIDIA 的神经网络方案

DLSS 全称 Deep Learning Super Sampling。和 TSR 不同,DLSS 把一大部分重建工作交给了一个预训练的神经网络。这个网络运行在 NVIDIA RTX GPU 的 Tensor Core 上,输入同样是低分辨率颜色、深度、运动向量、曝光,输出高分辨率图像。

DLSS 的关键差异在于,它用神经网络学习了大量真实游戏画面的超分映射。训练数据里包含各种材质、边缘、半透明、粒子效果,所以它在重建高频细节时往往比传统算法更聪明。比如铁丝网、栏杆、远处的树叶,DLSS 通常比 TSR 更稳定,拖影也更少。

DLSS 也带来了一些麻烦。第一,它是闭源的,你只能集成 NVIDIA 提供的插件,调参空间不如 TSR 大。第二,它依赖 RTX 显卡,非 NVIDIA 平台完全跑不了。第三,如果输入的运动向量或深度有瑕疵,神经网络的输出会出现奇怪的伪影,比如人物边缘出现黑色描边,或者烟雾变成棋盘格。

UE5.8 的 DLSS 插件把 Quality、Balanced、Performance、Ultra Performance 几个模式暴露出来。Quality 模式下内部渲染分辨率大约是输出的 67%,Performance 模式降到 50%,Ultra Performance 更低。我通常建议 PC 上优先用 Quality,只有在帧率实在守不住时才往下调。Performance 模式虽然帧数好看,但画面上的细节会明显变软,尤其是在 1440p 屏幕上。

DLSS 还有一个容易被忽略的点:它需要 jitter 偏移。UE5 的 DLSS 插件会自动处理这个偏移,但如果你自己写了一个自定义相机管线,比如电影机位或者固定视角,一定要记得给插件提供正确的 jitter。否则 DLSS 会认为每一帧采样的是同一个位置,神经网络积累不出足够的信息,画面反而会比原生低分辨率更糊。

下面这张序列图展示了 DLSS 在 UE5.8 里每帧和引擎交换数据的顺序。

sequenceDiagram
    participant Game as 游戏逻辑
    participant Camera as 相机系统
    participant Renderer as 渲染器
    participant DLSS as DLSS 插件
    participant Tensor as Tensor Core

    Game->>Camera: 更新相机 transform
    Camera->>DLSS: 获取本帧 jitter 偏移
    Camera->>Renderer: 应用 jittered 投影矩阵
    Renderer->>Renderer: 低分辨率渲染颜色/深度/速度
    Renderer->>DLSS: 提交颜色、深度、运动向量、曝光
    DLSS->>Tensor: 调用神经网络推理
    Tensor-->>DLSS: 返回高分辨率图像
    DLSS-->>Renderer: 输出上采样后的场景颜色
    Renderer->>Renderer: 后处理与色调映射

FSR:开源的通用选择

FSR 是 AMD 推出的时域超分辨率方案,目前已经发展到 FSR 3.1。它的最大卖点是跨平台:PC 上的 NVIDIA、AMD、Intel 显卡都能跑,主机上也能集成。FSR 在 UE5 里以插件形式存在,代码是开源的,团队可以根据自己的需求魔改。

FSR 第一代是纯空间上采样,靠 Lanczos 类的核和锐化放大图像,没有任何时域累积。FSR 2 开始引入时域算法,思路向 TSR 和 DLSS 靠拢:输入低分辨率颜色、深度、运动向量,做历史帧重投影和混合。FSR 3 又加入了帧生成,可以用光流和运动向量插出额外帧,进一步提高帧率。

FSR 2/3 的质量在我看来略逊于 DLSS,但差距已经比过去小很多。它的主要问题是静止画面的稳定性稍弱,某些材质会出现轻微抖动。另一个问题是锐化。FSR 自带的 RCAS 锐化非常激进,默认强度下经常让画面看起来像加了 USM 滤镜。我一般会建议把 FSR 的锐化关掉,让引擎自己的后处理锐化来接管。

FSR 的内存占用比 DLSS 低,因为它不依赖神经网络权重。这一点在主机上很有价值。PS5 和 Xbox Series S 的显存都算不上宽裕,FSR 的低开销让它成为很多跨平台项目的默认选择。UE5.8 官方也对 FSR 插件做了更新,支持和 Lumen、VSM 更好地协同。

下面这张关系图展示了 TSR、DLSS、FSR 在输入、算法、平台支持上的差异。

graph TD
    A[时域超分辨率技术] --> B[TSR]
    A --> C[DLSS]
    A --> D[FSR]

    B --> B1[内置/跨平台]
    B --> B2[传统时域算法]
    B --> B3[无需专用硬件]
    B --> B4[和 Lumen/Nanite 耦合最好]

    C --> C1[NVIDIA RTX 独占]
    C --> C2[神经网络重建]
    C --> C3[Tensor Core 加速]
    C --> C4[静止/高频细节最稳]

    D --> D1[开源/跨平台]
    D --> D2[时域算法]
    D --> D3[内存占用低]
    D --> D4[主机移植常用]

上采样算法、运动向量与历史帧累积

这三种方案虽然品牌不同,但底层问题是一样的。我想把几个关键技术点拆开讲。

上采样算法解决的是:如何用低分辨率输入恢复高分辨率输出。最粗暴的做法是双线性或双三次放大,但这会让画面变软。更好的做法是利用时域信息,把多帧的低分辨率采样拼成一张超采样图。TSR 和 FSR 用的是传统信号处理的方法,DLSS 在此基础上叠加了神经网络。

运动向量是时域超分的大脑。每个像素都要回答一个问题:上一帧它在哪儿?Velocity Buffer 通常存储的是屏幕空间里的像素位移,单位是像素/帧。UE5 里,运动向量可以由 Base Pass 或独立 Velocity Pass 写入。对于 Static Mesh,引擎可以用上一帧和当前帧的 transform 自动算;对于 Skeletal Mesh,需要 GPU Skinning 时把上一帧的位置也带出来;对于用了 World Position Offset 的物体,必须手工把偏移量传给 Velocity。

历史帧累积解决的是可信度问题。我们能不能直接用上一帧的颜色?不行。因为相机在动、物体在动、光照在变化,上一帧的颜色挪过来不一定对。所以时域超分需要一套历史帧校验机制。常见做法是维护一个历史颜色缓冲,把上一帧颜色重投影到当前位置,然后用当前帧局部颜色范围去裁剪它。如果历史颜色跑出当前帧局部范围太多,就降低它的权重,甚至完全抛弃。这个权重通常用指数衰减控制,越新的帧权重越高。

历史帧累积有一个经典问题:如果物体在原地高频震动,比如树叶被风吹得抖动,历史帧会被反复判定为不可靠,导致画面出现闪烁。UE5.8 在 TSR 里加入了一些反闪烁逻辑,但效果取决于具体场景。老周当时的项目里,一片桦树林在风里抖,TSR 把树叶当成不可靠历史,结果树叶边缘一直在闪。最后我们把 ScreenPercentage 从 67% 提到 75%,牺牲了一点性能换稳定性。

抖动、锐化与棋盘格渲染往事

Jitter 是时域超分的灵魂。没有 jitter,连续几帧采样的是同一个位置,无法积累出更多信息。UE5 的 jitter 通常用 Halton 序列生成,覆盖一个像素内的多个亚像素位置。Halton 序列的好处是分布均匀、低差异,几帧之内就能覆盖到足够多的采样点。

但 jitter 也会带来副作用。最明显的是静止画面会轻微抖动,尤其是在高对比度边缘。MRQ 做序列渲染时,如果 temporal sample 数量不够,画面会出现抖动的瑕疵。TSR 比 TAA 更聪明的地方在于,它会根据场景动态调整采样策略,自动选择最合适的 jitter 数量。

锐化是另一个容易踩坑的地方。上采样本身会带来轻微模糊,所以必须加锐化。但锐化是把双刃剑。强度太低,画面糊;强度太高,边缘出现白边,噪点被放大,远处的草地会变成一团颗粒。我在项目里一般会让 TA 先把锐化强度调到 0.3 左右,再围着显示器看一圈,逐步微调。

说到时域超分,不得不提棋盘格渲染。PS4 Pro 时代很多游戏用了 checkerboard rendering,把一帧画在红黑相间的半个像素上,下一帧画另一半,两帧拼成完整图像。这个方案和今天的 TSR 在思想上是一脉相承的:用时间换分辨率。但棋盘格渲染更 primitive,它在空间上直接只渲染一半像素,重投影和抗锯齿的处理也更粗糙。到了 PS5 时代,TSR 和 FSR 已经很大程度上取代了棋盘格渲染。不过理解棋盘格的历史很重要,因为它能帮你理解为什么今天的时域超分要处理抖动、运动向量和历史帧。

平台选择:主机和 PC 的差异

平台选择是很多项目最纠结的部分。我根据自己的经验列了一张支持矩阵,供大家参考。

平台推荐方案理由
PC,NVIDIA RTXDLSS Quality / TSRDLSS 质量最高;TSR 可作为兼容回退
PC,AMD/IntelFSR 2.x 或 TSRFSR 开源;TSR 内置稳定
PS5 / PS5 ProTSR 或 PSSRTSR 成熟;PS5 Pro 有 PSSR 专属优化
Xbox Series XSTSR 或 FSR
Switch 2TSR / FSR低功耗场景需要低开销方案

PS5 Pro 上的 PSSR 是索尼自研的机器学习超分,和 DLSS 思路接近。从目前已有的移植案例看,PSSR 在低分辨率输入下的质量优于 FSR 和 TSR,接近 DLSS。但 PSSR 只在 PS5 Pro 上可用,普通 PS5 还是得用 TSR 或 FSR。

主机上做选择时,我最看重的不是绝对画质,而是可预测性。主机硬件固定,调试好了之后,所有玩家看到的效果都一样。TSR 在这一点上非常稳,所以主机项目我倾向默认用它,只有在性能实在压不住时才切 FSR Performance 模式。PC 上玩家的显卡五花八门,DLSS 和 FSR 同时提供可以让玩家自己选。

一个真实的团队案例

回到老周的项目。那个开放世界游戏目标是 PS5 画质模式 4K30,Xbox Series X 性能模式 1440p60。项目中期帧率崩到 22 帧,美术坚持不关 Lumen,策划坚持森林密度不变,我们能动的只有分辨率和超分方案。

我们先把 PS5 画质模式从原生 4K 降到 TSR Quality,内部渲染大约 1440p。帧率从 22 升到 31,但树叶闪烁严重,角色头发在转身时会留下一缕残影。我让小林去查 Velocity,发现大量植被用了 World Position Offset 做风动,但材质里没有把风动偏移量写入速度缓冲。修复之后,拖影好了很多。

接着测 Xbox Series X 性能模式。目标是 60 帧,TSR Quality 内部渲染 1080p,帧率能到 58,但还不够稳。我们试了两个方案:一个是 TSR Performance,内部降到 720p 左右,帧率稳 60,但远景糊成一片;另一个是 FSR 2 Performance,帧率也是 60,但植被闪烁比 TSR 多一点。最后我们选了 FSR 2 Balanced,内部大约 900p,帧率 59 到 61 之间,画质比 Performance 好一档。

PC 版最折腾。我们有 DLSS、FSR、TSR 三种选项。高端 RTX 玩家普遍选 DLSS Quality;中端 A 卡玩家选 FSR 2;集显和低配玩家 fallback 到 TSR。为了保持一致性,我们统一用 UE5 的 upscaler 接口封装,避免在业务代码里直接调用某个厂商的 API。

下面这段配置来自我们当时的 DefaultEngine.ini,展示了如何在一套设置里为不同平台指定默认抗锯齿/超分方法。

[/Script/Engine.RendererSettings]
r.DefaultFeature.AntiAliasing=2
r.AntiAliasingMethod=2
r.TemporalAA.Algorithm=1
r.TemporalAA.Upsampling=1
r.TemporalAA.HistoryScreenPercentage=200
r.TemporalAAHistoryOp=0
r.TSR.History.ScreenPercentage=200
r.TSR.History.ResolutionOverride=0

[/Script/DLSS.DLSSSettings]
bEnableDLSS=True

[/Script/FSR2.FSR2Settings]
bEnabled=True

[/Script/ConsoleVariables]
r.TemporalAA.R11G11B10History=1
r.TSR.ReconstructionHistory=1

这段配置不是放之四海而皆准的。r.TemporalAA.Algorithm=1 表示用 TSR 而不是传统 TAA,r.TemporalAA.Upsampling=1 打开上采样。DLSS 和 FSR2 的开关在各自的插件配置段里。实际项目中,我们还会根据平台用 DeviceProfiles 做覆盖。

几个容易踩的坑

第一,TSR 和 DLSS 对 Exposure 的敏感度不同。DLSS 需要准确的曝光值来做时域稳定,如果自动曝光波动剧烈,画面会忽明忽暗。我们在一个洞穴场景里遇到过这个问题,最后把自动曝光速度放慢了两档。

第二,半透明物体的运动向量经常是错的。粒子、烟雾、玻璃后面的物体,它们的运动向量可能和背后实体不一致。TSR 会根据深度和运动向量做遮挡剔除,但半透明区域的处理很保守,容易出现闪烁或拖影。对于重要粒子效果,可以考虑在材质里关闭时域累积,或者手动调整 Reactive Mask。

第三,VSync 和帧生成的关系。FSR 3 的帧生成会额外插入一帧,如果显示器的刷新率不是帧率整数倍,容易出现画面撕裂或者延迟波动。主机上这个问题更麻烦,因为 VRR 支持并不统一。

第四,不要迷信 Quality 模式。TSR 和 DLSS 的 Quality 模式画质确实好,但如果你的内部渲染分辨率已经很低,比如 720p 超到 1080p,再强的算法也救不回缺失的细节。这种情况下不如把内部分辨率再降一点,把省下来的预算拿去做更好的材质和光照。

公式:上采样中的时域混合权重

时域超分的混合过程可以用一个简单公式描述。设当前帧低分辨率采样颜色为 CtC_t,重投影后的历史帧颜色为 Ht1H_{t-1},那么当前帧输出 OtO_t 可以写成:

O_t = \alpha \cdot C_t + (1 - \alpha) \cdot \operatorname{Clip}(H_{t-1}, A_t)

其中 α\alpha 是当前帧的混合权重,AtA_t 是当前帧局部邻域的颜色范围,\operatorname{Clip} 表示把历史颜色裁剪到当前局部范围内。这个公式是理想化的,实际引擎里还会加入深度一致性、运动可信度、曝光补偿等更多因子。

这个公式的关键不在形式,而在权重的选择。如果 α\alpha 太大,画面会保留很多当前帧噪点;如果 α\alpha 太小,运动物体会有明显拖影。UE5 的 TSR 会根据每个像素的可信度动态调整这个权重,这也是为什么它比固定权重的 TAA 更稳。

写在最后

时域超分辨率技术发展到今天,已经不再是一个单纯的性能优化手段,而是现代渲染管线里不可或缺的一环。UE5.8 把 TSR、DLSS、FSR 都摆在你面前,但选哪个,没有标准答案。PC 高配玩家要画质,主机项目要稳定,Switch 2 要省电,每个场景都有不同的账要算。

老周后来在那个项目里养成了一个习惯:每次调完超分参数,他都会让 QA 用 PS5 录十分钟视频,再逐帧看树叶、头发、金属反光和远景文字。他说,参数表上的数字会骗人,但录像不会。这个习惯我一直记着。技术选型最终还是要回到真实内容上验证,而不是在文档里吵架。