实时光线追踪与混合渲染 · 第 七 篇

降噪(下):从 SVGF 到神经降噪的取舍

2026-09-2116,923 字28 张配图

核心问题:上一篇(ep06)把时域复用讲透了——用运动矢量把历史帧搬过来,1 spp 的噪声就能压到肉眼可接受,代价是 disocclusion 与鬼影。可时域那一套只解决"同一个像素在时间轴上抖不抖",它对单帧内部的空间相关性毫无办法:一个像素的估计误差和它邻居的估计误差是同源的,时域滤波不会让它们互相纠正。

时域之后剩下的残差是空间上的——低频块状噪声和高频斑点。要补上这一块,就得让邻居互相帮忙,而"邻居怎么加权"就成了整条降噪链上最容易调错、也最值得讲清的一环。

本篇来回答——空间滤波的邻居该按什么加权、à-trous 为什么要"打洞"、方差怎么就变成了驱动信号、三根 σ 调错了会看到什么、SVGF 的三个软肋在哪、A-SVGF 怎么把固定参数换成自适应、生产级的 ReBLUR/ReLAX 怎么选、TSR 与 DLSS 光线重建把这件事推进到了哪一步、以及神经降噪到底值不值。十件事合起来,才是"降噪(下)"的完整账。

本篇是降噪话题的下半篇,承接 ep06 的时域结论,只讲空间滤波与学习型重建;不重复展开时域复用的原理与失效模式(只在必要处引用 ep06 的结论)。本篇也不涉及:离线渲染的降噪(路径追踪器内部的样本累积与自适应采样)、视频编解码的降噪(时域/空域视频去噪)、摄影后期降噪、以及图像超分(仅涉及 DLSS 光线重建中与降噪融合的那部分)。这么切是因为这四类问题的约束条件与实时渲染完全不同——离线渲染有无限时间,视频编解码处理的是已有像素,摄影后期不关心延迟,硬凑在一起只会把"预算下的取舍"讲糊。

本系列(实时光线追踪与混合渲染,共 12 篇)的前置阅读:第一篇《分界线》给出了三类效果判据与三条成本模型;第二、三篇《加速结构工程账》上下给出了构建/更新/压缩与动态场景的账;第四篇《命中着色器与 SBT》讲了找到三角形之后该调用哪段着色器;第五篇《混合管线编排》讲了光栅化与光追怎么接力、性能预算怎么分;第六篇《降噪(上)》给出了时域复用的数学、运动矢量与重投影、六类失效模式。其余前置专题为算法层《实时渲染管线探析》5 篇、约束层《移动端图形与功耗》7 篇、硬件实现层《GPU 架构剖析》12 篇。

本文由@溯光 出品,转载请注明出处。本文同步发布于公众号与个人博客:技术博客页。

三维渲染成品中的高频细节与镜面反射——降噪要守住的正是这类信息
题图三维渲染成品的高频细节与镜面反射(降噪要守住的信息)@ Schäferle / Wikimedia Commons,CC0

点题:空间滤波补的是哪一块

0.0 内容总览:本篇知识地图

先放一张总表,把十章各自回答的问题钉在这里。后面每一章都在展开其中的某一行。大家可以先从这张表里找到自己最关心的问题,再跳到对应章节去看。

横向覆盖全篇十章,纵向给出每章的核心问题、结论与主要依据。第一章讲"邻居该怎么加权",第二章讲"半径怎么变大而不变贵",第三、四章讲"方差怎么驱动、三根 σ 怎么调";第五章讲"SVGF 哪里会坏",第六章讲"怎么自适应地修";第七章是生产级实现的两条路线,第八章是学习型重建,第九章是两边的工程账,第十章收束交棒。

章 回答的问题 核心结论 主要依据
一 邻居该按什么加权 联合双边滤波:空间距离 × 深度/法线/亮度三组边缘停权 SVGF 论文 [Schied 2017]
二 怎么把半径做大而不变贵 à-trous 空洞卷积:步长翻倍、taps 不变;瓶颈在访存不在算术 JCGT vol.15 no.1 [2026]
三 方差怎么就驱动了整条链 一二阶矩 → 时域方差 → 同时决定 α 与滤波强度 SVGF 论文 [Schied 2017]
四 三根 σ 怎么调、错了什么样 σz/σn/σl 各有"过小/过大/失配"三类可见症状 实作资料 + 社区实践
五 SVGF 哪里会坏 鬼影残留、过模糊、高动态下方差估计失效 Q2VKPT [GDC/GTC 2019]
六 怎么自适应地修 逐像素 α + 自适应迭代数,平均 pass 数 5 → 2–3 A-SVGF [Schied 2018]
七 生产级怎么做 ReBLUR 自稳定递归模糊(≤32 帧)/ ReLAX 球谐域拆分 GTC 2020 · NRD 文档
八 学习型重建到哪一步了 单网络融合降噪+超分;2026 年的 4.5 版本已覆盖全部 RTX DLSS 4.5 公告 [2026]
九 神经降噪值不值 五维工程账:内存/延迟/可移植性/调参/失败模式,给判据不给结论 全篇汇总
十 边界与交棒 本篇不涉及什么;下一篇走向采样与重要性重采样 全篇总结

0.1 时域之后剩下的残差

先来看看一张最朴素的证据。这张图把"有抗锯齿"与"无抗锯齿"两种画法摆在一起——左边是阶梯状的硬边,右边是过渡带。它说明的是一件很基础的事:当一个像素只取一个样本时,误差不是随机的白色噪声,而是有结构的——它会沿着几何边缘排成一条链,也会在细密纹理里结成块。

抗锯齿与无抗锯齿对照:采样不足的误差沿几何边缘排成结构化的阶梯
图 7-1抗锯齿与无抗锯齿对照:采样不足的误差沿几何边缘排成结构化的阶梯@ George Shuklin / Wikimedia Commons,Public domain

路径追踪里的噪声同样有结构。1 spp 时每条光线只随机挑一个方向、随机挑一个光源采样点,单条光线的辐射度估计方差极大;但相邻像素的随机数通常来自同一个低差异序列的相邻项,于是它们"错得相似"。这就解释了为什么降噪器不能只看自己——邻居的误差与你的误差是同源的,把它们平均起来,误差才会互相抵消。

这件事有直接的量化证据。这张图是同一信号按 4 个样本与 32 个样本分组平均的结果:样本数从 4 涨到 32 只多了 8 倍,但方差的下降幅度远大于 8 倍观感上的改善——因为噪声的方差与样本数成反比,Var ≈ σ² / N,标准差只按 1/√N 下降。

同一信号按 32 样本分组平均:噪声随样本数下降
图 7-2同一信号按 32 样本分组平均:噪声随样本数下降@ Martinm de / Wikimedia Commons,CC BY-SA 4.0

把这套逻辑搬到实时渲染里就是一句话:时域复用是在时间轴上攒样本,空间滤波是在同一帧里借邻居的样本。前者受限于帧间连续性,后者受限于边缘——两者是互补的,谁也替代不了谁。

0.2 降噪器在整条管线里的位置

再定一次坐标。下面这张图把降噪器放回完整的混合管线里:左边是 G-buffer 与光追的原始输出(颜色、深度、法线、运动矢量、粗糙度),中间是降噪器内部的两段——时域累积与空间滤波,右边才是最终呈现。ep06 拆的是中间那段的前半,本篇拆它的后半。

降噪器在完整混合管线中的位置:G-buffer 与光追原始输出 → 时域累积 → 空间滤波 → 呈现
图 7-3降噪器在完整混合管线中的位置:G-buffer 与光追原始输出 → 时域累积 → 空间滤波 → 呈现@ 本系列绘制

值得先说清的是空间滤波阶段手里有哪些输入。这不是随便的加权平均,而是一组带语义的辅助缓冲(auxiliary buffers):

  • 深度(depth):判断邻居在不在同一层表面上;
  • 法线(normal):判断邻居的朝向是否一致,曲面折角与硬边在这里露馅;
  • 亮度(luminance):判断邻居的颜色是否来自同一段光照与材质;
  • 方差(variance):判断这一块到底有多脏,决定滤波强度与迭代层数。

少一个都会出问题:只用深度会在法线折角处渗色,只用亮度会把低对比度的真实纹理当成噪声抹掉。OK,言归正传,下面我们从最基本的一步开始——邻居该怎么加权。

