光线追踪降噪技术

引言

本文翻译自 光綫追蹤降噪技術,概述光线追踪降噪方法,包含滤波、时空、采样和人工智能技术,以改进光追应用的收敛性和时间一致性。

蒙特卡洛光线追踪是一种基于随机样本的累积来近似给定场景的方法,一般来说该方法是缓慢的,但随着基于最新图形卡的实时光线追踪解决方案的出现,在去噪技术上的研究有了复苏。

这些去噪技术包括使用导向模糊内核的滤波技术;机器学习驱动的滤波器或重要性采样;通过更好的拟随机序列如蓝色噪音,以及时空累积的采样方案来改进采样;尝试用某种空间结构(如探针probes、辐照度缓存irradiance caches等)量化结果的 近似技术

根据应用程序的权衡和需要,一个鲁棒的去噪器应该考虑使用所有这些技术。

最近的研究集中在通过改进采样方案和用缓存的信息重采样像素,将降噪步骤移动到渲染早期。之前的研究集中在滤波、机器学习中的自动编码器、重要性采样和实时方法上。这些方法目前用于各种商业游戏和渲染器中。本文将讨论降噪及其实现的关键论文,重点在如何实现自己的鲁棒实时光线追踪降噪方法。

Prior Art

使用高斯 Gaussian、双边 Bilateral、多孔 A-Trous [Dammertz et al. 2010]、导向 Guided [He et al. 2012] 和中值 Median [Mara et al. 2017] 等滤波技术对蒙特卡洛光线追踪图像进行模糊处理。特别是由特征缓冲区 feature buffers(如延迟渲染中的 common G-Buffer 附件:法线,反射率,深度/位置;以及特殊的缓冲区:首次反弹数据 first-bounce data,重投影路径长度 reprojected path length,或观察位置 view position)驱动的导向滤波器,已经在最近的论文和商业实现中使用。

虽然这些滤波技术是有效的且计算量小,但代价是有损的场景表示和高频信息如锐利边缘的损失。这种信息丢失非常严重,甚至会导致在处理带有椒盐噪声的高光或阴影图案中产生亮度水平上的差异。

机器学习已经被广泛用于与降噪相关的领域,从一般的图像重建到通过使用降噪自编码器实现的实时光线追踪去噪[Khademi Kalantari et al. 2013] [Khademi Kalantari et al. 2015] [R. Alla Chaitanya et al. 2017] [Vogels et al. 2018],导向滤波技术[Wang et al. 2018] [Xu et al. 2019] [Meng et al. 2020],时空技术[Hasselgren et al. 2020],以及通过超分辨率/超采样来提高图像的分辨率,同时保持细节的方法 [Dong et al. 2015] [Ledig et al. 2016] [Xiao et al. 2020]。

Intel 和 NVIDIA 等行业领军企业支持了基于机器学习的去噪方法的研究,Intel Open Image Denoise 和 NVIDIA Optix Autoencoder 都使用自动编码器去噪,取得了巨大成功。NVIDIA 的深度学习超采样 (DLSS 2.0) 也被用于光追游戏中,如 Minecraft RTX,Control 等,通过深度学习提高分辨率来降低计算成本。

