站点链接:技术博客页
系列文章前言
系列定位
这是《神经渲染与神经着色器》的第二篇。上一篇立起了“分界线”——把“神经渲染”这个词拆成四类可分别判断的替换,再给三道判据。这一篇开始进入第一块真正落地的地:降噪。
降噪是整条神经渲染路线上最成熟、最少争议、也最容易讲错的一环。成熟到每个做实时路径追踪的项目都得碰它;容易讲错到——多数资料把它讲成“加个 AI 降噪器画质就上去了”,而真实情况是:这一个环里同时挤着四条并行路线,其中卖得最好的那套至今不是神经方案。
系列文章写作风格说明
术语一律写 中文(English) 双语,首次出现处给出英文原名。所有数字都标注口径:规范事实(官方文档/API 写明)、厂商宣传(厂商页自报)、论文设定、第三方实测、开发者自述。凡本次未取得的数字,正文写“官方未公布”,不估算、不用相邻数字替代。
全系列内容总览
本篇是已发布篇目之一。至此为止本系列共发布五篇,后续规划暂缓,以系列现状为准。全系列目录(仅列已发布篇目):
| 序号 | 篇目 | 主题 |
|---|---|---|
| 一 | 神经渲染的分界线 | 哪一环该换成网络 |
| 二 | 神经降噪(上) | 当先验从人手写变成学出来(本篇) |
| 三 | 神经降噪(下) | 训练数据、泛化与失效模式 |
| 四 | 神经纹理压缩 | 把纹理解码交给网络 |
| 五 | 神经外观 | 从解析 BRDF 到学习式材质 |
系列结构
本系列目前已发布到第五篇(判据、降噪上/下、纹理压缩、外观),后续篇目因需要真实硬件/训练环境而暂缓,不再续写尚不能真实实验的篇目。
相关资源链接
- Khronos 的 ML Inference 教程(本系列第六、七篇的主要规范依据):Vulkan ML Inference 教程
- Microsoft DirectX Developer Blog(本系列第一篇的核心论据来源之一):DirectX Cooperative Vector
- 上一篇:神经渲染的分界线:哪一环该换成网络
噪声不是“脏了”,是采样不足
0.1 先把“降噪”这个词拆开
大家第一次听到“降噪”,直觉往往是“图片脏了,擦干净”。这个直觉在相机摄影里基本成立,在渲染里基本不成立。渲染里的噪声不是混进来的杂质,是估计量本身的方差——它没有“正确的值被盖住”,它就是我们对某个积分量的估计还不够准。
这个区别决定了后面所有事情。如果噪声是杂质,那“降噪”就是滤掉它;如果噪声是估计量的方差,那“降噪”其实是用先验去补上那些还没算出来的信息。而“先验从哪来”,正是本篇要追问的那件事。
好了,边界划完了。把这条分界线记在心里,本篇的整个走向就清楚了:降噪的技术史,本质上就是先验的来源被换掉的历史。
0.2 方差为什么降得比想象慢
言归正传,把这件事写成式子。一帧图像的某个像素,它要估计的是从该点射出的辐射亮度——一个高维积分。用蒙特卡洛方法做,估计量是无偏的,但方差不为零。多采一条样本,方差按 $1/N$ 下降:
$$\sigma^2_N = \frac{\sigma^2_1}{N}$$
这条式子看着很友好——样本翻倍,方差减半。但标准差(也就是我们肉眼感受到的噪声“毛刺”幅度)按 $1/\sqrt{N}$ 下降:
$$\sigma_N = \frac{\sigma_1}{\sqrt{N}}$$
说白了,这就是为什么「多给四倍样本」只换来「噪声减半」的观感。差别就在这里。要让噪声幅度降到一半,样本数要翻四倍;要降到四分之一,要翻十六倍。而渲染里最贵的恰好就是样本:一条路径追踪的样本要处理弹射、材质求交、光源采样。这就是为什么“多采样一点就好了”在实际项目里从来不是答案。
把这条式子代进一个具体数字,手感会更清楚。假设 1 spp 时噪声幅度是 1,那想到达 0.25(也就是“能看”)需要 16 倍样本;想到达 0.1(也就是“接近干净”)需要 100 倍样本。从“能看”到“接近干净”这一段,要再多付 6.25 倍——而这一段恰好是画质提升最容易被观众察觉的一段。
降噪器要抢的就是这一段:它不是替你把 1 spp 变成 1024 spp,它是用先验把这段差距补掉。 把这句话记住,本篇后面所有取舍都是它的推论。
0.3 采样不足长什么样
下面进入实际画面。道理讲完,得看一眼。下面这一组是同一个场景、从 1 spp 开始逐级翻倍到 32768 spp 的排列——注意它翻了很多级才“看起来干净”,而且中段那些级数里,画面已经不太刺眼了,细看却还能看出结构性的残留。
这就是降噪器要处理的输入:它不是一张“脏图”,而是一张在很多区域已经接近正确、在少数区域偏差极大的图。高动态范围区域(强光源附近、焦散、镜面高光)偏差可以比别人大一个数量级——这类像素在行业里有个专门的名字:萤火虫(firefly)。记住这个词,后面讲反例时要用。
0.4 别再拿相机噪声类比渲染噪声
先来看看一个流传很广的类比,以及它在哪一步开始失效。网上讲降噪的文章,十篇里有八篇会拿“高 ISO 照片的噪点”作类比。这个类比能帮人入门,但用到工程决策上会带偏,因为它把两种截然不同的噪声混成了一种。
相机噪声来自传感器:光子散粒噪声(photon shot noise)、读出噪声、暗电流(dark current)。它的特点是——噪声与信号的关系由物理决定,你无法通过“多算一会儿”把它降下来。下面这张是镜头盖盖住、曝光近一小时拍出来的纯暗电流噪声:画面里没有任何场景信息,全是传感器自己产的。
渲染噪声来自估计量:它没有物理下限,只有算力下限。多给时间它就降,但降得贵。
两者的处置完全不同:相机噪声要么换硬件、要么接受它、要么用先验去“猜”(这正是 AI 图像降噪在做的事,也是它为什么可以很激进);渲染噪声的第一选择永远是“少算一点、让先验补上”,因为信息本来是可以算出来的,只是太贵。
0.5 本篇的三道判据
本篇不写“降噪技术综述”,OK,铺垫到此为止。只回答一个问题:这一环的先验该不该从人手写换成学出来,换在哪一步,算不算得过来。 三道判据依次是:
判据一 · 分类:先分清你面对的伪影是哪一类。降噪里最常被抱怨的“鬼影”其实是三种不同的病,修法互不通用。
判据二 · 前提:投递(reprojection / 运动矢量 / 引导缓冲)错了,再好的先验也救不回来——它是前置缺陷,不是可以调参绕过的。
判据三 · 代价:换与不换,取决于你在“稳定 ↔ 时延”这条帕累托前沿上站哪。而这条前沿无法由模型自动最优化——这是厂商自己写在技术文档里的。
本篇还带一条边界声明:不涉及具体网络结构(那是下一篇)、不涉及训练流程与工具链细节(这一侧面未在当前已发布篇目里展开)、不涉及纹理与几何的压缩(第四篇)、不涉及硬件代际与显存带宽的底层机制(那属于第三专题《GPU 架构剖析》)。参数取值上,本篇讨论的是每像素 0.5 至 4 条采样这个实时区间——离线渲染(几十到几千 spp)的取舍与本篇不同。
一、手写先验的四十年
1.1 四次把先验写进代码的尝试
下面开始进入这条技术史。降噪不是新问题。从 2017 到 2019 年,这个方向连续出现了四个节点,每一个都在回答“先验该怎么写”这个问题,而且每个都比前一个更不依赖人手:
| 年份 | 方法 | 先验写在哪 | 相对上一代的变化 |
|---|---|---|---|
| 2017 | SVGF(时空方差引导滤波) | 手写:方差估计 + 固定滤波带宽 | 第一次把“方差”当引导信号 |
| 2018 | A-SVGF(自适应时域滤波) | 仍是手写,但混合权重由信号自己算 | 用时域梯度幅值驱动逐像素自适应 α |
| 2017 | KPCN(核预测卷积网络) | 交给网络:网络输出滤波核的权重 | 先验被换掉的第一次,见 Disney Research 论文 |
| 2019 | BMFR(分块多阶特征回归) | 手写:多阶回归的基函数 | 用分块回归替代滤波核,更省 |
把这四个节点排成一条线,会看出一个有意思的形状:前两个还在“手写规则”,后两个里有一个已经把规则交给网络了——而这个交叉发生在 2017 到 2019 这三年之间。换句话说,“先验该不该学”这个问题,在实时降噪这个具体环节上,已经被问了七年以上。
1.2 SVGF:把方差当引导信号
先看看最经典的写法长什么样。要理解“先验被换掉”这件事的价值,得先看清手写先验长什么样。SVGF 是这一族里影响最广的一个,它的做法可以概括成三步:
第一步,估计方差。 每个像素维护颜色的一阶矩与二阶矩(moments),用它们算出该像素的方差。方差大的地方说明估计不稳,需要更强的滤波。
第二步,算权重。 滤波不是一视同仁的:周围像素与中心像素的颜色、法线、深度差得越多,权重越小。这一步就是“引导”(guided)的含义——用几何与材质信息决定“哪些邻居算数”。
第三步,时域累积。 把这一帧的结果与上一帧重投影过来的结果按一个系数 α 混合。α 越大越稳、但动起来越容易拖影。
A-SVGF 的改进就在第三步:它发现固定 α 是这类方法最大的痛点——静止时希望 α 大(多累积),运动时希望 α 小(快换血)。于是它用时域梯度幅值去逐像素决定 α,把“混合权重”这个手写常数变成了由信号自己算的量。
注意这一步的意义:先验还是在人手写,但先验的强度开始由数据决定了。这是“手写”到“学出来”之间的过渡形态,也是理解 KPCN 为什么是分水岭的关键。
1.3 SVGF 的数学形式与一段最小实现
下面我们开始把它写成数学形式。上面三步讲的是做法,把前两步写成式子会更清楚。记第 $i$ 帧像素 $p$ 的颜色为 $c_i(p)$,一阶矩与二阶矩按指数滑动平均累积:
$$M_1(p) \leftarrow \alpha\, c_i(p) + (1-\alpha)\, M_1^{\text{prev}}(p)$$
$$M_2(p) \leftarrow \alpha\, c_i(p)^2 + (1-\alpha)\, M_2^{\text{prev}}(p)$$
于是该像素的方差估计为
$$\sigma^2(p) = M_2(p) - M_1(p)^2$$
而邻域像素 $q$ 对中心像素 $p$ 的权重,是三个判据的乘积——颜色、法线、深度:
$$w(p,q) = \exp\!\Big(-\frac{\lVert c_p-c_q\rVert}{\sigma_c\sqrt{\sigma^2(p)}}\Big)\cdot \exp\!\Big(-\frac{\lVert n_p-n_q\rVert}{\sigma_n}\Big)\cdot \exp\!\Big(-\frac{|z_p-z_q|}{\sigma_z}\Big)$$
这三个 $\sigma$ 就是“手写的先验”:$\sigma_c$、$\sigma_n$、$\sigma_z$ 是三个人手设定的常数,决定了“颜色差多少算不像、法线差多少算跨面、深度差多少算断代”。注意分母里的 $\sqrt{\sigma^2(p)}$——方差大的像素(估计不稳)会自动放宽颜色判据,这就是“方差引导”这个名字的来历。
这一个观察比公式本身重要:手写先验不是“一条规则”,而是一组可调常数背后的函数形式。A-SVGF 后来把 $\alpha$ 从常数改成由时域梯度驱动的逐像素量,改的正是这类常数中的一个。
1.3.1 落到代码:手写先验到底写了些什么
下面进入代码。把上面三步落成代码,量级就清楚了。下面这段是简化到骨架的版本(去掉了时空复用,只留单帧的空间滤波),目的是看清“手写先验”具体是哪些东西:
#-----------------------------------【方差引导滤波 · 单帧骨架】-------------------------------
# 描述:给定颜色、法线、深度与逐像素方差,按颜色/法线/深度三个判据算邻域权重
#------------------------------------------------------------------------------------------
#【1】先验常数:三个 sigma 是人手设定的,它们决定滤波器的性格
#【2】方差引导:sigma_c 乘以 sqrt(variance),估计不稳的像素自动放宽颜色判据
#【3】三判据连乘:颜色、法线、深度任一不满足都会把权重压下去
#【4】归一化:权重和为 1,输出必落在邻域样本的取值范围里——这是滤波的保底
import math
import numpy as np
SIGMA_COLOR, SIGMA_NORMAL, SIGMA_DEPTH = 4.0, 128.0, 1.0 # 【1】手写先验
RADIUS = 3 # 滤波窗半径(同样是手写的)
def variance_guided_filter(color, normal, depth, variance, W, H):
out = np.zeros_like(color)
for y in range(H):
for x in range(W):
wsum, accum = 0.0, np.zeros(3)
sc = SIGMA_COLOR * math.sqrt(max(variance[y, x], 1e-6)) # 【2】
for dy in range(-RADIUS, RADIUS + 1):
for dx in range(-RADIUS, RADIUS + 1):
yy, xx = y + dy, x + dx
if not (0 <= yy < H and 0 <= xx < W):
continue
wc = math.exp(-np.linalg.norm(color[y, x] - color[yy, xx]) / sc)
wn = math.exp(-np.linalg.norm(normal[y, x] - normal[yy, xx])
/ SIGMA_NORMAL)
wz = math.exp(-abs(depth[y, x] - depth[yy, xx]) / SIGMA_DEPTH)
w = wc * wn * wz # 【3】
wsum += w
accum += w * color[yy, xx]
out[y, x] = accum / wsum # 【4】
return out
下面对这段代码特别说明三点:
第一,三个常数是全局的。 SIGMA_COLOR / SIGMA_NORMAL / SIGMA_DEPTH 对整张图生效,而画面里平坦区与高频纹理区想要的取值差得很远。这就是 §1.6 说的“带宽是全局常数”那条天花板。
第二,半径也是手写的。 RADIUS = 3 意味着只看 7×7 邻域。孔洞(hole)大一点就填不上,而扩大半径会让边缘保持失效——这是手写滤波器的经典两难:想填得宽,就必然要模糊掉不该模糊的地方。
第三,朴素实现跑不动。 7×7 就是每像素 49 次 exp,1080p 下约 1 亿次每帧。生产实现会把它拆成可分离形式,或降采样到 strata 分辨率上算(A-SVGF 走的就是后一条路)。但那是性能优化,不改变“先验是人写的常数”这个事实。
完整实现可参考 SVGF 原论文(HPG 2017),以及把它放到现代 GPU 架构上重新优化的 JCGT 论文。
1.4 传统方法谱系在真实照片上的样子
先来看看图像处理侧的同一族方法——那边的对照图更好找,也更好核对。上面讲的是渲染侧的手写先验。图像处理侧的谱系更老,而且它的效果是好核对的——下面这张五格纵向对照,从上到下依次是:原始裁切(raw crop)、BM3D(γ=10)、BM3D(γ=15)、小波 + 非局部均值(profiled,即先喂了该图的噪声配置)、以及相机机内降噪的结果。
这张图值得多看一眼的原因在最后一格:厂商机内降噪(proprietary camera denoising)也是同一条谱系里的东西。手机拍照里那套“降噪算法”,和你渲染器里写的时空滤波,本质上是同一类技术——都在用“人写好的规则 + 邻域统计”去猜缺失的信息。它们的共同点是:先验的形状是人定的,只是参数可以调。
1.5 手写先验里“最像学会的”那一个
下面我们开始看一个承上启下的方法。谱系里有一个方法值得单独说:非局部均值(non-local means, NLM)。它的做法是——对每个像素,去全图里找“和它周围那一小块长得像”的所有小块,用它们的相似度加权平均。
这个思路的有趣之处在于:判断“哪些像素该算数”这件事,它不是按距离决定的,是按内容相似度决定的。也就是说,规则仍然是手写的,但“谁和谁像”这个判断已经交给数据了。
把它和后面的学习式方法对照着看,会发现一个连续谱:手写规则 + 数据决定强度(A-SVGF)→ 手写相似度度量 + 数据决定权重(NLM)→ 网络直接决定权重(KPCN)。这三个位置在数学形式上的距离,比它们在概念上的距离小得多。
下面这张是高斯噪声下 NLM 的示例,可以看清“按相似度加权”在图上留下的痕迹——平坦区很干净,纹理区会保留结构而不是一概抹平。
1.6 手写先验的共同天花板
好了,把这一章收一收。把 1.1 到 1.5 合起来看,这一族方法有三条共同的天花板,而且都没有一条能靠调参绕过去:
第一,带宽是全局常数。 SVGF 的滤波带宽、BM3D 的块尺寸、NLM 的搜索窗半径——这些都是全局设定的。平坦区想要大带宽,边缘区想要小带宽,一个常数满足不了所有像素。
第二,假设是固定的。 每个手写滤波器都内建了“光照大概是这样、材质大概是这样”的假设。假设之外的场景(非常规光源、复杂材质组合)就会露馅,而且是静默露馅——它不会报错,只是看起来不对。
第三,要逐项目调参。 这条不是技术缺陷,是工程成本,但它最要命。厂商在原厂技术文档里自己写了这句话的含义:手调降噪器“受固定光照与材质假设限制,泛化差,每个游戏都要重新调参,耗时且难以扩展”。
这三条合起来,就是“先验该不该学”这个问题的动机。而动机之外还有代价——本篇第五章会算。
二、判据一:鬼影有三类
2.1 一个被讲成一件事的三件事
先看看这个被讲得最混乱的词。只要做时域降噪,就一定会遇到“鬼影”这个词。但把资料摊开看——论文说的鬼影、GitHub issue 里的鬼影、开发者论坛抱怨的鬼影——根本不是同一种现象。它们至少有三类,机制不同、表现不同、修法互不通用:
| 类 | 机制 | 画面上怎么认 | 修法方向 |
|---|---|---|---|
| 历史污染型 | 时域复用的权重判断错误,把不该复用的旧帧拉了进来 | 物体移走后,原位置残留一个模糊的“影子”,且形状与已移走的物体一致 | 收紧历史拒绝(往 α 小、往拒绝方向调) |
| 去遮挡型 | 新出现的表面没有有效历史,只能退回低质量的空间滤波或单帧估计 | 从遮挡后露出的一片区域整体偏糊,且糊的程度随时间收敛 | 缩短历史长度 / 显式检测去遮挡 |
| 噪声拖尾误读型 | 遮挡后重现的表面像拖尾噪声,降噪后极易被看成像鬼影的阴影 | 残留物的形状与任何物体都不像,更像一片脏污 | 这不是历史污染,收紧拒绝反而更糟 |
第三类是最反直觉的:它看起来像鬼影,但它的成因不是“复用了不该复用的历史”,而是“该用的历史还没攒够”。如果把这三类混成一类,最常见的后果就是照第一类去调参,结果第三类越来越糟。
这里有个很漂亮的反证:在 Intel OIDN(Open Image Denoise)的 issue 列表里检索 ghosting,命中 0 条——OIDN 是单帧滤波器,没有时域就没有鬼影。这反过来证明:鬼影是时域复用的代价,不是“降噪”的通用代价。 一个方法如果完全不看上一帧,它就不会有鬼影,它只会闪。
2.2 时域复用到底在赌什么
说白了,时域复用是一次下注。要理解这三类,得先看时域复用这个动作本身在赌什么。
每一帧,降噪器会做一件听起来很合理的事:把上一帧的降噪结果,按运动矢量重投影到这一帧,然后和新算出来的这一帧混合。这样静止的部分能持续累积样本,等效采样数随时间线性增长——这是它能用 1 spp 出图的根本原因。
赌注在于:上一帧的那个像素,和这一帧的这个像素,看到的是同一个表面吗? 是,就该复用;不是,复用了就是历史污染。
判断这件事需要三类输入:运动矢量(它去哪了)、深度与法线(它是不是同一个面)、上一帧的历史置信度(上次复用是否成功)。这三样只要有一样错,就会错判。而它们都会错——这是本篇第三章的主题。
下面这张是帧间差值本身的样貌(同一镜头相邻两帧的差):可以看到差值的分布极不均匀——静止背景几乎全黑,运动主体与边缘处则是一圈亮。降噪器要在这张图上判断“哪里变了”,而它拿到的判断依据本身就有误差。
2.3 运动矢量是第一现场
运动矢量(motion vector)通常长这样:一张看起来像“糊掉的色块”的图,每个像素的 RGB 表示它在下一帧会移动到哪。下面这张来自开放电影《Sintel》,前景人物与背景的运动方向相反——这正是鬼影最容易发生的位置。出处口径要说清:它是视频编码侧前向预测矢量的可视化(P 帧),不是渲染器的输出;两者算的东西不同,但看图能建立的直觉是一样的。
为什么反向运动特别容易出问题?因为重投影是单向的:它假设“我这个像素上辈子在那边”。前景人物挡住背景时,背景像素在这一帧被挡住了,它没有“可见的上辈子”;等到人物移开,背景重新露出来,它的历史要么不存在(去遮挡型),要么是别的表面的(历史污染型)。
2.4 判据一的小结:两个自问
第一问:残留物的形状,与画面里已经移走的物体一致吗? 一致 → 历史污染型,往收紧历史拒绝的方向调。不一致、更像一片脏污或拖尾噪声 → 大概率是第三类,收紧拒绝会更糟。
第二问:那片模糊区域,随时间是在收敛还是在持续变化? 收敛 → 去遮挡型,问题出在“历史没攒够”,方向是缩短历史或显式检测去遮挡。持续变化、且位置跟着运动走 → 回到第一问,历史污染。
这两问都不需要看代码,看画面就能答。它们之所以有用,是因为分类决定了修法方向——而方向错了,调参只会越调越远。
2.5 怎么在自己的项目里把这三类造出来
判据一给的是判别方法,但判别能力靠练。这三类在真实项目里不会排队等你,往往是混在一起出现的。所以要有一段可控的复现流程——下面这套办法用的是项目里本来就有的东西,不需要额外工具。
造第一类(历史污染型):让一个高亮度物体(自发光的牌子、强反光的金属)匀速横穿一个静止场景。亮度越高、移动越慢,残留越明显——因为慢速运动给了历史足够的时间去"相信"那个位置还属于这个物体。观察到残留形状与被移走物体一致,就是这一类。
造第二类(去遮挡型):让摄像机缓慢平移,使一根近处的柱子从画面边缘移出、露出后面的墙。新露出的墙面区域整体偏糊,而且随着帧数增加逐渐变清晰——这个"逐渐变清晰"就是第二类的签名。
造第三类(噪声拖尾误读型):这一类的触发条件最刁钻——需要大面积去遮挡 + 低采样率同时出现。做法是把采样率压到平时的一半,再做大幅度的摄像机横移。这时候如果看到一片"像脏污"的残留、并且收紧历史拒绝之后反而更严重,那就是第三类:它要的是更多累积,不是更少。
一个关键的检查动作:把降噪器关掉,看原始噪声。第三类和第一类在降噪后的画面上很像,但在原始噪声上完全不同——第三类的原位置是高密度噪声,第一类则是干净但错误的颜色。这一步不能省,因为它是唯一一个不依赖猜测的区分手段。
做完这三组对照,判据一就不再是三条文字,而是一组你亲手验证过的条件反射。下一篇会接着讲:当你确认了病根之后,网络那一侧到底能不能治它。
三、判据二:投递错了,再好的先验也救不回来
3.1 重投影的数据流
判据一解决了“看到鬼影怎么办”,但它有一个前提没被检查:你凭什么认为历史是对的?
把重投影这条链摊开看,它其实经过四个环节,每个环节都可以出错:
环节一,运动矢量生成。 引擎根据几何与相机运动算出每个像素在本帧到上一帧之间的位移。这里就有第一个问题:屏幕空间里“像素的位置”和“表面的位置”不是一回事。
环节二,历史采样。 用运动矢量去上一帧的对应位置取历史颜色。位移通常不是整数像素,所以要做双线性插值——这一步会把四个邻居的历史混进来。如果这四个邻居里有本该被拒绝的像素(比如被去遮挡剔除过的),插值就会把一个错误值以权重形式掺进来。
环节三,有效性判定。 用深度、法线、物体 ID 等判断“取到的历史是不是同一个表面”。判定阈值是手写的,紧了会丢掉本该复用的历史(→ 第三类噪声拖尾),松了会混进不该复用的(→ 历史污染)。
环节四,混合。 按 α 把历史与新帧混合。α 怎么定,回到判据一。
3.2 引导缓冲的字段契约
上面四个环节里,“有效性判定”依赖一组常被统称为引导缓冲(guide buffer)的输入。它们的核心问题是:每个字段都有它管不了的情况。
| 字段 | 它管什么 | 它管不了什么 |
|---|---|---|
| 运动矢量 | 像素去哪了 | 镜面高光与反射上的运动矢量不准(反射里的移动是“看到的像在动”,不是表面在动);UI 元素的运动矢量同样不准 |
| 深度 | 是不是同一个表面 | 对光栅化之后绘制的元素(UI、粒子、后处理)不可靠——深度是几何深度,不是“最终像素从哪来” |
| 法线 | 表面朝向有没有变 | 对薄几何、法线贴图后的细节、以及被降噪平滑过的输入不可靠 |
| 物体 ID | 是不是同一个物体 | 物体的形变、蒙皮动画下无法反映(同一个 ID,表面已经变了) |
这张表看着像细节,其实它决定了一件大事:投递错误不是“参数没调好”,是输入信号的语义本身有缺口。 你用一组有空缺的信号去做判定,判定就一定会在这几类像素上出错——而这几类像素(高光、反射、UI、薄几何)恰好是画面上最显眼的地方。
3.3 两个可核的真实案例
不举抽象的例子,举两个有公开记录的。
案例一,引擎官方 issue。 Unreal Engine 的 issue UE-350323:当法线与像素深度信息不一致时,Lumen 的反射会在铬材质上留下黑斑。官方确认并在 5.5/5.6 复现(UE-350323)。法线和深度都给了,但它们互相矛盾——这正是“字段各管一段、合起来有缺口”的典型。
案例二,照文档接也不对。 NRD(NVIDIA Real-Time Denoisers)的 issue #110 是一位做竞速游戏的开发者报的:他按官方文档的 NRD_FrontEnd_SpecHitDistAveraging 去处理命中距离(hit distance),结果在高速移动 + 强镜面反射的场景下出现严重鬼影、反射随相机运动滞后;改成手动平均之后明显缓解。
这个案例的价值在于它的性质:它不是“没看文档”,而是“照文档做了还是错”。竞速游戏恰好同时踩中了 §3.2 表里的两个缺口——高速运动(运动矢量跨度大)与强镜面反射(运动矢量语义不准)。
同类记录还有一条更基础的:NRD issue #90——在空像素(没有有效信号的像素)上,降噪器会输出随机颜色。这是引导缓冲缺省值处理不当的经典坑:没有信号时应该输出什么,是必须在集成层定义清楚的契约,而“没定义”的默认后果是随机。
同类记录还有一条更基础的:NRD issue #90——在空像素(没有有效信号的像素)上,降噪器会输出随机颜色。这是引导缓冲缺省值处理不当的经典坑:没有信号时应该输出什么,是必须在集成层定义清楚的契约,而「没定义」的默认后果是随机。
3.3.1 运动矢量是怎么来的
先来看看它是怎么产生的。上面反复提到「运动矢量不准」,但没说它是怎么产生的。渲染器里的运动矢量不是猜的:把每个顶点用上一帧的视图投影矩阵再变换一次,得到它上一帧的屏幕位置,与当前位置相减就是该顶点的位移,三角形内部按插值给出。所以它是解析算出来的,精确到顶点——代价是它只说明「这块几何自己动了多少」,不说明「这个像素看到的东西动了多少」。
和它并列的还有另一族做法:运动估计(motion estimation)——对屏幕上的每个小块,在上一帧里找最相似的一块,两者的位移就是候选。视频编码走的是这一族,一些基于光流的估计器也是。下面这张是其中最基础的形式,块匹配(block matching)的机制示意:搜索窗在上一帧里滑动,按某种相似度度量挑出最佳匹配块。
看懂这张图,就能理解「反射上的运动矢量不准」为什么是结构性的。块匹配假设「相邻像素属于同一表面、位移一致」,而在镜面反射里,一个像素看到的是另一个物体的像,它自己的位移和它所在表面的位移可以完全不同。解析算的那条路同样躲不过:镜面像素拿到的是镜面自己的位移,它看到的却是反射物——数字精确,方向还是错的。这不是估计精度问题,是两条路的前提在反射上都不成立。
同样的道理适用于半透明、粒子、屏幕空间 UI。这一条也能解释为什么有些项目干脆把反射单独剥出来去噪——把前提不成立的那部分从滤波输入里拿掉,剩下的部分才符合假设。
3.3.2 空像素与无信号区的契约
下面开始把它写成契约。把 §3.3 的两个案例合起来,会得到一条必须写进集成层的契约。问题是这样提出来的:画面里总有一些像素,它的输入信号在语义上是「无效」的——边缘外、天空盒缝隙、被剔除的图元、第一次出现的去遮挡区。这些像素的引导缓冲读到的往往是未初始化的内存或上一轮的残值。
契约至少要回答三个问题:
其一,无效时该读什么? 常见做法是填一个哨兵值(深度填远平面、运动矢量填零),并在有效位图(validity mask)里标出来。不填的后果就是 NRD #90 的随机颜色。
其二,无效时该输出什么? 合理的输出是「未定义但视觉上安全」的值——黑、上一帧的值、或邻域值的外推。输出随机是绝对不能接受的,因为它会被时域累积放大成雪片。
其三,有效位图要不要参与滤波? 双线性历史采样会把四个邻居混进来(见 §3.1 环节二),如果邻居里有一个无效像素而不加处理,它的错误值会以插值权重的形式掺进结果——这就是为什么有效位图必须一路传进滤波核,而不只是用来做一次早期剔除。
这三条都不难做,难的是记得做:它们不会让画质变好,只会让画质不至于在某些帧突然崩掉。所以它们最容易被跳过,也最容易在验收前一天暴露。
顺带说一件容易被忽略的事:这几类问题在单帧降噪器上不会出现。Intel OIDN 的 issue 列表里检索 ghosting 命中 0 条、也不需要引导缓冲的有效位图——代价换来的是它拿不到时域累积的收益。选型时这条要一起想清楚:你要的是「不闪」,还是「不糊」,两者在架构层面就已经分岔了。
3.3.3 这不是本文的推演:学界与厂商都承认这条边界
好了,到这里该核一下这些结论的出处——它们不是本文的推演。上面这三节是我把公开材料整理成一条检查顺序,但结论本身不是我的推演,两侧都有公开承认:
学界那一侧:SIGGRAPH 2026 一篇专门处理去遮挡下时域复用的工作(Multi-Layer Reservoir Splatting)直接指出,NRD 的去遮挡修补「会触发激进空间滤波、导致过度模糊」,并称之为严重去遮挡下时域降噪的根本限制。它同时还给了一个更细的观察:遮挡后重现的表面「像拖尾噪声」,降噪后极易被看成像鬼影的阴影——这正是判据一第三类的来源。
厂商那一侧:NVIDIA 自己的论文里承认,网络「倾向过度时域复用」(NTASAD);官方技术长文里承认运动矢量在镜面高光、反射、UI 上不准,深度对光栅化后绘制的 UI 不可靠。
把两句话放在一起看:手写先验、学习式先验、以及重投影这件事本身,在三处不同的地方各自承认了同一类边界。这不是某一派的问题,是这个问题的形状。
3.4 判据二的小结:先检查前提,再谈先验
判据二可以压成一句话:投递错误是前置缺陷,不是可以调参绕过的。
落到操作上是一条检查顺序:先确认引导缓冲在你这个场景里是可靠的,再谈先验强弱。 具体要问三个问题:
第一,你的场景里有没有 §3.2 表里那几类“字段缺口”覆盖的对象? 有强镜面反射、有屏幕空间 UI、有薄几何或大量蒙皮动画的项目,投递天然更脆。
第二,空像素与无信号区有没有定义清楚? 边缘、天空盒缝隙、被剔除的图元——这些地方降噪器应该做什么,必须在集成层写死,不能靠默认行为。
第三,官方给的引导处理建议,在你的运动速度与反射比例下还成立吗? NRD #110 的教训是:文档建议是在某个场景假设下给的,高速 + 强反射这类极端组合需要自己验证。
四、先验被换掉的第一次:核预测
4.1 换掉的到底是什么
判据一管分类、判据二管前提,到这里终于可以回答“先验在哪一步被换掉”。而答案比“换了个 AI 降噪器”精确得多。
先看手写滤波器在做什么:给定中心像素与它的邻居,按一个手写的规则算出每个邻居的权重,然后加权平均。规则长什么样由人写——颜色差多少算“不像”、深度差多少算“跨了表面”、方差大就该多滤——这些都是人定的函数形式与常数。
核预测(kernel prediction)换掉的正是这一步:网络不再是“输出这个像素的颜色”,而是输出这一组邻居的权重。也就是说,它学的是怎么把已有的样本组合起来,而不是直接给出答案。
这个区别在工程上有三个直接后果,而且都是好的:
第一,退化有保底。 滤波是加权平均,权重和为 1,输出一定落在输入样本的取值范围里。它不会凭空造出画面里没有的东西——这一点和端到端重建(网络直接出像素)完全不同。
第二,训练更稳。 要学的是“权重”,量纲统一、取值范围有限(通常经过 softmax 归一化),比直接学像素值(HDR 范围可以跨几个数量级)好训得多。
第三,可控。 权重是可见的中间量。出了问题可以看权重图——是哪几个邻居被赋了过大的权重,一目了然。端到端重建出问题时,中间层是一片不可解释的特征。
4.2 两条路线的分野
到这里出现了本篇最值得记住的一条分野:
| 路线 | 网络学什么 | 代表 | 优势 | 代价 |
|---|---|---|---|---|
| 核预测 | 学“怎么滤波”(输出权重) | KPCN 及多数学术工作 | 退化有保底、易训练、可控 | 上限受限于“滤波”这个形式 |
| 端到端重建 | 学“输出什么像素” | OptiX AI Denoiser、DLSS Ray Reconstruction | 上限更高、能重建滤波拿不到的结构 | 更难控、资源需求更高 |
两条路线都在生产里存在。怎么选,取决于你更怕“上限不够”还是“失控”——这又是一个判据型问题,而不是技术优劣问题。
有一个细节能说明端到端路线的资源量级:NVIDIA 官方口径里,OptiX AI Denoiser 的训练数据是 1000 个 3D 场景渲染出的数万张图像,跑在单台 DGX-1 上(厂商宣传口径,见 OptiX AI Denoiser 官方页)。注意这个规模意味着什么:它不是“随便找个现成数据集就行”,而是需要自建渲染管线来产数据。同一条线上还有一条更具体的官方自述:现成数据集(Vimeo-90k、Middlebury、UCF101)“对游戏内容存在经验空白,UI 元素完全缺失”,因此模型在游戏内容上表现很差,只能自建 benchmark 与训练管线。这条留到第六章讲域间隙时展开。
4.3 “学出来”这一侧的最小形态
要看懂核预测网络,不需要从 Transformer 开始。它这一侧的最小可解释形态是去噪自编码器(denoising autoencoder):
结构是“编码 → 瓶颈 → 解码”。编码器把含噪输入压到一个低维表示,瓶颈处是信息量最小的地方,解码器再把它还原成干净的输出。为什么这个结构能去噪? 因为在瓶颈处,“噪声”这种高频、随机、不携带结构信息的成分没有足够的表示容量被保留下来,而“结构”(边缘、纹理走向、物体边界)有规律、可压缩,能通过瓶颈。
把这三点对齐看:手写滤波器压缩的是“邻域统计”,自编码器压缩的是“结构表示”。 后者的容量可以随网络变大,前者不能——这是“学出来”这一侧最本质的优势,也是它的代价来源(容量大意味着要更多数据、更多算力去填)。
4.4 卷积网络在这一环的骨架
真做核预测的网络通常是卷积结构。不需要把整个 CNN 讲一遍,只需要理解它在这一环承担的一件事:用局部感受野逐层聚合空间信息,最后输出一组权重。
下面这张是卷积网络的层间结构示意。看的时候抓两个点就够:第一,每一层的通道数在变,空间尺寸在缩——这就是“把邻域信息压进表示”的过程;第二,最后一层的输出不是“图像”,而是每个像素的一组参数(在核预测里,就是那组权重)。
4.5 核预测的代价落在哪
核预测把“权重”交给网络,代价有三个落点,都需要在项目里算清:
第一,额外的输入通道。 网络不仅要看颜色,还要看方差、法线、深度、运动矢量——引导缓冲从“可选”变成“必需”。这就把第三章的判据二变成了先决条件:引导缓冲不可靠的项目,换核预测只会把不可靠放大。
第二,逐像素推理的开销。 每个像素都要跑一次网络前向。这个开销的量级与分辨率和帧率同时成正比——1080p 60fps 是每秒 1.24 亿次前向,4K 120fps 是每秒 10 亿次。下一篇会详细算这笔账。
第三,训练数据的获取成本。 这一侧的最大隐性成本不是显卡,是数据:要训一个能用的核预测网络,需要大量“含噪输入 + 干净目标”的成对数据,而干净目标通常只能靠自己渲染(因为没有人会公开发布你要的那个场景的收敛结果)。这就是 §4.2 里“1000 个场景、数万张图”那个数字背后的东西。
4.6 把逐像素推理的账算到毫秒
下面进入这几笔账里最容易算错的一笔。§4.5 的第二条代价(逐像素推理的开销)值得单独算一遍,因为它的形状决定了它能不能用:开销与分辨率、帧率同时成正比。
#-----------------------------------【逐像素推理开销估算】-----------------------------------
# 描述:给定分辨率、帧率与网络规模,估算每帧算力、权重带宽与帧预算占比
#------------------------------------------------------------------------------------------
#【1】规模与目标:参数量、每参数字节、分辨率、帧率
#【2】算力本账:FLOPs = 2 × 参数量 × 像素数(乘加各算一次浮点运算)
#【3】带宽本账:权重带宽 = 参数量 × 字节 × 像素数 × 帧率(每个像素都要把权重读一遍)
#【4】帧预算占比:与目标帧时间相比,看这笔开销吃掉多少
def inference_budget(params, bytes_per_param, w, h, fps):
pixels = w * h
flops_frame = 2 * params * pixels # 【2】
bw = params * bytes_per_param * pixels * fps # 【3】
frame_ms = 1000.0 / fps
return {
"每帧算力(GFLOP)": round(flops_frame / 1e9, 2),
"权重带宽(GB/s)": round(bw / 1e9, 1),
"帧预算(ms)": round(frame_ms, 2), # 【4】
}
for (w, h, fps) in [(1920, 1080, 60), (3840, 2160, 120)]:
print((w, h, fps), inference_budget(0.4e6, 2, w, h, fps))
下面对这三本账特别说明两点:
第一,带宽这一项常常是真正的墙。 注意权重带宽里同时含「参数量」与「像素数」——每像素都要把整份权重读一遍。40 万参数、fp16、1080p、60fps 下,权重带宽约 91 GB/s;换成 4K 120fps,同样的网络就要约 729 GB/s。这个量级已经和桌面独显的显存带宽同一档了。
这就是为什么小网络不以「层数少」取胜,而以「每像素要读的参数量少」取胜——前者省算力,后者省带宽,而这两件事在工程上的解法完全不同。
第二,算力与带宽的方向不一定一致。 核预测网络里算力的大头在卷积、带宽的大头在权重读取;而核预测与端到端重建在这两项上的比例不同——端到端重建的网络更大更深,算力占比更高。所以「哪种更快」不能只看参数量,要看你的瓶颈在算力还是在带宽。下一篇会把这个对照做全。
顺带说清一个常见误解:这段代码算的是「上界量级」,不是实测值。 它假设每个像素都要完整跑一遍网络、且权重无法驻留缓存。真实实现会靠批处理(多像素共享一次权重读取)与算子融合把带宽压下来——neural.slang 那篇 HPG 2026 的报告把这件事的困难讲得很直白:负载是「一条微型 Mat-Vec 链 × 数百万 GPU 线程 × 每帧一次」,而手写内联的 forward 与 backward 是几百行代码。把上界算出来,才知道优化空间在哪。
五、判据三:换与不换
5.1 一件容易被忽略的事:非 AI 路线还活着
好了,现在到了选型这一步。讲到这里,很容易顺势得出结论“那就换吧”。但有一条事实必须先摆出来,因为它会改变整章的结论方向:
NVIDIA 自己一边推 AI 降噪(DLSS Ray Reconstruction),一边持续维护非 AI 的 NRD,并且官方在 README 里写着它“在 AI 主导的时代仍是站得住的强力非 AI 替代方案”,还说其 SH 模式的质量“可与 DLSS-RR 相比”。
这句话的份量在于它是厂商自己说的,而且是在它的 AI 产品同一时期。所以本篇的结论不能是“该换”,只能是“换的边界在哪”。下面把四条路线的开销摆在桌上。
5.2 四条路线的推理开销
先声明口径,这一节的数字不可横向当同一基准:
| 路线 | 方案 | 推理开销 | 口径与条件 |
|---|---|---|---|
| 非 AI 工程库 | NRD ReBLUR(漫反射+镜面) | 2.55 ms(SH 模式 3.40 ms) | 规范事实:官方 README 实测,RTX 4080 @1440p 原生、默认设置 |
| 非 AI 工程库 | NRD ReLAX | 3.25 ms(SH 模式 4.80 ms) | 同上 |
| 非 AI 工程库 | NRD SIGMA(阴影) | 0.40 ms(半透明阴影 0.45 ms) | 同上 |
| 学习式(闭源) | DLSS Ray Reconstruction | 官方未公布自身推理耗时 | 官方只公布了帧生成耗时(DLSS 3 在 RTX 4090 上 4K 单帧约 3.25 ms;DLSS 4 多帧生成在 RTX 5090 上每帧约 1 ms)——那是帧生成,不是去噪,不可替代 |
| 学习式(开放) | AMD FSR Ray Regeneration | 1080p 4.26 ms / 1440p 8.10 ms(4 信号) | 厂商宣传:官方性能表,RX 9070 XT;硬件门槛为 RDNA 4 及以上 + DX12 + SM 6.6 + Windows 11 |
| 学习式(开放) | Intel OIDN | 相对指标:fast 模式再快 1.5–2×(画质略降) |
规范事实:官方版本说明;官方未公布绝对毫秒数,也未公布相对解析式降噪的加速倍数 |
三条要说清的边界:第一,NRD 的数字是同一台机器、同一分辨率、同一份官方实测,内部可比;第二,FSR RR 的数字是另一块卡(RX 9070 XT)上的官方性能表,不能与 NRD 直接比;第三,DLSS RR 没有公开的自身推理耗时,任何“RR 比 NRD 快/慢多少”的说法都是推测。本篇不给这个数。
还有两条会影响决策的实现事实:NRD 的开销里,开启反萤火虫(anti-firefly)会让 ReBLUR 增加 0–2%、ReLAX 增加 7–10%(规范事实);OIDN 的质量分三档,官方明确建议交互与实时场景用 balanced 或 fast,而不是默认的 high——最高质量模型本身不适合实时。
5.3 稳定与时延:这条前沿不能自动最优化
开销只是账的一半。另一半是稳定性,而这里有一条厂商自己写下来的限制,值得单独记:
“没有明显办法让 AI 模型偏向稳定而不引入滞后。”
这句话把降噪器的取舍说透了:时域累积越久越稳(抖动少),但物体运动时残留越久(滞后/鬼影);累积越短越跟手,但闪得厉害。这是一条帕累托前沿,不是一个可以调到最优的点。 而且——关键在这——这条前沿不能由模型自动最优化,因为“稳定”与“跟手”哪个更重要,取决于你的游戏是什么:竞速游戏要跟手,恐怖游戏要稳定,同一个模型不可能两边都赢。
具体的三个调节旋钮,可以在四条路线上找到对应:
旋钮一,历史长度。 NRD 官方口径里,白噪声下历史长度可以安全提升到 60 帧(前提是提供 History Confidence,该计算本身开销 <5% 帧时间,见 NRD README)。60 帧意味着“一个像素上辈子的信息能记住一秒”——这是稳定性的来源,也是滞后感的来源。
旋钮二,引导可靠性。 UE 的路径追踪器在官方文档里明写:降噪“跑在 CPU 上、当前并非为交互式降噪设计、不保证时域一致性,需要相对较高的每像素采样数”。Unity HDRP 则把 Temporal 与 Use AOVs 做成单独开关——时域稳定性是要额外付代价的选项,不是默认白送的。
旋钮三,质量档位。 OIDN 的 high/balanced/fast 三档就是这条前沿的三点采样:官方建议实时用后两档。
5.4 判据三的小结:三种情形
把开销与前沿合起来,判据三可以落成三种情形:
情形一:项目已有可靠的引导缓冲,且延迟预算宽(≥4 ms)。 那么学习式方案值得试——它的上限比手写滤波高,尤其在复杂材质与高频细节上。
情形二:引导缓冲不可靠,或帧预算紧(<2 ms)。 非 AI 工程库(NRD 这类)仍是更稳的选择:它的开销在同一台机器上可以低到 0.40 ms(阴影通道),且官方口径已经声明其质量可与 AI 方案相比。先修投递,再谈先验(判据二的顺序不能颠倒)。
情形三:离线或非实时场景。 那是另一个取舍空间——OIDN 这类方案的设计目标就是离线,它的质量档位建议已经说明了这一点。
5.5 把三种情形写成可判的条件
依然是惯例,把形容词换成可以判的条件。§5.4 的「宽」与「紧」是形容词,落到项目里要变成条件。下面这几条是从公开数字直接推出来的,可以直接拿去做初判——注意它们只是初判,不是结论。
条件一,延迟预算。 若单帧总预算在 16.6 ms(60fps)量级,NRD 的 2.55–3.25 ms 占掉 15%–20%;若预算在 8.3 ms(120fps),同样的开销占到 31%–39%。这就是为什么高帧率项目对降噪开销格外敏感——同一笔开销,帧预算减半,占比翻倍。
条件二,降噪信号的条数。 FSR Ray Regeneration 的官方性能表给了很直白的对照:同一块卡、同一分辨率下,去噪 4 个信号(直接与间接的漫反射与镜面)是 4.26 ms,去噪 2 个信号是 3.37 ms,只去噪间接镜面是 2.63 ms(均为 1080p,厂商宣传口径,见 GPUOpen 官方页)。信号条数与开销几乎线性——所以「我只降噪间接光」这类裁剪是很实在的省法。
条件三,硬件门槛。 AMD 这条线硬性要求 RDNA 4 及以上 + DX12 + SM 6.6 + Windows 11;NRD 的 Vulkan 路径需要 VK_NV_cooperative_vector 与 R570+ 驱动。门槛不满足时,这些方案不是在性能上吃亏,是根本跑不起来——这一条应该放在性能对比之前看。
条件四,引导缓冲的可靠性。 这条无法用数字表达,只能用判据二的检查顺序:有强镜面反射、屏幕空间 UI、薄几何或蒙皮动画的项目,先做投递验证再谈选型。
把这四条与 §5.4 的三种情形对齐,会得到一个有点反直觉的结论:「哪个方案更好」这个问题在门槛不满足时是无意义的——因为可选项从一开始就被砍掉了。这也是判据三与「跑分对比」最大的区别:先看有没有资格上桌,再谈谁跑得快。
5.6 噪声阶梯:量级感
最后给一个量级参照。下面这张是同一场景在 ISO 50 到 ISO 102400 之间的横条推进——它不是渲染噪声,但它的价值在于给出“噪声量级”的直观标尺:人眼能接受的那条线,比想象中低得多;而从“能接受”到“干净”之间的差距,往往比“能接受”到“完全不能看”还大。
这个数量关系正好解释了为什么降噪器值得投入:它要处理的不是“从极差到能看”(那部分靠硬件与采样就能解决),而是“从能看到干净”这一段——而这一段恰好是最贵的。
六、反例与边界
6.1 反例一:你的输入可能已经被降过噪了
这篇讲的是渲染管线里的降噪,但有一条反例必须先说:如果你处理的是相机素材,它可能已经被机内降噪动过手了。
下面这张是两台相机(松下 LX7 @800、GM1 @3200)用同一款后期降噪(Nik Dfine 2)处理后的对照。它要说明的不是“有没有降噪”,而是降噪把什么也一起抹掉了:细纹理(织物、毛发、树叶)与噪声在频域上高度重叠,任何降噪器在同一位置做取舍,都会连带损掉一部分真实纹理。
这条对本篇的启示是:评估降噪效果时,用“看起来干净”当指标会系统性地偏向过度降噪的方案。必须与干净参考图对比——这也是为什么学习式方案要花那么大力气去产成对数据。
6.2 反例二:域间隙不只存在于“合成 vs 真实”
多数人理解的域间隙(domain gap)是“训练用合成数据、部署在真实场景”。但在这个方向里,它有一类更隐蔽的形态:训练数据与部署内容在“有没有那种东西”上的差别。
厂商原厂自述里有一条很具体的案例:现成数据集(Vimeo-90k、Middlebury、UCF101)“对游戏内容存在经验空白,UI 元素完全缺失”——结果就是模型在游戏内容上表现很差。出处要说清:这句出自该长文的帧生成章节,讲的是视频插值模型的数据集偏差;同一份文档在降噪那一章给出了同构的证据,见下。
注意这条的形态:不是“数据不够多”,是“数据里没有那一类东西”。再怎么扩充视频数据集,只要里面没有游戏 UI,模型对 UI 的处理就不会变好。
同一份文档在降噪那一章给出了更硬的证据,而且方向与直觉相反。官方原话是 CNN 骨干“很快就碰到了极限”(they quickly reached their limits);紧接着的下一句是——为了提升泛化去扩大训练集时,反而碰上了鬼影、油画感伪影与时域不一致,原文 “As we scaled up the training dataset to improve generalization, we encountered issues like ghosting, painterly artifacts, and a lack of temporal coherence”。他们因此才另造了 Transformer 架构(DLSS 4 Technical Insights)。
这句比我上面那条推论更有分量:加数据不是免费的——加到一定程度,新失效模式会先于新能力出现。
6.3 反例三:跨域证据不能直接搬(本篇的自我声明)
本篇引用的量化证据里,有一部分来自图像降噪域而非渲染降噪域。这里明确声明,并给出正确用法:
已引用的跨域项:图像降噪研究中有结论指出,用 MSE 训练的降噪器会加重暗部问题,暗像素的重建误差可达其分区噪声地板的 6×;另有结论指出黑电平标定误差导致的颜色偏置是低光降噪性能退化的主因。
正确用法:这两条是相机 RAW 域的实验结论,说明的是“训练目标函数与标定偏差会在暗部造成系统性偏差”这个机制。渲染降噪域有类似机制(暗部样本少、萤火虫被反萤火虫逻辑压掉),但没有公开发表的等量级实验。
错误用法(本篇不做):把“6×”写成渲染降噪的结论。那不是一篇渲染论文给的数字。
6.4 反例四:把降噪器当万能药
好了,反例列了三条,还有一条不是技术问题。最后一条反例针对的不是技术,是决策习惯。
降噪器的存在会让一个诱惑出现:既然能把 1 spp 修成能看,那是不是可以再少采一点? 这个想法在账面上很合理——少采样省的是最贵的那部分。但它有一个隐藏前提:降噪器的输入还剩下足够的信息。
采样率继续往下走,先到的不是「更模糊」,而是结构性错误:萤火虫(firefly)密度上升、去遮挡区面积占比变大、高光与反射区域的有效样本数掉到个位数。这时候先验再强也补不回来——因为它要补的已经不是「缺的信息」,而是「错的信息」。
官方文档里有一条相关的边界表述可以作参照:某些路径追踪器的降噪档位「需要相对较高的每像素采样数」,且「并非为交互式降噪设计」(UE Path Tracer 官方文档)。把降噪器当成「采样率的替代品」而不是「采样率的补足」,是这条边界最常见的越界方式。
落到实践上是一条纪律:先在目标采样率下确认降噪结果的失效模式,再决定能不能继续降采样率。 而失效模式怎么认——回到判据一的三个分类。
6.5 边界声明
本篇不涉及:具体网络结构与训练流程(下一篇 ep03)|训练管线与工具链细节(未在当前已发布篇目里展开)|神经外观与辐射缓存(第五篇)|纹理与几何的压缩(第四篇)|硬件代际与显存带宽的底层机制(第三专题《GPU 架构剖析》)|许可与合规(未在当前已发布篇目里单列)。
参数范围:本篇讨论每像素 0.5 至 4 条采样的实时区间。离线渲染(几十至几千 spp)的取舍不同——在离线场景里,“多算一会儿”往往是比“换先验”更简单的选择。
证据边界:本篇不承诺鬼影的量化指标对比——因为这类指标目前不存在(本篇研究阶段多轮检索未找到任何公开的鬼影数值化评测方法)。所以判据一给的是判别方法,而不是打分表。
七、结论:一张判据表
把三章判据合起来,本篇的结论可以压成下面这张表。它的用法是:按行排查,遇到第一个“是”就停在那里修,不要跳步。
| 顺序 | 自问 | 是 → 处置 |
|---|---|---|
| 1 | 残留物的形状与已移走的物体一致吗? | 历史污染型 → 收紧历史拒绝 |
| 2 | 模糊区随时间在收敛吗? | 去遮挡型 → 缩短历史 / 显式检测去遮挡 |
| 3 | 残留物像不像任何物体(像脏污)? | 噪声拖尾误读型 → 不要收紧拒绝,要延长累积 |
| 4 | 场景里有强镜面反射 / 屏幕空间 UI / 薄几何 / 蒙皮动画吗? | 投递更脆 → 先验证引导缓冲,再谈先验 |
| 5 | 空像素与无信号区的行为定义清楚了吗? | 未定义 → 先在集成层写死(否则默认是随机颜色) |
| 6 | 延迟预算 ≥4 ms 且引导可靠? | 可以评估学习式方案 |
| 7 | 延迟预算 <2 ms 或引导不可靠? | 非 AI 工程库更稳;先修投递 |
| 8 | 你在“稳定 ↔ 跟手”上站哪一端? | 这条前沿不能自动最优化,必须按你的游戏类型定 |
这张表的顺序不是随意的:前三条是分类,中间两条是前提,最后三条才是选型。 顺序颠倒(比如先选了方案再回头修投递)是最常见的失败路径——NRD #110 那个“照文档接也不对”的案例,本质就是在第 4 行上没做验证。
7.1 走一遍:一个竞速游戏场景
下面我们开始走一遍。把这张表用一个具体场景走一遍,它的用法会更清楚。假设项目是竞速游戏、雨夜赛道、有强镜面反射、目标 120fps。
第 1 行:车位上残留的是身后的墙的形状,与已移走的车一致 → 历史污染型,收紧历史拒绝。但注意——这只是第一行,不要停在这里。
第 4 行:有强镜面反射 → 投递天然更脆 → 按判据二先做引导验证。这一步会暴露真正的病根:湿滑路面上的反射把运动矢量的语义缺口放大到了整屏。
第 5 行:检查空像素契约——雨刷掠过时的去遮挡区、挡风玻璃外的边缘,这些位置的输入是否被正确标记为无效。
第 7 行:延迟预算 8.3 ms → 降噪开销占比会很敏感 → 非 AI 工程库更稳,且可以按 §5.5 的信号条数裁剪。
第 8 行:竞速游戏要「跟手」 → 历史长度不能长(NRD 官方口径里白噪声下可安全到 60 帧,但那是在白噪声假设下;高速运动下这个数字要自己验证)→ 接受稳定性上的让步。
走完八行会发现:最初看到的「鬼影」只是症状,真正的处置点在第 4 行与第 8 行。 如果一开始就按第 1 行的结论去收紧拒绝,结果会是 §2.1 里那个最坏情形——把第三类的噪声拖尾当成第一类去治,越治越糟。
这个例子的每一步都能在公开材料里找到对应证据(NRD issue #110 的竞速场景报告、官方 README 的历史长度口径、FSR RR 的官方性能表)。但把它们连成一条处置顺序,是这份判据表本身在做的事。
7.1.1 一条判据表之外的提醒:别只看别人的结论
竞速场景这个例子还有一层:它的每一条证据都能找到,但每一条都需要在你的场景里重新验证一遍。第三方评测在这条线上留下过很具体的失效清单——第一代光线重建在部分细节上「被降噪到消失」、硬边出现「标准降噪没有的起伏波纹」(Digital Foundry 的 Star Wars Outlaws 分析);而到了 4.5 代,同一批评测又记录到鬼影明显减轻(TechSpot 的 4.5 评测)。
这两条合起来说明一件事:这类方案的画质口径随版本快速变化。所以「某某方案鬼影严重」这种结论有保质期——引用时要带版本号,而自己在目标版本上验证一次永远比引用别人的结论可靠。
八、收束与交棒
依然是惯例,收尾不复述:只留下能当判据用的那条原理。
本篇从头到尾只在回答一件事:降噪这个环里,先验该不该从人手写换成学出来。 答案是三条边界:分类先行(鬼影有三类,修法互不通用)、前提优先(投递错误不是先验能补的)、代价按前沿定(稳定与时延不能同时最优,必须按项目选一端)。
还有一条值得单独记住的:非 AI 路线还活着,而且是厂商自己说的。 任何“AI 降噪全面胜出”的叙述,都与官方 README 里那句“质量可与 DLSS-RR 相比”相矛盾。
我们正式开始收尾。下面这张收尾图把 §0.3 的那组采样序列收成一个更小的尺度——同一场景三档噪声并排。看的时候注意中间那档:它已经「能看」了,但还不是「干净」。这就是本篇反复说的那一段差距,也是降噪器真正在抢的地方。
下一篇进这一环的内部:网络结构长什么样、代价落在哪、训练数据从哪来。 也就是上篇里一直悬着的那三个问题——稽核预测网络的通道与层数怎么定、逐像素推理的开销怎么算到毫秒、以及“1000 个场景、数万张图”这笔数据成本能不能压下来。
相关链接
- 主要规范依据:NVIDIA Real-Time Denoisers 官方 README —— NRD
- 官方技术长文(手写先验三个病灶的原始出处):DLSS 4 Technical Insights
- 一线踩坑原文:NRD issue #90、#110、UE issue UE-350323
- 引擎官方文档:UE Path Tracer、Unity HDRP Path tracing
- 开放方案官方文档:Intel Open Image Denoise、AMD FSR Ray Regeneration
- 上一篇:神经渲染的分界线:哪一环该换成网络
图片出处清单
| 图 | 出处 | 许可 |
|---|---|---|
| 题图 | 《Path tracing 001》 @ Qutorial | CC BY-SA 4.0 |
| 传感器暗电流噪声 | @ Dragon 280 | Public domain |
| 非局部均值示例 | @ Kriomet | CC0 |
| 帧间差值 | © Blender Foundation(运动补偿示例) | CC BY 2.5 |
| 运动矢量可视化 | © copyright Blender Foundation(《Sintel》) | CC BY 3.0 |
| 卷积网络结构 | @ Irisbox | CC BY 4.0 |
| 噪声阶梯 | @ Trougnouf | CC BY 4.0 |
| 高 ISO 后期降噪 | @ Mustang Joe | CC0 |
| 降噪方法对照(章内引用) | @ Trougnouf | CC BY 4.0 |
With Best Wishes.