0.3 口径声明

本篇的性能与内存数字基于以下口径,跨来源比较时请先对齐:

  • 硬件:以 RTX 3080/4070 级别为参考(主流高端桌面 GPU),涉及内存的条目按 4K 分辨率计;
  • 分辨率与帧率:1080p / 1440p(游戏常用),帧率目标 60 fps(16.67 ms 帧预算);
  • 光追效果:以 1–2 spp 路径追踪为基准(最低有效采样率),GI/反射/阴影同开;
  • 降噪器版本:SVGF 按原论文设定(固定 5 级 à-trous)、NRD 按 ReBLUR/ReLAX 默认、DLSS 光线重建按 2026 年 4.5 版本;
  • 厂商宣传数字的处理:凡属厂商公布的相对倍数(如 NRD 相较 SVGF 的 +50%),一律保留其原口径并标注来源,不转述为跨项目定论。

一、联合双边滤波:为什么"邻居"不是随便加权的

1.1 朴素高斯模糊的代价:噪声和边缘一起没

OK,空间滤波的起点是高斯模糊(Gaussian Blur):离得近的邻居权重大,离得远的权重小,权重按高斯函数衰减。它的数学干净、可分离、实现简单,图像处理里用了半个世纪。

但它有一个致命问题:它不知道边缘的存在。这张图是 Canny 边缘检测流程的第一步——对原图做高斯模糊后的结果。大家注意看:噪声确实被压下去了,但物体轮廓也一起软掉了。原因很直白,高斯核的权重只看空间距离,不看内容;边缘两侧的两个像素在几何上是同一块砖,只是恰好被轮廓线分开了,高斯模糊照样把它们平均到一起。

高斯模糊后的图像:噪声与轮廓被同时软化
图 7-4高斯模糊后的图像:噪声与轮廓被同时软化@ Babujayan / Wikimedia Commons,CC BY 3.0

顺带说一句,Canny 流程本身要在模糊之后才算梯度,这个顺序不是随便定的——先模糊再求导,等于在求导前把高频噪声掐掉,否则梯度图上全是噪声引起的假边缘。这个"先抑制、再判边"的思路,和我们下面要讲的空间滤波是同一个道理。

1.2 保边平滑:第三条路

既然"只看距离"不行,"完全不看距离"也不行,好了,既然两头都不合适,那就有一条中间路:按内容决定要不要平滑。这类方法有个总称叫保边平滑(edge-preserving smoothing),最直观的一族是各向异性扩散(anisotropic diffusion,又称 Perona–Malik 扩散):它在平坦区域像热扩散一样把像素抹平,到了梯度大的地方就自动刹住,不让跨边缘的颜色通过。

含噪图像经各向同性扩散(左)与各向异性扩散(右)的最终结果对照(原作是 60 帧动图,此处取末帧)
图 7-5含噪图像经各向同性扩散(左)与各向异性扩散(右)的最终结果对照(原作是 60 帧动图,此处取末帧)@ CapeItalist / Wikimedia Commons,CC BY 4.0(基于 Pen Waggener 的照片,CC BY 2.0)

图里这张对照很能说明问题:各向同性(isotropic)那一版糊成一团,各向异性(anisotropic)那一版把纹理细节留住了。这条思路的直接后代就是双边滤波(bilateral filter)——把"内容"直接写进权重里:权重 = 空间距离项 × 亮度差项。

到这里轮廓就清楚了。先做一次二次归类:空间滤波这一族可以按"权重里放了什么"排成一条线:

方案 空间距离 亮度/颜色差 深度 法线 用在哪
高斯模糊 ✅ ❌ ❌ ❌ 通用图像处理、Bloom 等
双边滤波 ✅ ✅ ❌ ❌ 摄影后期、降噪预处理
各向异性扩散 ✅(迭代) ✅(梯度) ❌ ❌ 医学影像、图像增强
联合双边滤波 ✅ ✅ ✅ ✅ 实时光追降噪(本文主线)
à-trous 小波滤波 ✅(多级) ✅ ✅ ✅ SVGF 的空间滤波阶段

"联合"(joint)二字的含义就在这里:除了当前颜色,它还吃别的缓冲——深度和法线来自 G-buffer,是几何侧给的"这块像素属不属于同一个表面"的判据。

1.3 卷积:邻居加权求和的标准形式

OK,讲具体权重之前,先把数学形式固定下来。空间滤波的本质是一次卷积(convolution):

I'(p) = Σ_{q ∈ Ω} w(p, q) · I(q) / Σ_{q ∈ Ω} w(p, q)

其中:

  • p 是当前像素,q 是邻域 Ω 里的某个邻居;
  • I(q) 是邻居的输入值(在光追降噪里通常是颜色,也可以对辐照度、方差等分别滤波);
  • w(p, q) 是权重,整个空间滤波的设计都在这一项里;
  • 分母是权重的归一化,保证滤波后整体亮度不漂移。

写成这样以后,"高斯模糊 vs 联合双边滤波"的区别就只剩 w 的写法了:

w_gauss(p, q)  = exp( -|p - q|² / (2σ_spatial²) )
w_joint(p, q)  = w_gauss(p, q) · exp( -|I(p) - I(q)|² / (2σ_l²) )
                            · exp( -|z(p) - z(q)|² / (2σ_z²) )
                            · max(0, dot(n(p), n(q)))^σ_n

四个因子相乘,各自管一件事:空间距离管"离得远不远",亮度管"颜色像不像",深度管"在不在同一层",法线管"朝向一致不一致"。任意一项趋近于零,w 就趋近于零,这个邻居直接被排除。

卷积与相关的对照:邻域加权求和的标准形式
图 7-6卷积与相关的对照:邻域加权求和的标准形式@ Cmglee / Wikimedia Commons,CC BY-SA 3.0

这里有个容易被忽略的实现细节:着色器里跑的多半是"相关"(correlation)而不是严格的卷积,差别只在核要不要翻转。因为我们的核是 w(p,q) 这种对称形式(|p-q| 用的是距离,dot(n(p), n(q)) 是对称的),翻转不翻转结果一样,所以工程上通常直接写成 Σ w · I,不再纠结定义。但如果哪天核变成非对称的(例如按梯度方向加权),这个差别就要回来了——而且回归时会表现为"图像被镜像"这种很难一眼看出的错误。

1.4 联合双边滤波核:一个像素的权重怎么算出来

下面我们开始算一像素的账。取当前像素 p 为中心,看它 3×3 邻域里的一个邻居 q,权重是四个因子的乘积:

联合双边滤波核的四步计算:取邻域 → 算四组因子 → 相乘 → 归一化
图 7-7联合双边滤波核的四步计算:取邻域 → 算四组因子 → 相乘 → 归一化@ 本系列绘制

这里要提醒一个实现上的坑:权重必须先算完再归一化,不能边算边累加。因为归一化分母 Σ w 在算完所有邻居之前是未知的;早期一些实现为了让循环更快,用固定分母(例如按 3×3 除以 9),结果在边缘密集处会整体偏暗——那不是算法错,是归一项错。

另一个坑是深度的写法。深度差可以直接比差值 |z(p) - z(q)|,也可以比"沿视线的相对深度",后者在掠射角与大面积斜面上明显更稳。工程上更常见的一招是把深度转成线性深度再比(透视投影后的非线性深度在远处极密,直接比会把远处的同一表面判成两层)。

1.5 三组边缘停权:为什么必须同时存在

这张图是本篇第一个"自家账本"——三组权重随差异的衰减曲线画在一起,横轴是差异量,纵轴是权重。曲线形状是一样的(都是高斯型),但横轴的量纲完全不同:深度差是距离量纲,法线差是夹角(弧度或余弦),亮度差是颜色量纲。

σ 是衰减尺度而非硬阈值:曲线偏陡与偏平各自的代价
图 7-8σ 是衰减尺度而非硬阈值:曲线偏陡与偏平各自的代价@ 本系列绘制