采样技术在最新的论文中再度兴起。虽然 naive 版本的蒙特卡洛光线追踪只是简单地在一个不变的场景上累加样本,但是在一个移动的场景中重用样本是可能的。包括 TAA Temporal Anti-Aliasing [Korein et al. 1983] [Yang et al. 2020],时空滤波器 Spatio-Temporal Filter [Mara et al. 2017,时空方差引导滤波器 Spatio-Temporal Variance Guided Filter (SVGF) [Schied 2017],空间去噪Spatial Denoising [Abdollah-shamshir-saz 2018],自适应 SVGF (A-SVGF) [Schied 2018], 分开多阶特征回归 Blockwise Multi-Order Feature Regression (BMFR) [Koskela et al. 2019]

这些技术依赖于高频准随机序列 quasi-random sequences,如蓝噪声(与滤波结合使用)[Benyoub 2019],以及常见的工具,如 firefly rejection[Liu et al. 2019]、下一事件估计 next event estimation(NEE) 和重要性采样 importance sampling [Veach 1998]

最近,有人尝试通过 ray hashing [Pantaleoni 2020] 来减少采样的偏差或重用邻近采样数据[Bitterli et al. 2020],将降噪转移到渲染更早期的阶段中。

此外,还有 近似技术,试图微调路径追踪器的各个方面。RTX Global Illumination 论文使用光探针 light probes 模拟全局光照,使用光线追踪来更好地确定每个探针的辐射度,并将探针定位在场景中,以避免 bleeding 或内部错误。RTXGI 最近被集成到商业游戏引擎中,如 Unreal Engine 4 和 Unity [Majercik et al. 2020]

Sampling

SVGF

时空方差导向滤波 Spatio-Temporal Variance Guided Filter SVGF [Schied 2017]是一种使用时空重投影和特征缓冲驱动的双边滤波来模糊高方差区域的去噪方法。

Minecraft RTX 使用了 SVGF,并添加了辐照度 irradiance 缓存 ,使用光线长度来更好地驱动反射,以及对透光表面 (如水) 的分割渲染。SVGF 虽然非常有效,但在游戏中产生了明显的延迟。

A-SVGF

自适应时空方差导向滤波 Adaptive spatial - temporal Variance Guided Filtering, a -SVGF [Schied 2018] 改进了 SVGF,自适应的将之前的样本根据时间特征(如方差、视角等的变化)在空间上进行重新投影,在 Moment Buffer 中编码,并通过快速双边滤波器进行滤波。因此,与基于历史长度累积样本不同,采用方差的变化来决定旧样本和新样本的比例,从而减少重影 ghosting。SVGF 只使用 moment buffer 来做模糊,而 A-SVGF 则是既进行滤波有进行累积。

虽然引入一个 moment buffer 有助于消除时间延迟,但并不能完全消除它。积累样本较多的区域和新区域之间可能存在亮度差异。这在较暗的光线追踪场景,如室内尤为明显。为了缓解这个问题,与其使用 1 spp,最好在教案的区域使用 2 spp。

Quake 2 RTX 使用 A-SVGF 作为去噪解决方案。

ReSTIR

多光源光线追踪的时空重要性重采样 Spatiotemporal Importance Resampling for Many-Light Ray Tracing (ReSTIR) [Bitterli et al. 2020] 试图在渲染时将实时去噪器的时空重投影步骤提前,重用邻近采样概率的统计信息。本质上是对早期论文的结合,讨论了重采样的重要性采样 Resampled Importance Sampling,并加入了时空去噪的思想。

ReSTIR 将用于 NVIDIA 的 RTXDI SDK中。

Machine Learning

机器学习技术,如降噪自动编码器,样本图估计器驱动的样本计数或重要采样,神经双边网格滤波,超采样,虽然这些技术比其他算法如 A-SVGF 要慢,但在图像质量方面的改善最为显著。

OIDN

一个机器学习自动编码器,输入反射率 albedo,首次反弹法线,和你的输入噪声图像,输出滤波后的图像。

// 👋 Declare Handles

// Images loaded from stb as 3 components
float* color;
float* normal;
float& output;
unsigned width;
unsigned height;

oidn::DeviceRef device = oidn::newDevice();
device.commit();
oidn::FilterRef filter = device.newFilter("RT");
filter.setImage("color", color, oidn::Format::Float3, width, height);
filter.setImage("normal", normal, oidn::Format::Float3, width, height);
filter.setImage("output", output, oidn::Format::Float3, width, height);
filter.set("hdr", true);
filter.commit();
filter.execute();

Optix

NVIDIA 的 Optix 7 Denoising Autoencoder [R. Alla Chaitanya et al. 2017] 采用与 OIDN 相同的输入和输出,速度比 Intel 的解决方案快得多,但以质量要差一些。

// 👋 Declare Handles
OptixContext ctx;
OptixDevice device;
unsigned width;
unsigned height;
CUDABuffer denoiserState;
CUDABuffer denoiserScratch;

// https://github.com/ingowald/optix7course/blob/master/example12_denoiseSeparateChannels/SampleRenderer.cpp#L764
OptixDenoiser denoiser;
OptixDenoiserOptions opt;
opt.inputKind = OPTIX_DENOISER_INPUT_RGB_ALBEDO;
opt.pixelFormat = OPTIX_PIXEL_FORMAT_FLOAT4;
optixDenoiserCreate(ctx, &opt, &denoiser);
optixDenoiserSetModel(denoiser, OPTIX_DENOISER_MODEL_KIND_HDR, nullptr, 0);
OptixDenoiserSizes denoiserReturnSizes;
optixDenoiserComputeMemoryResources(denoiser, width, height, &denoiserReturnSizes);
denoiserState.resize(denoiserReturnSizes.stateSizeInBytes);
optixDenoiserSetup(denoiser,0,
width, height,
denoiserState.d_pointer(),
denoiserState.size(),
denoiserScratch.d_pointer(),
denoiserScratch.size());

DLSS

深度学习超级采样(DLSS)是一种上采样技术,它使用一个小的颜色缓冲区和一个方向图来将输出的分辨率增至 2-4 倍。这是 NVIDIA 授权的开发者独有的,目前还没有办法公开使用,不过还有其他的选择,比如 DirectML’s SuperResolution Sample

Denoiser Design

一个理想的降噪器,结合了最新的技术论文的想法,可以是这样的:

  1. Prepass - 计算场景的 NDC 空间速度,编写 common G-buffer attachment 如反射率,法线等。您可能还需要 first bounce 资料作为基于光线追踪的前置。
  2. Ray Trace - 使用带有 sample map 的 AI Adaptive sampling [Hasselgren et al. 2020],以更好地确定哪些区域应该接收更多样本,一般是高光/阴影区域,以避免椒盐噪声,并能随着时间的推移保持亮度。将镜面反射和全局光照分别写入单独的附件的分离降噪器将是理想的,因为第一次反射的数据能更好的处理反射噪声,全局光照/环境光遮挡/阴影则可以使用更简单的时空积累。
  3. Accumulation - 尽可能多地使用时空重投影,这对于全局光照/环境光遮挡这样的 lambertian data 来说比较容易,而对于像镜面反射这样的specular data 则比较困难。为了获得更好的结果,可以使用像normals/albedo/object IDs 这样的启发式数据来将以前的样本转换到当前位置,以及第一次反弹数据,如视图方向、第一次反弹法线/反照率等。任何成功的重投影都可以用于 importance sample [Bitterli et al. 2020] 或将其radiance 编码到 radiance 历史缓冲区中 [Schied 2018]
  4. Statistical Analysis 统计分析 - 估计当前光线追踪图像的方差,计算亮度/速度的方差变化,并使用它来驱动时空重投影和滤波。并用方差信息拒绝 fireflies
  5. Filtering 滤波 - 可以用一个 À-Trous bilateral filter 快速完成,根据你想要的模糊程度,重复这一步 3-5 次,每次将stepWidth减小 2 的幂次 (所以3次迭代的序列是4,2,1)。或者,你可以使用去噪自动编码器,它慢一些,但是可以产生更好的过滤结果。然后,这个结果可以被输入一个超级采样自动编码器,这个编码器可以提升你的结果,类似于 NVIDIA 的 DLSS 2.0
  6. History Blit - 写入当前的数据,如反射率,深度等,以便 reprojection 下一帧

Prepass

在去噪之前,重要的是使用某种 General Pass (G-Pass) 对材质信息进行编码,如法线、反射率、深度/位置、对象ID、粗糙度/金属度等。此外,速度 Velocity 信息可以将以前的样本转换到当前位置。

速度缓冲区 Velocity Buffer 可以通过顶点过去和当前的 NDC 空间坐标位置的差获得。

// GLSL
// 🏃 NDC space velocity
vec3 ndc = inPosition.xyz / inPosition.w;
vec3 ndcPrev = inPositionPrev.xyz / inPositionPrev.w;
outVelocity = ndc.xy - ndcPrev.xy;

Accumulation

时空重投影 Spatiotemporal reprojection 是将以前帧中的数据空间上重新投影到当前帧中。将以前的示例转换到当前帧需要您首先在视图空间中找到前一帧数据的坐标,这可以通过速度缓冲来完成。通过比较这个屏幕空间的 当前位置/法线/对象ID等 与它之前的坐标之间的差异,可以知道一个对象是否曾经被遮挡但现在在视图中,或者重用之前的样本。

在执行时空重投影时,使用一个缓冲区来描述给定样本必须积累的时间是非常有价值的,即历史缓冲区 History Buffer。它可以用来驱动滤波器模糊在样本积累较少的区域,或者用来估计当前图像的方差(历史越长,方差越小)。

outHistoryLength = successfulReprojection ? prevHistoryLength + 1.0 : 0.0;

Statistical Analysis

const float radius = 2; //5x5 kernel
vec2 sigmaVariancePair = vec2(0.0, 0.0);
float sampCount = 0.0;
for (int y = -radius; y <= radius; ++y)
{
for (int x = -radius; x <= radius; ++x)
{
// ⬇️ Sample current point data with current uv
ivec2 p = ipos + ivec2(xx, yy);
vec4 curColor = texelFetch(tColor, p, 0);

// 💡 Determine the average brightness of this sample
// 🌎 Using International Telecommunications Union's ITU BT.601 encoding params
float samp = luminance(curColor);
float sampSquared = samp * samp;
sigmaVariancePair += vec2(samp, sampSquared);

sampCount += 1.0;
}
}
sigmaVariancePair /= sampCount;
float variance = max(0.0, sigmaVariancePair.y - sigmaVariancePair.x * sigmaVariancePair.x);

Christoph Schied 在 A-SVGF 中使用 edge avoiding guassian filter 的组合进行空间方差估计,并在一个 feedback 循环中使用它来驱动时空重投影期间的 accumulationFactor

/**
* Variance Estimation
* Copyright (c) 2018, Christoph Schied
* All rights reserved.
* 🎓 Slightly simplified for this example:
*/

// ⛏️ Setup
float weightSum = 1.0;
int radius = 3; // ⚪ 7x7 Gaussian Kernel
vec2 moment = texelFetch(tMomentPrev, ipos, 0).rg;
vec4 c = texelFetch(tColor, ipos, 0);
float histlen = texelFetch(tHistoryLength, ipos, 0).r;

for (int yy = -radius; yy <= radius; ++yy)
{
for (int xx = -radius; xx <= radius; ++xx)
{
// ☝️ We already have the center data
if (xx != 0 && yy != 0) { continue; }

// ⬇️ Sample current point data with current uv
ivec2 p = ipos + ivec2(xx, yy);
vec4 curColor = texelFetch(tColor, p, 0);
float curDepth = texelFetch(tDepth, p, 0).x;
vec3 curNormal = texelFetch(tNormal, p, 0).xyz;

// 💡 Determine the average brightness of this sample
// 🌎 Using International Telecommunications Union's ITU BT.601 encoding params
float l = luminance(curColor.rgb);

float weightDepth = abs(curDepth - depth.x) / (depth.y * length(vec2(xx, yy)) + 1.0e-2);
float weightNormal = pow(max(0, dot(curNormal, normal)), 128.0);

uint curMeshID = floatBitsToUint(texelFetch(tMeshID, p, 0).r);

float w = exp(-weightDepth) * weightNormal * (meshID == curMeshID ? 1.0 : 0.0);

if (isnan(w))
w = 0.0;

weightSum += w;

moment += vec2(l, l * l) * w;
c.rgb += curColor.rgb * w;
}
}

moment /= weightSum;
c.rgb /= weightSum;

varianceSpatial = (1.0 + 2.0 * (1.0 - histlen)) * max(0.0, moment.y - moment.x * moment.x);
outFragColor = vec4(c.rgb, (1.0 + 3.0 * (1.0 - histlen)) * max(0.0, moment.y - moment.x * moment.x));

Firefly Rejection 可以通过多种方式实现,从调整光线追踪期间的采样方式,到使用滤波技术或启发式方法来调整输出亮度。

Increase Roughness Per Bounce

//https://twitter.com/YuriyODonnell/status/1199253959086612480
//http://cg.ivd.kit.edu/publications/p2013/PSR_Kaplanyan_2013/PSR_Kaplanyan_2013.pdf
//http://jcgt.org/published/0007/04/01/paper.pdf
float oldRoughness = payload.roughness;
payload.roughness = min(1.0, payload.roughness + roughnessBias);
roughnessBias += oldRoughness * 0.75f;

Clamp Rejection

// 🗜️ Ray Tracing Gems Chapter 17
vec3 fireflyRejectionClamp(vec3 radiance, vec3 maxRadiance)
{
return min(radiance, maxRadiance);
}

Variance Rejection

// 🧯 Ray Tracing Gems Chapter 25
vec3 fireflyRejectionVariance(vec3 radiance, vec3 variance, vec3 shortMean, vec3 dev)
{
vec3 dev = sqrt(max(1.0e-5, variance));
vec3 highThreshold = 0.1 + shortMean + dev * 8.0;
vec3 overflow = max(0.0, radiance - highThreshold);
return radiance - overflow;
}

Filtering

A-Trous 避免了以轻微抖动的模式进行采样,以覆盖比通常 3x3 或 5x5 高斯核更大的范围,能够重复多次,并避免了由于不同输入的数量而在边缘产生的模糊。

可以与下列工作结合进行:

  • 根据抖动模式进行子采样,从而进一步减少了模糊内核中的样本数量
  • 用更多的信息来驱动你的模糊操作,比如表面粗糙度 [Abdollah-shamshir-saz 2018],the aproximate Specular BRDF lobe [Tokuyoshi 2015],shadow penumbras [Liu et al. 2019], etc.

Limitations

所有这些算法都依赖于重用数据,因此当不可能重用数据时,比如在快速移动的对象、高度复杂的几何图形或历史信息很少的区域,这些方法的质量都会下降。有一些方法可以利用一些缓存数据来帮助避免这种情况,比如在 Minecraft RTX 中使用 irradiance 缓存来获得更好的默认颜色。

时空重投影在处理反射时也非常困难,所以通常降噪器将依赖第一次反射的数据,其中反射表面的法线和位置数据等都是基于第一次反射的。

References