ReSTIR 与软光追降级路径 2026:从硬件 RT 到混合渲染的统一架构
把渲染方程的形式化与 reservoir 的数学对偶串成一条主线,剖析 ReSTIR DI 的候选生成、时间域与空间域复用、ReSTIR GI 的多 bounce 推广、硬件 RT 与软光追降级路径的统一抽象,以及移动端 Mali/Adreno 的工程取舍;给出引擎程序员 5 个反直觉和 5 条可观测性清单。
约 40 分钟阅读11,931 字2 次阅读博主

把渲染方程的形式化与 reservoir 的数学对偶串成一条主线,剖析 ReSTIR DI 的候选生成、时间域与空间域复用、ReSTIR GI 的多 bounce 推广、硬件 RT 与软光追降级路径的统一抽象,以及移动端 Mali/Adreno 的工程取舍;给出引擎程序员 5 个反直觉和 5 条可观测性清单。

实时路径追踪曾经是图形学的"圣杯":Kajiya 1986 年给出的渲染方程是一个积分方程,理论上需要无穷多光线样本才能得到无偏的解。三十多年来,游戏工业始终在用"足够好的近似"——Phong、Cook-Torrance、SSR、SSAO、HBAO、Light Probe,本质都是在回避"真的要追那束光"。直到 NVIDIA 在 RTX 20 系上量产硬件 BVH 遍历单元,光线追踪才从论文走进了引擎代码;但到 2026 年的今天,真正在游戏里跑全路径追踪的产品仍然只有少数——Quake II RTX、Control、Cyberpunk 2077 的 RT Overdrive,以及 UE5.4+ Lumen 的 High Quality 模式。原因不是硬件不够,而是硬件 RT 与渲染目标的耦合极其复杂:移动端没有硬件 RT、主机端帧预算紧张、桌面端 RTX 4090 也只能在 4K + DLSS 下勉强跑满。本文的核心命题:ReSTIR(Reservoir Sampling for Real-Time Ray Tracing)是一套把硬件 RT 与软光追降级路径统一在同一个数学框架下的算法,它的真正贡献不是"DXR 调用更快",而是给出了任意硬件能力下都能跑出有意义结果的降级曲线。本文会从渲染方程的形式化开始,推导 ReSTIR 的核心数学(reservoir 与权重估计),再到时间域/空间域 reuse 的工程取舍,再到 ReSTIR GI 的多 bounce 推广,最后落到移动端 Mali/Adreno 的实战降级。
游戏渲染三十年的历史,本质上是一个在硬件能力曲线上滑动的过程:1995 年的 Quake 用顶点光照,2000 年的 DOOM3 用每像素光照,2010 年的 Crysis 用 SSAO+HBAO,2020 年的 UE4 用 SSR+Reflection Capture,2024 年的 UE5 Lumen 开始用硬件 RT 做部分反射和漫反射——但全路径追踪仍然稀缺。
稀缺的根本原因是光线的几何分布与场景几何分布不匹配。一条光子在场景中经历的反射次数服从几何分布(期望值与反射率、表面复杂度强相关),要得到无偏的最终结果,需要对每条路径采样足够多次。Kajiya 1986 年给出的蒙特卡洛估计是 O(1/√N) 收敛——也就是说,要降低 1 个数量级的方差,需要增加 100 倍样本。这与硬件光栅化的"三角形越多越慢"完全不同,后者是 O(N) 线性,而前者是 O(1/√N) 平方根倒数。
硬件 RT(DXR、Vulkan RT、Metal RT)把"单条光线的求交"从 CPU 卸载到 GPU 的 BVH 遍历单元,但它不解决"需要多少条光线"这个根本问题。一张 4K 帧 = 830 万像素,即便每像素只发射 1 条光线,一次屏幕空间反射就需要 830 万次 BVH 遍历;如果是 1 bounce 全局光照,需要 830 万 × N 次遍历;2 bounce 立方增长。这就是为什么"硬件 RT 时代"不等于"全路径追踪时代"——硬件解决了单次求交的成本,但没解决样本复杂度。
ReSTIR(Bitterli et al. 2020 SIGGRAPH)的核心贡献是把"需要多少条光线样本"这个问题,在数学层面转换为"需要多少个 reservoir 复用"。它给出的不是"我需要 64 spp"的硬约束,而是"我能用 1 spp 加上时间域 4 frame + 空间域 5 neighbor 的复用,等效于 64 spp 的偏差方差比"。这是一个降级曲线,不是一个开关。
到 2026 年,ReSTIR 已经被 NVIDIA RTXGI SDK、UE Lumen 的部分路径、Unity HDRP 的新 RTGI 实验分支、Frostbite 的 Strands 头发光照、id Tech 的部分反射路径采用。但工业落地仍然碎片化——因为 ReSTIR 的"复用"在时间域会引入 bias,在空间域会引入闪烁,这两件事必须在生产中各自做出 trade-off。本文要给出的,是一份把 ReSTIR 与硬件 RT 降级路径串起来的工程地图。
渲染方程是 Kajiya 1986 年给出的:
L_o(x, ω_o) = L_e(x, ω_o) + ∫_Ω f_r(x, ω_i, ω_o) · L_i(x, ω_i) · (ω_i · n) dω_i
其中 L_o 是出射辐射,L_e 是自发光,f_r 是 BRDF,L_i 是入射辐射(对 ω_i 方向上方的积分)。这个方程的解是一个高维积分——光线从摄像机出发,经过多次反射,每次反射都对应一个对 BRDF 的半球积分。
蒙特卡洛路径追踪对这个积分做无偏估计:对每条路径采样 N 个样本,得到
I = (1/N) · Σᵢ f(xᵢ) / p(xᵢ)
其中 p(xᵢ) 是采样概率密度。方差 Var(I) = σ²/N,N 越大方差越小。这就是为什么**"采样更多光线 = 更低噪声"是蒙特卡洛方法的根本性质**。
ReSTIR 的关键洞察是:路径采样的样本不是独立的,它们之间存在相关性。具体地说,在时间域,相邻两帧的同一个像素采到的入射方向几乎相同;在空间域,相邻像素的入射方向也高度相关。这种相关性可以用weighted reservoir sampling(WRS)的形式化来刻画。
一个 reservoir R 包含两个东西:一个候选样本 y(当前最优的样本)和一个累积权重 W(所有候选样本的归一化权重和)。在第 t 次候选到达时,我们以概率 Wₜ₋₁/(Wₜ₋₁ + w(yₜ)) 选择保留旧样本,以概率 w(yₜ)/(Wₜ₋₁ + w(yₜ)) 选择新样本;然后把 W 更新为 Wₜ₋₁ + w(yₜ)。这个过程的输出分布严格等于从所有候选中按权重 w(y) 做有放回抽样——这是 Efraimidis-Spirakis 2005 年证明的。
ReSTIR 把这个抽象应用到路径采样的每一步:第 t 帧的第 p 像素不是直接采样光线,而是维护一个 reservoir Rₜ,ₚ,这个 reservoir 累积了 (1) 当前帧的初始候选,(2) 时间域上前几帧邻域像素的 reservoir,(3) 空间域上当前帧邻域像素的 reservoir。最终 Rₜ,ₚ 输出一个样本 y*,用它的权重做 BRDF 积分的蒙特卡洛估计。
这个抽象的威力是:它把"采样 N 条光线"和"复用 1 个 reservoir"统一在同一个数学框架下。采样 64 条独立光线是方差 σ²/64;复用 1 个 reservoir 等效于"在样本空间做加权抽样",方差不再遵循 1/N 律,但代价是引入 bias(因为时间域复用旧帧的样本,这个样本在当前帧可能已经过时)。
Bias-variance 分解是理解 ReSTIR 工程取舍的核心:
E[(R̂ - R)²] = Bias(R̂)² + Var(R̂)
ReSTIR 的所有"参数"——时间域复用帧数、空间域邻居数、reservoir 权重 w(y) 的计算方式、jacobian 修正项——全部都是在 bias 和 variance 之间滑动。增加时间域复用 → bias 上升、variance 下降;增加空间域复用 → bias 上升、variance 下降(但空间域会引入闪烁)。引擎程序员调 ReSTIR 的本质工作,就是在给定的帧预算和视觉容忍度下,找到一个 bias-variance 的最优平衡点。
我们聚焦 Direct Illumination(DI),即一次反射的路径。给定点 x 处的像素 p,我们想估计它在当前帧 t 的入射辐射 L_i(x, ω_i),其中 ω_i 是来自光源方向的入射光。
经典路径追踪的做法是:采样 N 个光源方向,每个方向追踪一条 shadow ray,看是否能到达光源,然后做加权平均:
L̂_i(x) = (1/N) · Σₖ (V(x→yₖ) · L_e(yₖ) · G(x, yₖ) · cos θₖ) / p(yₖ)
其中 V 是可见性测试(0 或 1),G 是几何因子,cos θ 是入射余弦。方差来自"看不见光源"的样本——如果 5 个光源里只有 1 个从 x 可见,4 个样本权重为 0,这相当于浪费了 80% 的样本预算。
ReSTIR DI 的做法是:把 reservoir 维护在"光源空间",而不是"屏幕空间"。具体步骤:
关键技巧:jacobian 修正项 J。时间域复用时,前帧的样本 y 是从光源空间采样的,但前帧的像素 p' 与当前帧的像素 p 可能不重合——比如相机移动、物体运动、屏幕抖动。这种情况下,把前帧的 reservoir 直接合并到当前帧是错的,需要乘以 J(x, x') 把"前帧像素处的 BRDF × cos θ"重新映射回"当前帧像素处的 BRDF × cos θ"。这个 J 项是 ReSTIR 数学正确性的核心,也是实现里最容易被忽略的地方——省略 J 项的结果是 bias 在运动场景下爆炸。
ReSTIR DI 的输出样本数 N 是这样分布的:当前帧 M 个候选 + 时间域约 T 个复用 + 空间域约 S 个复用。在 M=1, T=4, S=5 的典型配置下,每个像素"实际采样" 1 + 4 + 5 = 10 个候选,但等效样本数可以超过 64(因为复用候选的 BRDF 加权是"聪明的",而不是"均匀的")。这就是 ReSTIR 的核心收益。
到 2026 年的工业实现里,ReSTIR DI 已经被进一步优化:重要性采样光源(ReSTIR DI 的"RIS"前身) + reservoir merging 时的 streaming 算法 + jacobian 项的预计算表——这些工程优化让单帧的 ReSTIR DI 可以在 4K 屏上跑满 60 fps,前提是硬件支持足够的 BVH 吞吐。
ReSTIR 的"复用"在工程上带来两个核心矛盾:时间域 bias vs 视觉一致性、空间域 variance vs 闪烁。这两件事必须分开讨论。
时间域 bias:当前帧复用前 N 帧的 reservoir,意味着光线样本"延迟"了 N 帧。在静态场景下这没问题——前 N 帧的光线样本今天仍然有效;但在动态场景下(物体运动、光照变化、相机快速移动),延迟的样本会引入"拖影":光源移动时,反射表面会出现一条"延迟的"反射,而不是当前的反射。ReSTIR 用两个工具缓解这个问题:
工业经验:在 60 fps 的室内场景里,temporal reuse 帧数取 4-8 是甜点;在 30 fps 的室外大场景里,取 2-4;在 120 fps 的 VR 头显里,因为双眼需要同步,取 1-2(完全不用 temporal,只用 spatial)。
空间域 variance:当前帧的邻域像素 reservoir 复用,会引入"屏幕空间的低频噪声"——具体表现是闪烁(flicker)。闪烁的物理原因是:reservoir 的归一化权重 W 是一个有偏估计(它等于真实权重的蒙特卡洛估计,但单个像素的 W 会有噪声),当邻域像素的 W 噪声不一致时,相邻像素的最终光照估计会有差异,人眼对低频差异的闪烁极其敏感。
ReSTIR 控制闪烁的工程工具:
移动端的特殊取舍。Mali GPU 是 tile-based,Adreno GPU 是 slice-based(分块渲染但有 binning),它们都严重忌讳跨 tile 的全局状态同步。ReSTIR 的时间域复用本质上是"读前帧的 ROP(raster operations)结果",在移动端这意味着必须把前帧的 reservoir buffer 复制到当前帧的 tile 内存——带宽开销是 O(RT × tile 数),在 4K + tile 数 = 64 时,这个带宽不可忽视。移动端的工程取舍是:
到 2026 年的 Frostbite(EA)、Lumen(Epic)、HDRP RTGI 实验分支(Unity),这些工程取舍都已经在产品代码里。工业经验:ReSTIR DI 在桌面端 + RTX 3060+ 上可以 4K/60fps 跑满,在 PS5 上 1440p/60fps 跑满,在 Mali G715(中端移动)上 1080p/30fps 跑满。
DI 处理的是"一次反射"的全局光照,但真实场景里大部分光线都经历多次反射(典型的室内场景,主光源照到地板,地板反射到天花板,天花板再反射到墙面,墙面最终到达眼睛——这是 3 bounce 路径)。多 bounce 的全局光照(GI)是 ReSTIR 推广的核心难点。
ReSTIR GI(Bitterli et al. 2021 SIGGRAPH)的数学推广是:把 reservoir 维护在"路径节点"上,而不仅是"光源方向"上。每个路径节点(弹射点)维护一个 reservoir,reservoir 里的候选是"下一次弹射的方向"。这个推广让 ReSTIR GI 能处理任意 bounce 数,但代价是jacobian 项的复杂度爆炸——每多一次 bounce,J 项的维度就多一维。
ReSTIR GI 的核心工具:
工业上的妥协:ReSTIR GI 在工业里通常被截断到 2-3 bounce,因为超过 3 bounce 后:
UE Lumen 的 High Quality 模式用的是 ReSTIR GI 的 2 bounce 截断,加上一个"screen-space probe"作为 fallback,处理动态光源变化。Unity HDRP 的 RTGI 实验分支用的是 3 bounce + Light Probe 混合。Frostbite 的 Strands 头发光照用的是"特殊化的 ReSTIR GI"——头发是各向异性 BRDF,普通的 reservoir 权重计算不适用,他们写了定制版。
DDGI(Dynamic Diffuse Global Illumination, Majercik et al. 2019)是 ReSTIR GI 的另一种实现路径。DDGI 不维护 reservoir,而是在场景里撒"irradiance probe"(通常 0.5m 间距的体素网格),每个 probe 用少量光线 sample 周围场景的 irradiance,然后做 screen-space 插值。DDGI 的优点是完全确定性的——probe 是不动的,光照变化只影响 probe 的内部值,屏幕空间插值没有 bias;缺点是 probe 网格的分辨率限制了高频细节(动态光照的边缘会出现"梯子"现象)。
工业经验:ReSTIR GI 适合动态光源多的场景(室内多灯、动态特效),DDGI 适合大世界静态光照(开放世界、室外场景)。两种方法在生产里经常被混合使用——静态部分用 DDGI,动态部分用 ReSTIR GI。
硬件 RT 的工程现实是:不同 GPU 的 RT 能力天差地别。NVIDIA RTX 20/30/40 系有完整的 BVH 遍历单元,AMD RDNA 2/3 有简化版的 RT 单元,Intel Arc 有 RT 单元但吞吐低于 NVIDIA,Apple M1/M2/M3 有 RT 单元但只支持 triangle intersection,Qualcomm Adreno 700 系列有部分 RT 支持,ARM Mali G715+ 有 RT 支持但性能受限,移动 GPU 的"硬件 RT"严格来说更接近"硬件辅助的软 RT"。
生产引擎必须面对的现实:同一份代码要在所有这些硬件上跑出"有意义的结果",而不是"有/没有"的二值。这就是软光追降级路径(software ray tracing fallback)存在的理由。
统一的抽象层是 2024-2026 年的工业共识:抽象出"IRaytracer"接口,把"硬件 RT 调用"和"软件 RT 实现"封装成同一个调用层。引擎上层(ReSTIR、ReSTIR GI、DDGI)只调 IRaytracer.TraceRay(...),具体实现由运行时根据硬件能力选择:
关键观察:ReSTIR 的降级曲线在 Tier 0 → Tier 1 → Tier 2 是平滑的——因为 ReSTIR 本质上是"采样 + 复用"的算法,它的输入是"光线追踪的结果",无论这个结果是来自硬件 BVH 还是软件 BVH。在 Tier 2(纯软件)下,单条光线的 cost 高 10-100 倍,但 ReSTIR 的复用可以让等效样本数保持一致——所以视觉质量不会断崖式下降,只是帧率降低。
Tier 3(完全不用 RT)下,ReSTIR 的输入换成 SSR(屏幕空间反射)——SSR 本质上是"短距离的 RT 近似",它能处理的反射距离受 depth buffer 限制。ReSTIR 在 Tier 3 下用 SSR 的结果作为"初始候选",然后用 reservoir 复用扩展时间域和空间域。这种"ReSTIR over SSR"的组合在 Frostbite、UE Lumen Low Quality 模式里都有实现。
BVH 构建是另一个工程现实。ReSTIR DI/GI 每帧需要 BVH 用于 shadow ray 追踪。BVH 的构建在 NVIDIA RTX SDK 里是 1-2 ms(典型场景),但对动态物体(粒子、布料、刚体)需要 incremental rebuild,这是 2026 年的研究热点(Temporal BVH、Bottom-up SAH、GPU 端 parallel build)。到 2026 年,GPU 端 incremental BVH 构建已经在 Frostbite、UE Lumen、id Tech 里落地——单帧 BVH 构建时间从 5-10 ms 降到 1-2 ms。
IRaytracer 抽象的工程价值:让引擎上层(ReSTIR、Light Probe、SSR)的代码不需要关心硬件差异——一份代码,所有硬件。NVIDIA RTXGI SDK、AMD Radeon Rays、Intel Embree、Apple Metal RT 都是这个抽象层的不同实现。
移动端是 ReSTIR 最复杂的战场。截至 2026 年,没有任何一款旗舰手机能在原生分辨率下跑 60 fps 的全路径追踪——iPhone 15 Pro Max 的 A17 Pro 有硬件 RT,但单帧 RT 吞吐不足以支持全场景;Snapdragon 8 Gen 3 的 Adreno 750 有 RT,但同样受限。
移动端的工程现实是:Tile-Based Deferred Rendering(TBDR)是主流架构(Mali、Adreno、PowerVR 都是),Apple GPU 也是 slice-based。TBDR 把屏幕分成小块(tile),每个 tile 内的所有像素一次性渲染完,中间结果存在 tile memory(高速片上 SRAM),最后写回主存。TBDR 的优势是带宽极低(片上 SRAM 比主存快 10-100 倍),但代价是任何跨 tile 的全局状态(比如 RT 的 BVH、ReSTIR 的 reservoir buffer)都必须在 tile 间同步——这个同步的开销是移动端的命门。
移动端 ReSTIR 的关键改造:
Mali vs Adreno 的差异:
到 2026 年的工业实践:
关键观察:移动端的 ReSTIR 不是"减配版桌面端",而是完全不同的工程取舍。桌面端追求"高视觉质量",移动端追求"高能效比"。两者的优化目标不同,算法实现也不同。
我们用三个工业引擎的实际实现来落地 ReSTIR 的工程取舍。
UE Lumen(Epic, 2022-2026 持续演进):
Unity HDRP(Unity, 2023-2026 实验分支):
Frostbite(EA, 2022-2026 持续演进):
id Tech(id Software, 2023-2026 持续演进):
关键观察:ReSTIR 在生产代码里不是"一个算法",而是"一族算法的统称"。每个引擎根据自身的技术债务、目标硬件、视觉风格,做不同的工程取舍。这种碎片化是正常的——没有任何两个引擎会用完全相同的 ReSTIR 实现。
最后给读者一份"反直觉 + 可观测性"的清单。这些是 ReSTIR 落地时容易踩的坑。
5 个反直觉:
5 条可观测性清单:
给读者的最后一个建议:不要从论文读 ReSTIR,要从代码读。NVIDIA RTXGI SDK 是工业里最完整的 ReSTIR 实现,代码量约 5000 行 C++,从候选生成到时间域复用到空间域复用都齐全;Frostbite 的 Strands 头发光照是定制版 ReSTIR 的最佳案例;UE Lumen 的源码(虽然不开源,但 Epic 在 GDC 上有详细分享)是工业里最复杂的 ReSTIR 集成。读这三份代码,胜过读 100 篇论文。
一句话摘要:ReSTIR 把"采样 N 条光线"转换为"维护一个 reservoir",在硬件 RT 与软件 RT 之间提供了一条 bias-variance 可调的统一降级曲线——它在桌面端是 RTXGI,在移动端是 half-res + tile-local + 64 光源,而这一切的核心是数学上的 weighted reservoir sampling 与工程上的 jacobian 修正项。
Conversation
0 条