好了,三者为什么缺一不可——它们各自都在补别人的漏:

  • 只有深度:斜面上深度梯度大,同一块地板被切成两半;法线折角处深度却是连续的,于是折角两侧互相渗色。
  • 只有法线:法线对薄几何失效——一片薄墙的正反面法线几乎相反,但夹角判据在掠射时因为法线接近垂直视线而变得迟钝;同时法线对"同一平面上的两块材质"毫无分辨力。
  • 只有亮度:低对比度的真实纹理(例如同一材质上的缓慢明暗过渡)会被当成噪声抹掉;反过来,噪声本身造成的亮度差又会被误判成"这里是边缘"。

这就是联合双边滤波的核心理念:判据要互补,因为没有任何单一物理量能同时刻画"是不是同一个表面、是不是同一段光照"。深度管空间上的层次,法线管朝向的一致性,亮度管辐射学上的相似性。三组一起用,才把"邻居"这两个字落到实处。

到这里第一章的闭环还剩最后一环——这一套东西在工业实现里怎么落地。SVGF 的参考实现公开在 NVIDIA Research 的论文页上,逐行代码级的讲解见 Jacques Pillet 的 SVGF 实作笔记,配套的矩与方差写法见 Adventures in Hybrid Rendering;UCSB 的 GAMES202 第 14 讲则把工业解法这一节讲成了可以直接照做的作业。这一章的公式在以上三处都能对上,感兴趣的朋友可以交叉核验。


二、à-trous:用"打洞"换半径

2.1 从"一个大核"到"多级小核"

第二章要解决的问题是:第一章定了权重的写法,但还有一个更实际的约束没解决:半径要多大。

降噪需要的往往不是 3×3 的邻域,而是几十个像素的跨度——因为残差是低频的块状噪声,半径不够大就压不下去。可如果真去做一个 33×33 的核,采样数就是 1089 次,每像素一千多次纹理读取,实时渲染的预算根本承受不起。

à-trous 小波滤波换了个思路:不在一次里做大核,而是分成多级做小核。每一级只用固定数量的采样点,但把采样点之间的间隔(步长)翻倍——第一级步长 1(就是普通 3×3/5×5),第二级步长 2,第三级步长 4,第四级步长 8,第五级步长 16。这样一来,5 级之后有效覆盖半径达到了几十个像素级别,而每级的采样数始终不变。

要理解它为什么合理,得先看它借用的工具——小波变换(wavelet transform)。下面这张图是二维离散小波分解(2D DWT)的结果:一次分解会把图像拆成四个子带——左上角的低频近似(approximation)与三个方向的细节(水平、垂直、对角)。

二维离散小波分解的子带结构:低频近似 + 水平/垂直/对角细节
图 7-9二维离散小波分解的子带结构:低频近似 + 水平/垂直/对角细节@ Tristesse1024 / Wikimedia Commons,Public domain

这张图解释了小波族的共同特点:它不是一次把图像换到频域,而是分层地换——每一层只在上一层留下的低频部分继续分解。这个"逐层递进"的结构正是 à-trous 的骨架:à-trous 的每一级也是在一个尺度上做一次加权平均,下一级在更大的尺度上继续。

2.2 名字的来历:为什么要"打洞",以及为什么是 B 样条

à-trous 是法语,直译是"带洞的"(with holes)。这个名字描述的就是它的做法:把上一级的核"插进空洞"再膨胀——原来相邻的两个采样点,现在中间塞进一个空洞,核的实际跨度变成原来的两倍,但采样点数量不变。

那这个核本身长什么样?经典 à-trous 小波变换用的尺度函数来自 B 样条(B-spline)族——它天然紧支撑、可分离、且不带负旁瓣,用它做加权平均不会引入振铃(ringing)。这张图是二阶 B 样条小波基函数的形状:

基数 B 样条小波基函数(二阶):紧支撑且无负旁瓣
图 7-10基数 B 样条小波基函数(二阶):紧支撑且无负旁瓣@ Krishnachandranvn / Wikimedia Commons,CC BY-SA 3.0

紧支撑这件事在工程上很值钱:核的支撑范围决定了"一个像素会读到多远的邻居"。支撑越紧,越容易放进缓存、越容易做波前式的并行;而 B 样条族在给定支撑宽度下能有最好的频率选择性——说白了就是在"该保留的细节"和"该压掉的噪声"之间划界划得最干净。

SVGF 的空间滤波阶段用的就是这个族里的核:5×5 大小、权重取自三次 B 样条(B3-spline)的一维权重序列,再按可分离性拆成横竖两趟。逐级的实现与核的写法在 SVGF 实作笔记里有完整对照。

2.3 五级结构:有效半径怎么算出来

先来看看每一级的采样点摆出来是什么样。这张图就是本篇第二个自家账本——五级 à-trous 的采样点分布,横轴是像素坐标,每一行是一级,步长分别是 1、2、4、8、16:

à-trous 五级的覆盖半径与采样数:步长翻倍而每级采样数不变
图 7-11à-trous 五级的覆盖半径与采样数:步长翻倍而每级采样数不变@ 本系列绘制

算一下覆盖半径:如果每级用的是 5×5(即每边 2 个采样点、间距为步长),那么第 i 级的覆盖半径是 2 · 2^{i-1} 像素。五级下来,最大一级的半径是 2 · 16 = 32 像素,单边直径 64 像素——而累计采样数只有 5 × 25 = 125 次,对比朴素大核的 1089 次,采样数降到约 1/9,覆盖范围反而更大。

这里有个值得单独说清的工程判断:为什么是 5 级,不是 3 级或 8 级?

  • 级数太少(例如 2–3 级):最大半径只有 4–8 像素,低频块状噪声压不下去,画面会留下"脏底";
  • 级数太多(例如 8 级):最大半径到 256 像素,这时跨区域采样不可避免——即使边缘停权在工作,几十像素外的邻居也很可能属于完全不同的光照条件,滤出来的颜色会脱离本地实际,表现为大范围的"糊"与光照渗漏;
  • 而且每一级都是一次全屏 pass,多一级就多一次全屏读写,成本是线性增长的。

5 级是一个被工业界反复验证过的折中:半径够压住 1–2 spp 的块状噪声,又不会跨到大到失真的范围。这也是 SVGF 原论文的默认设定,后续实现基本沿用。

2.3.1 实现顺序:可分离性与两趟滤波

还有一个实现层的判断必须写下来,因为它直接决定了这笔账怎么算:B 样条核是可分离的(separable)。

一维的 B3 样条权重序列是 [1/16, 1/4, 3/8, 1/4, 1/16],二维核是这两个序列的外积。这意味着一个二维 5×5 滤波可以拆成"先水平一趟、再垂直一趟"两次一维滤波:每像素的采样数从 25 次降到 5 + 5 = 10 次。这不是优化技巧,而是这些核被选中的理由之一——不可分离的核(例如按梯度方向旋转的自适应核)就得老老实实付 O(r²) 的采样代价。

落到 à-trous 的每一级上,结构是这样的:

//--------------------------------【à-trous 单级滤波(可分离 · 横竖两趟)】--------------------------------
// 描述:第 level 级的空洞卷积,步长 step = 1 << level;辅助缓冲只读一次
//------------------------------------------------------------------------------------------
[numthreads(8, 8, 1)]
void AtrousLevel(uint3 id : SV_DispatchThreadID) {
    //【1】取本级需要的四类输入:颜色、深度、法线、时域方差
    //【2】横竖各采 5 个 tap,tap 间距 = step;权重 = B3 样条权重 × 三组边缘停权
    //【3】在同一趟内完成权重求和与归一化后写回;level+1 时 step 翻倍
}

下面对这段结构特别说明三点:

  1. 步长是本级唯一的变量。五级之间除了 step 之外没有任何差别,所以实现上通常就是一个循环五次 dispatch,把 step 当参数传进去。这也解释了为什么"减级数"的收益是线性的——减的是 dispatch 次数,不是每级内部的计算量。
  2. 归一化必须在同一趟内完成。横竖两趟各做一次归一化,不能"横向只累不加权、纵向统一归一化",因为纵向无法知道横向跳过了哪些邻居。
  3. 辅助缓冲只读一次。这是 2.4 节那笔访存账的落点:每一级都要读颜色、深度、法线、方差,读的次数乘以级数就是总的读取量;把本级要用的 tap 邻域先搬到寄存器或共享内存,再在片内做加权,避免同一纹素被反复取——JCGT 2026 的优化正是沿这个方向做的。

2.4 那它为什么慢:JCGT 2026 给的量化答案

OK,那么一个自然的问题是:既然采样数已经降到 1/9,为什么 à-trous 还是被公认是降噪里最贵的一段?

JCGT(Journal of Computer Graphics Techniques)第 15 卷第 1 期在 2026 年 1 月专文优化了 SVGF 的 à-trous 阶段,给出的答案很明确:瓶颈不在算术量,而在访存模式。每个全屏 pass 都要读一遍颜色、深度、法线、方差,写一遍结果;5 级就是 5 遍"读一大把纹素、写一张全屏图"。滤波本身的计算(几次乘加)远不是问题,重复读取与缓存命中率才是。

该文针对现代 GPU 的内存访问特征重构了这一阶段,在滤波质量不变的前提下拿到 1.3–2.5× 的加速(原文口径,见 JCGT vol.15 no.1)。这篇文章的价值不只在这个倍数,更在于它把"à-trous 为什么慢"从经验判断变成了可复核的访存账——这也是本篇第一处需要给出数字的结论。

这笔账摊开是这样:每一级 pass 的读/写流量,以及优化前后在访存上的差别。

à-trous 的访存账:瓶颈在全屏 pass 的读写模式
图 7-12à-trous 的访存账:瓶颈在全屏 pass 的读写模式@ 本系列绘制

口径提醒:1.3–2.5× 是一个区间,不是一个定值——原文的加速比取决于场景与 GPU 架构(不同架构的缓存层次与带宽不同)。凡是看到"à-trous 加速 2.5 倍"这类被抹平口径的转述,都值得回去对一下原文的测试条件。

对做项目的朋友,这一节的实操含义是:调 à-trous 的级数是在拿画质换访存,而减级数的收益可能比想象中大——因为成本是每级的固定全屏开销,不是按有效半径平方涨的。反过来,如果降噪占了整个光追预算的一半以上,先查访存而不是先删 pass。


三、方差驱动:SVGF 真正的新意

3.1 从"攒样本"到"估方差"

前两章讲的是"怎么滤"。但还有一个更根本的问题没人回答:滤多狠?

OK,如果对整张图用同一套参数,结果一定是两头不讨好:平坦区域滤得不够(噪声残留),几何边缘滤得过狠(细节丢失)。要逐像素决定强度,就得知道"这个像素现在有多脏"——也就是噪声的方差。

SVGF 在时域累积时多存了一样东西:颜色的一阶矩与二阶矩。这件事在 ep06 里提过一句,这里把它展开,因为它是整条链的驱动信号。

矩(moment)的记法与更新:

m1_t = α · c_t + (1 - α) · m1_{t-1}          // 一阶矩:颜色的加权平均
m2_t = α · c_t² + (1 - α) · m2_{t-1}         // 二阶矩:颜色平方的加权平均

其中:

  • c_t 是当前帧该像素的原始颜色(1 spp 的噪声值);
  • m1_t 是累积后的颜色,也就是最终要输出的值;
  • m2_t 是颜色的平方的累积值;
  • α 是时域混合系数(ep06 已详述,SVGF 默认取向是让历史占主导,常见取值在 0.05 量级)。

有了两个矩,方差就是一步减法:

σ²_t = m2_t - m1_t²

这个式子的物理含义很直白:如果颜色一直在稳定值附近小幅波动,m2 与 m1² 会非常接近,方差趋近于零——说明这个像素已经收敛、很干净;如果颜色方差很大(刚出现的新表面、快速运动的高光),m2 会明显大于 m1²,方差就大。

这里有一个必须点明的坑:用差值法算方差在浮点下是不稳定的。当 m2 与 m1² 都是大数而方差本身很小时,两者的差会被浮点精度吃掉,甚至算出负方差。工程上的标准做法有两条:一是按通道分开算,二是给方差设一个下限(clamp 到 0 或一个极小正值)。这一点在 Adventures in Hybrid Rendering里给了可核对的写法。

3.2 采样分布:方差从哪来

方差这个量,最终还是要回到采样本身。同一条光线打出去,落在哪、打中什么,全由采样决定。下面两张图是同一件事的两种样本分布:

抖动采样的样本位置分布:成簇与空洞会抬高方差
图 7-13抖动采样的样本位置分布:成簇与空洞会抬高方差@ Vierge Marie / Wikimedia Commons,Public domain
准蒙特卡洛低差异序列的样本位置分布:更均匀、方差更低
图 7-14准蒙特卡洛低差异序列的样本位置分布:更均匀、方差更低@ Vierge Marie / Wikimedia Commons,Public domain

两张图的差别就是成簇(clumping)与空洞(gap):均匀随机的抖动采样会在某些区域挤成一团、另一些区域留白,这直接抬高了积分估计的方差;低差异序列(准蒙特卡洛,Quasi-Monte Carlo)让样本分布得更均匀,同样的样本数下方差更低。

这就解释了为什么工业光追管线几乎都用低差异序列(Halton、Sobol、蓝噪声)加子像素抖动:它不减小单样本的方差,但它让有限样本的分布更接近理想,把估计的方差压下来。ep06 里讲过的 Halton 序列抖动,正是为时域累积服务的同一件事——时域上每帧换一个抖动位置,帧间的样本合起来就覆盖了更多子像素位置。

还有一个实用推论:方差的估计质量直接决定了降噪的上限。如果采样分布本身很差(成簇严重),那么 σ² 这个信号就会失真,降噪器会把"采样太差"误判成"这里该狠滤",于是把细节一起抹掉。这也是第九章要算的账里的一项——采样与降噪不是两件事,是先后的同一笔预算。

3.3 一条链:方差同时驱动 α 与滤波强度

下面我们把它接回整条链。这是本篇最核心的一张结构图:

方差驱动的降噪主链:一二阶矩 → 时域方差 → 同时决定 α 与滤波强度
图 7-15方差驱动的降噪主链:一二阶矩 → 时域方差 → 同时决定 α 与滤波强度@ 本系列绘制

三个环节:

  1. 进:当前帧的 1 spp 颜色与上一帧的历史矩,来时域累积;
  2. 算:由两个矩得到时域方差 σ²,加上深度/法线的几何信息;
  3. 出:方差去两个地方——一处决定 α(越脏越该信当帧,α 越大、历史权重越低),一处决定空间滤波的强度(越脏越该多滤几级、权重放得越宽)。

这就是 SVGF 与它之前所有做法的分界。在此之前,"滤多狠"是人为设定的常数;在此之后,它是一个逐像素、逐帧变化的观测量。说白了,SVGF 真正的新意不是那 5 级 à-trous,而是让方差变成了整个算法的驱动信号。

代价也很清楚:多两个全屏缓冲(一阶矩、二阶矩),多一遍方差计算,多一倍的显存写入。对内存敏感的管线,这笔开销是要认真算的——到第七章讲 NRD 时我们会看到,生产级实现在这一点上做了非常不同的取舍。

这一章的公式与流程同样可以交叉核验:SVGF 原论文给了方差驱动的原始设定,GAMES202 第 14 讲给了作业级的实现骨架,SVGF 实作笔记给了可逐行对照的代码。三处的口径在"方差怎么算、算出来驱动谁"这一点上是一致的。

3.4 方差钳制:把离群值关在门外

第五章会说"方差估计会失效",但失效的机制在这里就该讲清,否则第五章的结论会显得突然。

OK,问题出在反馈环上:方差由历史矩算出来,而历史的长度又由方差决定——α 越小,历史越长,而 α 又受方差影响。这是一个闭环,闭环的系统最怕两件事:正反馈与离群值。

  • 正反馈:如果某一帧因为一次离群采样(例如一条光线恰好打中高亮光源)把方差抬得很高,α 就会被迫抬高(弃用历史),历史变短 → 下一帧的方差估计样本更少 → 估计更不稳 → 方差继续偏高。于是画面在该区域反复"变脏—重滤—再变脏"。
  • 离群值:m2 是平方的累积,对离群值极其敏感——一个比均值大 100 倍的样本对 m2 的贡献是 100² = 10000 倍。一阶矩 m1 会被它轻微拉动,但 m2 会被它彻底带偏。

工程上的标准对策是方差钳制(variance clamping):给 σ² 设一个上限,把估计值截断在合理范围内。

σ²_clamped = clamp( σ²_t , 0 , σ²_max )

σ²_max 的取值思路有两条,都在实践里用:

  • 按当帧的统计量定:用当帧目标邻域的方差(或全局分位数)作参考上限,让钳制尺度随画面自适应;
  • 按经验绝对量定:对归一化后的线性颜色,给一个固定上限,超出的部分一律按上限处理。

这里要区分两个容易混淆的"钳制":方差钳制管的是"滤波强度别失控",钳制对象是 σ²;而 NRD 里暴露给使用者的"噪声钳制"(noise clamp)管的是另一件事——把降噪结果往原始噪声图按比例拉回,避免降噪器产出"比真实信号还干净"的结果(第七章 7.4 提到的 blendFactor 就是这条)。两者的作用点完全不同,一个在估计环节,一个在输出环节。

顺手把这一章的一条实操推论写下来:如果你想验证自己的方差估计稳不稳,就去看"方差缓冲的可视化图"。健康的方差图应该只有几何边缘与运动区域亮,平坦静止区域应该是大片黑色;如果整屏都是灰的,说明钳制没做对,或者矩的更新用了错误的像素权重。


四、三根 σ 的实操:调错了会看到什么

4.1 先分清两种"边缘"

先来看看一个容易被跳过的事实:调 σ 之前必须先接受它——梯度图上的"边缘"有两种,而且降噪器必须区别对待。一种是几何与材质造成的真实边缘——物体轮廓、法线折角、纹理分界;另一种是噪声造成的假边缘——1 spp 的随机波动在梯度算子下同样会亮起来。

这张图是同一张图做过边缘/梯度检测的结果,大家可以直观看到边缘图的样貌:轮廓清晰,但纹理区域也出现了大量细碎响应——在实拍图里那是真纹理;在 1 spp 的光追输出里,那些细碎响应就有相当一部分是噪声。

边缘/梯度检测结果:真实几何边缘与噪声假边缘会同时亮起
图 7-16边缘/梯度检测结果:真实几何边缘与噪声假边缘会同时亮起@ Davidwkennedy / Wikimedia Commons,CC BY-SA 3.0

这件事和 σ 的关系在于:σl(亮度停权)本质上是在回答"多大的亮度差才算边缘"。设小了,噪声引起的假边缘也被当成真边缘保护起来,噪声就滤不掉;设大了,低对比度的真实纹理被当成噪声抹掉,画面像被磨皮过度。

4.2 σ 的数学形状:为什么它是"尺度"而不是"阈值"

三组权重都是高斯型,形式统一:

w_z(p, q) = exp( -|z(p) - z(q)|² / (2σz²) )
w_n(p, q) = max( 0, dot(n(p), n(q)) )^σn
w_l(p, q) = exp( -|l(p) - l(q)|² / (2σl²) )
正态分布概率密度曲线:σ 决定权重的衰减尺度
图 7-17正态分布概率密度曲线:σ 决定权重的衰减尺度@ Inductiveload / Wikimedia Commons,Public domain

图上的 σ 变化说明了一个常被误解的点:σ 不是硬阈值,它是衰减尺度。权重不会在某个差值上突然从 1 跳到 0,而是随着差值增大逐渐衰减。这意味着:

  • σ 稍小一点,远处的邻居权重变得很小但非零,仍会影响结果——"边缘两侧互相渗色"永远不会完全消失,只能被压到看不见;
  • σ 稍大一点,权重曲线变平,滤波更像加权平均,细节会缓慢丢失而不是突然糊掉;
  • 因此调 σ 看到的现象是"渐变"的,不是"开关"式的。这也是为什么调参必须靠"看到症状"来反推方向,而不是靠猜。

法线那一项形式不同——用 dot(n(p), n(q)) 的幂次。用点积而不是夹角差,是因为点积天然给出 [-1, 1],且 max(0, ·) 把背对背的邻居(点积为负)直接归零,正好对应"法线相反就不可能是同一表面"。幂次 σn 越大,衰减越陡:小的幂次(如 8)很宽松,大的幂次(如 128)非常严格。

4.3 调错了会看到什么:九种症状对照

下面这张图把三根 σ 的三种错法各画一行,每一格是对应的可见症状。这张表和图,是这一章最需要带走的实操内容:

三根 σ 的九种可见症状:过小 / 过大 / 失配
图 7-18三根 σ 的九种可见症状:过小 / 过大 / 失配@ 本系列绘制
旋钮 过小 过大 失配(取值与内容不匹配)
σz(深度) 斜面上的同一表面被切成两半;掠射角看地面时出现明显的同心分层 前景与背景互相渗色,物体边缘"漏"出背景颜色(典型是头发丝、栏杆的镂空处糊在一起) 场景尺度变化后没重调:近景合适的 σz 在几公里的大场景里等于"完全不判深度"
σn(法线) 曲面细分密集处出现斑块状残留(相邻三角形的法线差被误判为不同表面) 法线折角处拖出污迹,硬边物体像被水彩晕开 只对法线做了插值而没归一化,权重整体偏移
σl(亮度) 低对比度纹理(织物、木纹、缓慢明暗过渡)被抹平成塑料感 噪声被当成信号保留,画面留下一层细沙 在 HDR 线性空间与色调映射后的空间之间混用,σl 的实际含义差几个数量级

三种错法里,实战中最容易踩的是 σl 的失配:很多管线在色调映射之后的 LDR 图上看噪声做滤波,又在 HDR 线性空间里取 σl 的经验值,结果同一个数字在两种空间里的等效强度差出几个数量级——表现为"参数怎么调都不对",因为基准本身就错了。

判据给三条,方便自查:

  1. 看症状的位置:只有斜面上出问题 → σz;只在折角与硬边出问题 → σn;只在特定材质上出问题 → σl。
  2. 看症状的方向:细节被吃掉 → σ 偏大;噪声残留 → σ 偏小;斑块与条带 → 多半是空间尺度失配(σz 或 σn)。
  3. 做单旋钮实验:把另外两组 σ 调到极宽松(几乎不参与判断),只扫一根,看画面怎么变。一次只动一根,是调这三根旋钮唯一有效的办法。

这三根 σ 是工程上最常被调的旋钮,也是所有降噪教程都会提到、却很少讲清"调错什么样"的地方。社区里对这一点有第一手的抱怨——UE 用户最常讨论的两个问题就是"切了 ReBLUR/ReLAX 之后画面观感变化"和"噪声钳制该给多少",说明同一个降噪器在不同内容上需要的调参量并不小(见 NVIDIA 开发者论坛的实操帖)。

4.4 参数清单与取值带

按本系列的规矩,这一节给出可核对的参数清单,而不是停在"要调 σ"。下表是工业实现上常见的一组量级参考,不是标准答案——它的用途是让大家知道"大概从哪开始扫",以及"扫到哪个方向算离谱"。

参数 含义 常见取值带 取到带外会怎样
σz 深度停权的尺度(相对深度口径) 几十到几百(随场景尺度,按"相机到目标的距离"量级取) 只有一个绝对值、不随场景尺度换算,等于没判深度
σn 法线停权的幂次(dot(n_p, n_q)^σn) 8–128 小于 8 几乎不拦法线差异;大于 128 会把曲面细分后的相邻三角形判成两个表面
σl 亮度停权的尺度(线性 HDR 颜色) 0.05–0.5(按归一化后的线性值) 在色调映射后的 LDR 值上套这组数字,实际强度差几个数量级
α 下限 历史占比上限(越小平滑越强) 0.02–0.1 过小会拖出长尾鬼影;过大则时域累积几乎不起作用
α 上限 剧变处的上限(AQ 自适应时) 1.0(相当于弃用历史) 设成 0.5 之类的中间值,会得到"既弃不干净、又留一点错历史"的最差组合
迭代层数 à-trous 级数(或自适应上限) 3–5(上限 5) 见 2.3 节的半径推算
累积帧数 历史长度上限 8–32(ReBLUR 口径为 32) 无上限会让鬼影变成"永久性错误"
噪声钳制 输出向原始噪声图拉回的比例 按内容调,先从中等强度起扫 过强会把降噪结果拉回噪声态,等于白做

三条使用注意:

  1. 先定口径,再填数字。σz 依赖场景尺度、σl 依赖颜色空间(线性还是色调映射后)、σn 依赖法线的归一化方式。同一组数字换个口径就不是一个东西——这是横向对比时最容易出错的地方(本章开头提到的社区抱怨,一半来自这里)。
  2. 按内容而不是按显卡取值。同一台机器上,室内场景与几公里视野的开阔场景,σz 的量级能差出两个数量级。
  3. 一次只动一根。三根 σ 之间会互相掩盖症状(例如 σl 过小会掩盖 σz 过大造成的渗色),同时调等于把三个变量搅在一起,症状无法归因。

五、SVGF 的三个软肋

5.1 软肋一:鬼影残留

OK,ep06 已经把鬼影的机理讲透了——运动矢量不准导致历史颜色被错误复用。这里要看的是它在真实项目里的具体样子,因为这是判断"SVGF 够不够用"的关键证据。

Q2VKPT(Quake 2 的路径追踪版)在 GDC/GTC 2019 的材料里给了一个非常具体的观察:SVGF 的时域模糊会让光源已经关掉的画面里仍然残留光照,光滑的高光会拖出彩带(smearing)——材料里用第 412 帧的品红框标出了这个现象。他们的应对办法是估计并重建稀疏时域梯度,用它逐像素自适应 α——例如被闪烁蓝光照亮的区域在 406 帧附近 α 很大,等于主动放弃历史(见 Q2VKPT 的 GDC/GTC 2019 材料)。

这条证据的价值在于它不是实验室合成图,而是一个真实游戏里跑出来的、能指名到帧的失败案例。它同时也预告了第六章的内容:修鬼影的方向就是把 α 从常数变成逐像素量。

5.2 软肋二:过模糊

第二个软肋是本章最容易被忽视的:固定 5 级 à-trous 总是跑满。

第二章算过,5 级的最大半径是 32 像素。对平坦区域这是必要的;但对一块已经收敛、几乎没噪声的锐利区域(例如静止的镜面、干净的墙面),跑满 5 级就是在拿细节做无用功——边缘停权能保护硬边,却保护不了边缘附近的渐变与细节纹理,于是光滑表面会显得"发蜡"。

这个问题的本质是滤波强度与噪声水平没有耦合:SVGF 已经有方差这个观测量(第三章),但标准设定里 à-trous 的迭代层数是固定的——方差只用来调 α 与权重宽度,没有用来决定"跑几级"。这个缺口正是 A-SVGF 要补的。

5.3 软肋三:方差估计在高动态场景失效

好了,第三个软肋最隐蔽:方差估计本身会错。

第三章的公式 σ² = m2 - m1² 假设"历史与当帧来自同一个分布"。一旦这个假设破了,方差估计就失去了意义:

  • 1 spp 的方差有离群值:单帧一条光线的结果可能恰好打到光源上、亮出几个数量级,m2 被一个离群值拉高,于是方差被高估,降噪器误以为"这里很脏"而重滤——明明只是采样运气差;
  • 当帧与历史不一致:镜头快速转动或光照突变时,m1 与 m2 混合的是两个不同分布的颜色,算出来的方差既不是旧的也不是新的,α 与滤波强度就被同时带偏;
  • 高动态范围放大误差:亮度跨度大时,m1² 与 m2 都是大数,浮点精度损失更严重(3.1 节提过)。

三条合起来是这样:

SVGF 的三个软肋与各自的修复方向
图 7-19SVGF 的三个软肋与各自的修复方向@ 本系列绘制

到这一章为止,SVGF 的全貌就清楚了:它的框架是对的(方差驱动),但它的参数是固定的。下一个问题自然就是——能不能让参数跟着画面走?

5.4 这一章留下的问题

好了,三句话收一下这一章:

  1. 鬼影残留说明"历史信任度"不该是常数;
  2. 过模糊说明"滤波层数"不该是常数;
  3. 方差失效说明"方差本身"也该被估计得更稳。

三个"不该是常数",指向同一个方向:逐像素自适应。这就是下一章。


六、A-SVGF:把固定参数换成逐像素自适应

6.1 梯度估计器:先判断"变化有多大"

先来看看它怎么改——A-SVGF(Adaptive SVGF)针对第五章的三条软肋逐个改,而它的地基是一篇单独的论文——Schied 等人 2018 年的《Gradient Estimation for Real-Time Adaptive Temporal Filtering》,核心是估计时域梯度幅值:比较当帧亮度与历史亮度,判断画面在这里"变化得有多剧烈"。

逻辑很朴素:如果当帧与历史的亮度差很大,说明这里正在发生变化,历史已经不可信;差值小就说明画面稳定,历史可以多信一点。这个"亮度差"就是时域梯度的幅值,它比方差更容易算稳——因为它比的是一对值,而不是两个矩的差。

这张图把梯度估计器的判定链画出来。要注意它并不直接给出"要不要弃用历史"——它先给一个可比较的幅值,再由幅值去驱动后面两个决定(改 α、或钳制当帧输入)。

梯度估计器的判定链:从亮度差到 α 与钳制
图 7-20梯度估计器的判定链:从亮度差到 α 与钳制@ 本系列绘制

6.2 逐像素 α:剧变处直接弃用历史

有了梯度幅值,α 就不再是固定的 0.05,而是逐像素计算:

  • 画面稳定处(梯度小):α 取小值,历史权重高,噪声被充分累积;
  • 画面剧变处(梯度大):α 直接推向 1——等于当场丢弃历史,只用当帧结果。

这一步解决的是鬼影:Q2VKPT 里那种"光源关了还亮着"的残留,本质上就是该弃用历史的地方没有弃用。把 α 推到 1 的瞬间,画面会短暂变脏(只剩 1 spp 噪声),但换来的是不会留下错误的历史。这是一个明确的取舍——用一瞬间的噪声换掉持续的鬼影。

6.3 自适应迭代数:平均 pass 数从 5 降到 2–3

好了,第二处改动直接用上了方差:由时空估计的方差决定 à-trous 跑几级(1–5)。低噪声的像素跳过深层,脏的像素才跑满。

这一步解决的是过模糊:已经干净的锐利区域不再被反复平滑。

两项改动叠加的实测效果是:平均 pass 数从 5 降到 2–3,镜面更锐、快移相机下更稳,而 GPU 成本相当(见 降噪方案综述中对 A-SVGF 与原版 SVGF 的对照;梯度估计的原始出处见 Schied 的论文主页)。

这里要提醒口径:"平均 pass 数 2–3"是一个统计量,不是每一帧的确定值。它意味着在全屏范围内平均跑了 2–3 级;局部看,静止的平坦区域可能跑满 5 级(因为方差大),而锐利的静止镜面只跑 1 级。做性能预估时按平均值算没问题,但如果要算最坏帧时间,得按 5 级算。

两项改动放在一起对照:

SVGF 与 A-SVGF 的逐像素对照:固定参数 vs 自适应
图 7-21SVGF 与 A-SVGF 的逐像素对照:固定参数 vs 自适应@ 本系列绘制

6.4 这一章留下的问题

OK,到这里 A-SVGF 已经把 SVGF 的三个常数换成了三个观测量,方向是对的。但它仍然是"手调降噪器"这一族:参数虽然自适应用了,可设计本身还是人定的——5×5 的核、5 级的上限、三组 σ 的形式、α 的上限。这些都是人在看画面、改代码、再测试的循环里定下来的。

那么问题来了:如果把这个循环交给一个能自己从数据里学的东西,会怎么样?以及——已经有一批跑了七八年的生产级手调实现(第七章要讲的 NRD),它们的账该怎么算?

这是第八、九章的事。


七、ReBLUR / ReLAX:生产级的两条路线

7.1 NRD:一个把降噪当工程题来做的集合

到这一步,我们讲的都是"论文里的降噪器"。但项目里真正跑起来的是实现——NVIDIA Real-Time Denoisers(NRD,NVIDIA 实时降噪器)是目前公开资料里最完整的一套生产级集合,包含两个主力:ReBLUR 与 ReLAX。选它作样本,是因为它的文档把设计判断写出来了,而不只是给结果。

先把两条路线的定位摊开:

维度 ReBLUR ReLAX
核心表达 自稳定递归模糊(self-stabilizing recurrent blur) 球谐(Spherical Harmonics, SH)域表达
处理对象 漫反射与镜面分别处理 漫反射与镜面拆成两套独立信号
累积策略 恒定半径 + 一趟额外 pass 修离群值;最多累积 32 帧 按信号分组,独立累积
权重 线性权重 与 SH 系数配套的重建
内存特征 相对紧凑 工作集大(见 7.3)

7.2 ReBLUR 的四个设计判断

ReBLUR 值得单列,是因为它有四个不太一样的判断,每一个都能反过来解释我们前六章讲的机制:

判断一:恒定半径 + 一趟额外 pass 修离群值。 标准 à-trous 是"多级由小到大",ReBLUR 用恒定半径的递归模糊,再用一趟单独的 pass 处理离群值。这样做的好处是每一帧的滤波代价可预测——没有"这一帧跑了 5 级、下一帧跑了 2 级"的波动,对实时管线的预算管理更友好。

判断二:自适应半径随累积帧数变化。 累积帧数少时噪声大,半径放大;累积帧数多时画面已经稳定,半径收小。这实际上是把第六章"自适应迭代数"的思想换到了半径这个维度上,但实现代价更低——不用改 pass 数。

判断三:"时域稳定"不等于"时域累积"。 这是全篇最值得抄下来的一句话。两者的差别在于:

  • 时域累积:把历史帧加权进来,等效采样数增加,噪声下降——代价是引入滞后(lag),因为历史占比高,画面反应慢;
  • 时域稳定:不追求"多攒样本",只追求"少闪"——它更像一个设计得更好的 TAA 滤波器,不引入额外滞后,靠更宽的方差钳制把波动压住。

对项目来说这个区分很实际:如果目标是画质(静止画面要干净),选累积;如果目标是响应(快速运动不能拖),选稳定。很多团队抱怨"降噪一开手感就变粘",往往是因为在需要稳定性的场景里做了重累积。

判断四:累积速度是视差、NoV、粗糙度的函数。 这三者共同决定"这个像素的历史值不值得信":视差大(物体移动快)不信历史、NoV 小(掠射角,几何信息不可靠)不信历史、粗糙度高(本来就模糊,历史信息量低)少信历史。这套判据比单一的"亮度差"更全面,代价是要多读几个 G-buffer 通道。

ReBLUR 的设计讲解见 GTC 2020 的《Fast Denoising with Self-Stabilizing Recurrent Blurs》,落地案例分析见 NVIDIA 官方博文(Watch Dogs: Legion)。

ReBLUR 自稳定递归模糊与 ReLAX 球谐域表达两条路线
图 7-22ReBLUR 自稳定递归模糊与 ReLAX 球谐域表达两条路线@ 本系列绘制

7.3 内存账:低端卡开不开得起,由这个数决定

下面我们算本篇的第二笔硬账。社区的讨论里有一个很实在的观察:"能不能上降噪"的真实门槛是内存,不是算力。降噪器要维护工作集(working set)、常驻(persistent)与可混叠(aliasable)三类缓冲,4K 下部分配置的工作集是数百 MB 级——例如 ReLAX 的漫反射+镜面 SH 配置,工作集口径达 303.94 MB(数据源为 NRD 文档的内存表)。

这个数字的实操含义:显存紧张的卡(例如 8 GB 级)在 4K + 光追 + 高材质的情况下,降噪器很可能就是最后一根压垮的稻草。而降噪器又是不能关的——关掉画面上就是满屏噪点。于是现实里的取舍常常是降分辨率、降光追质量,或者换 ReBLUR。

这张账表把三级内存口径与两种路线的差别摆出来:

NRD 的内存三级账:工作集 / 常驻 / 可混叠
图 7-23NRD 的内存三级账:工作集 / 常驻 / 可混叠@ 本系列绘制

口径提醒:内存表随版本更新会变,且与分辨率、信号数量、是否开启可混叠强相关。凡是引用"某个降噪器占 XX MB"的说法,务必回去对口径——是工作集还是常驻?是 1080p 还是 4K?开了几个信号?这一条在横向对比时几乎每次都会踩到。

7.4 可调项:为什么"切一下"画面就变了

NRD 在运行时把这些选择暴露给了使用者:NRD 版本、ReBLUR/ReLAX 切换、噪声钳制强度等,在 Remix 的运行时面板里都能直接改(见 Remix 运行时文档 · 降噪页)。OptiX 侧的降噪器参数结构也说明了 blendFactor 的线性插值语义——它控制的是"把降噪结果与原始噪声图按什么比例混合"(见 OptiX 8 降噪器参数结构)。

这解释了一个反复出现的用户抱怨:同一台机器、同一个游戏,切了 ReBLUR/ReLAX 之后"画面观感变了"。这两条路线在处理漫反射与镜面的方式上根本不同,SH 域表达对低频光照更友好,递归模糊对高频细节更保真;再叠加噪声钳制这个"把结果往噪声图拉回多少"的旋钮,同一场景给出不同的观感是必然的,不是 bug。

对做项目的朋友,这一节的结论是:降噪器的参数不是"调一次就完"的配置项,而是随内容变化的运行参数。把 σz/σn/σl 与噪声钳制暴露给美术,让 TA 按场景微调,往往比在引擎代码里写死一套值更有效。


八、TSR 与 DLSS RR:从"调参"到"学习"

8.1 TSR:把降噪思路用在时域超分上

在讲学习型方案之前,先看一个"仍然是手调、但思路换了个对象"的例子——UE5 的时域超分(Temporal Super Resolution, TSR)。它做的是超分(把低分辨率渲到高分辨率),但用的机制和降噪是同一套。Epic 的文档把它的机制拆成几件可点名的事:

  • 历史(History)的累积与复用:和 ep06 讲的时域累积同源,区别在于它复用的是渲染分辨率的历史;
  • 视差启发法(Parallax Heuristics):用运动矢量重投影,并在解除遮挡(disocclusion)时保住画质——这正是 ep06 第三章的 disocclusion 问题;
  • 着色拒绝(Shading Rejection):检测颜色变化与半透明,避免把已经变化的历史颜色带进来,同时还要和 Nanite 的细节量做平衡;
  • 闪烁时域分析(Flickering Temporal Analysis):稳定画面,抑制摩尔纹与远景细节的抖动。

这四件事在实现里是有先后次序的,这张图把它们串成一条链——要留意的是最后那个"边界",它是 FAQ 自己承认管不到的部分:

UE5 TSR 的四段机制链:从历史复用到闪烁时域分析
图 7-24UE5 TSR 的四段机制链:从历史复用到闪烁时域分析@ 本系列绘制

机制名称见 UE5 官方中文文档(5.6),历史拒绝的两种累积策略对比见 5.2 版文档。

TSR 这一节最有价值的,其实是它的 FAQ 承认自己管不到所有闪烁:Lumen 在高强度色物体附近会失稳、Nanite 在几何边未焊接时远景会闪(见 Flickering Temporal Analysis 单篇 FAQ)。

这条"承认做不到"的证据,比任何成功案例都稀缺。它给我们的判据是:降噪/时域类方案的上限不由它自己决定,而由上游数据的质量决定——几何边没焊好、光照解算不稳,降噪器只会把这些错误忠实地传下去。

8.2 DLSS 光线重建:一个网络替掉一整排降噪器

先来看看学习型重建的代表——DLSS 光线重建(Ray Reconstruction, RR)。它的做法和前面所有方案都不同:用一个神经网络替掉手调的降噪器排布,并把降噪与超分融进同一个模型。

官方给出的关键信息有两条:它读取引擎提供的时间与空间数据(对应我们前面说的 G-buffer 与历史帧),重建采样光线之间的像素;训练数据是 DLSS 3 的 5 倍,且模型能识别不同的光追效果,据此决定如何使用时空数据、保留高频信息(见 DLSS 3.5 官方页,解读见 Kunal Ganglani 的拆解)。

"降噪 + 超分融合进同一个模型"这一点值得单独说:在手调方案里,降噪和超分是两段串联的全屏处理,中间有一次全屏写回;放进一个网络后,中间结果可以留在网络内部。这省下的不只是显存带宽,还有一段原本必然存在的量化与写回误差。

卷积网络层级结构:学习型重建的骨架
图 7-25卷积网络层级结构:学习型重建的骨架@ Renanar2 / Wikimedia Commons,CC BY-SA 4.0

8.3 2026 的时间戳:4.5 版本已覆盖全部 RTX

关于"学习型降噪值不值",2026 年发生了一件让这个问题从预测变成事实的事:DLSS 4.5 光线重建在 Gamescom 2026 发布,并覆盖全部 GeForce RTX GPU;相较上一代模型算力 +35%、参数量 +20%,而官方称其性能影响可忽略(RTX 40/50 级)(见 NVIDIA 中文官方公告、覆盖范围的第三方报道、算力与参数增幅的报道)。

为什么这条时间戳对本篇关键:它把"神经降噪"从"未来的可能性"挪到了"现在的既成事实"。讨论它值不值,必须基于"它已经在所有 RTX 卡上跑起来了"这个前提,而不是复述 2023 年的预测。

把这一章的脉络按时间线钉一下(年份口径:论文按发表在 HPG/SIGGRAPH 的年份,产品按公开可用年份):

年份 事件 提出者 关键变化
2017 SVGF:时域累积 + 方差估计 + à-trous 空间滤波 Schied et al. 降噪第一次成为"方差驱动的完整链"
2018 时域梯度估计(A-SVGF 的地基) Schied et al. 参数从常数变成逐像素自适应
2020 NRD/ReBLUR:自稳定递归模糊 NVIDIA 生产级实现,恒定半径 + ≤32 帧累积
2022 UE5 TSR:时域超分 Epic Games 降噪思路用于超分,正式承认机制边界
2023 DLSS 光线重建:单网络融合降噪 + 超分 NVIDIA 从"调参"转向"学习"
2026 DLSS 4.5 光线重建:覆盖全部 RTX NVIDIA 算力 +35%、参数 +20%,学习型方案进入普适期

九、神经降噪值不值:一笔工程账

9.1 五维对照

"哪个更好"是个伪问题——手调与会学习根本不是同一个维度上的东西:一个比的是"参数怎么定",一个比的是"参数从哪里来"。真正该算的,是它们在五个维度上各自付出的代价,而且这五个维度必须放进同一张表里看,单独拎出任何一个来比都不成立——只比内存会得出"手调更省",只比调参成本又会得出"学习型更省",两个结论都对,合起来才有用。

手调降噪器与学习型重建的五维工程账
图 7-26手调降噪器与学习型重建的五维工程账@ 本系列绘制
维度 手调降噪器(SVGF / A-SVGF / NRD) 学习型重建(DLSS RR)
内存 数百 MB 级工作集(ReLAX SH 配置 303.94 MB @4K,NRD 文档口径) 网络权重 + 推理激活,与分辨率相关;权重部分固定
延迟 常数级全屏开销,随 pass 数线性(5 级 → A-SVGF 平均 2–3 级) 推理有固定开销,与输出分辨率相关;官方称 4.5 在 RTX 40/50 上性能影响可忽略
可移植性 逐引擎、逐厂商调;同一套 σ 换项目常常要重调 一套权重跨引擎、跨项目;但依赖特定 GPU 厂商的加速单元
调参成本 高:三根 σ + 噪声钳制 + 迭代数 + 累积帧数,且随内容变 低:少数强度档;代价是无法手工兜底
失败模式 可解释、可局部修(哪个区域坏了就调哪根 σ) 整模型级失败,且训练分布外的光照/材质更明显

9.2 判据,而不是结论

OK,把这张表翻译成可操作的判据:

选手调降噪器,当:项目要跨平台(含不支持推理加速的 GPU)、需要可解释与可局部调试、要对最坏帧时间负责、或者有大量非常规内容(自定义光照模型、特殊材质)需要手工兜底。

选学习型重建,当:目标平台固定且有对应加速单元、要的是最高画质与最低 CPU 侧调参成本、内容落在主流分布内(常规 PBR 材质与光照)、且能接受"失败时只能等模型更新"这一条。

两者可以共存:主流做法是把学习型方案作为高质量档,手调降噪器作为兼容档与兜底档。这不是骑墙——第五章和第八章给出的证据都指向同一件事:降噪器的上限由上游数据质量决定(几何是否干净、光照解算是否稳定、采样分布是否合理)。上游不修,两条路线都救不回来。

9.3 这一章留下的问题

本章给了判据但没给结论,因为结论取决于项目。但有一条可以确定:不管走哪条路,"降噪"这件事的性价比天花板正在下降——前面九章讲的都是"如何把已有的采样结果修得更好看",而真正决定信噪比的是发了多少条光线、发在哪。

修图修得再好,也不如一开始采得更聪明。这是下一篇的题目。


十、边界与交棒

本篇的边界前面已经声明过,这里再收一次:不涉及离线渲染的降噪(路径追踪器内部的样本累积与自适应采样)、视频编解码的降噪、摄影后期降噪、以及图像超分(只涉及 DLSS RR 中与降噪融合的部分)。此外,ep06 已讲透的时域复用原理与失效模式,本篇只做引用,不重复展开。

10.1 本篇三处量化结论的口径汇总

全篇引用的数字不少,但真正称得上"结论级"的只有三处。把它们与口径一起钉在这里,方便大家复核,也方便避免被抹平口径的转述带偏:

结论 数字 口径 出处
à-trous 阶段针对访存优化后的加速 1.3–2.5×(区间) 滤波质量不变;区间因 GPU 架构与场景而异 JCGT vol.15 no.1(2026)
A-SVGF 自适应迭代数带来的平均 pass 数 5 → 2–3 平均值,非最坏帧;局部可跑满 5 级 A-SVGF / 梯度估计论文
NRD 中 ReLAX 漫反射+镜面 SH 的工作集 303.94 MB 工作集口径,4K 分辨率,随版本更新 NRD 文档内存表

三条口径提醒,值得单独记一下:

  1. 区间不是定值。1.3–2.5× 这种区间式表述在降噪领域很常见——不同分辨率、GPU、目标信号(阴影/反射/GI/AO)会给出不同的数。写"横向对比"前必须先声明口径,否则比较没有意义。
  2. 平均值不是最坏帧。做性能预算要按最坏帧算:如果平均 2–3 级,最坏帧仍可能跑满 5 级,帧时间预算是按后者准备的。
  3. 内存要看是三级中的哪一级。工作集、常驻、可混叠三者在不同实现里差别很大,引用任何单个数字都要问清是哪一列。

10.2 三句话收束

好了,三句话收束全篇:

  1. 空间滤波补的是时域补不上的那一块——邻居的误差与你的误差同源,只有互相平均才能抵消;
  2. 三根 σ 与方差是这套机制的两条命脉——前者决定"谁算邻居",后者决定"滤多狠",而它们都必须在正确的空间(线性 HDR、正确的尺度)里取值;
  3. 从 SVGF 到神经降噪,变的是参数怎么定,不变的是降噪的上限由上游数据质量决定。

交棒方向:降噪之后剩下的问题回到了几何与材质侧的采样预算——如何用更少的采样光线拿到同样的信噪比,也就是采样与重要性重采样(importance resampling)的题目。那正是【实时光线追踪与混合渲染】(八)《ReSTIR 与光照采样:小样本怎么骗过大方差》要回答的。

降噪的性价比上限因果链:从采样预算到画质上限
图 7-27降噪的性价比上限因果链:从采样预算到画质上限@ 本系列绘制

本篇作者:溯光 本系列(实时光线追踪与混合渲染,共 12 篇):分界线 → 加速结构工程账(上)→ 加速结构工程账(下)→ SBT → 混合管线编排 → 降噪(上)→ 降噪(下)→ ReSTIR → 光线预算表 → RT 资源生命周期 → 透明与体积 → 反射与阴影混合 → 跨平台差异 前置专题:《实时渲染管线探析》5 篇 · 《移动端图形与功耗》7 篇 · 《GPU 架构剖析》12 篇


相关链接 - 上一篇:【实时光线追踪与混合渲染】(六) 降噪(上):时域复用的原理与失效模式 - 下一篇(预告):【实时光线追踪与混合渲染】(八) ReSTIR 与光照采样:小样本怎么骗过大方差 - 参考资源:SVGF 论文 NVIDIA Research · JCGT vol.15 no.1 à-trous 优化 · NRD GitHub · UE5 TSR 文档

With Best Wishes.