上一篇我们着重介绍了实时光线追踪的具体思路,我们提到关键点是如何用滤波去进行降噪,所以本篇我们将着重介绍实时光线追踪中所使用到的滤波的细节,包括上篇我们提到的本帧使用的空间上的滤波等等。
滤波我们在GAMES101里提到过,简单地说就是模糊操作,或者说过滤的操作。如上图通过低通滤波去实现降噪的效果对比图,可以看到还是很明显的。当然以我们用的低通滤波为例,它自然会有很多问题,首先它不仅会去掉高频的噪声,同时也会去掉高频的信号,并且低频的噪声它无法去除。这里我们同样定义有噪声的图像为 ,而滤波核为K,滤波核就是我们之前提到过的加权模板,不同格内可以有不同的权重分布,这可以定义这个滤波是在干什么,并且自然的就表示滤波过了的图像。
在蒙特卡洛积分解路径追踪中我们常用的降噪滤波核是高斯核,自然它的滤波权重分布就如同高斯分布一样,随着距离增加对中心像素的贡献减少。 伪码如上图所示,我们先定义权重和和贡献和两个变量,然后计算周围每个像素的对应权重,最用权重把贡献和计算出来,再把权重和计算出来,然后相除就得到了最终像素的颜色。
这里可以看出最后一步其实就是在归一化,这样我们并不需要把高斯核进行归一化而是最后归一化就可以了,并且保证了能量守恒,即滤波过后不会凭空变暗/变亮。其次高斯分布本身不是有限的,人们一般会把它截断在正负3σ,也就是在像素中心点半径3σ开外的像素都不去考虑它们的贡献了。
并且伪码中的输入的像素值和贡献值可以是rgb三通道的,而不是仅仅只能是一个值。另外高斯核的权重和是不为0的,但是其它的滤波和可能会有这种情况出现,这时候人们通常会做一个判断,最终贡献就会按0来处理。
我们前面说的高斯核的滤波虽然能做模糊,但是它会把图像的所有地方都模糊掉,包括边界,而边界是我们通常想保留下来的高频信息,所以我们就需要引入新的滤波核,也就是双边滤波—Bilateral Filtering。
我们提到保留边界,那么边界是什么呢?显然就是颜色的突变,而双边滤波的思路也就在这里。我们只增加了个判断,当周围的某个像素 j 和当前像素 i 颜色差别特别大的时候说明它们在边界的两边,此时就不计算 j 对 i 的贡献,而反之,则和高斯滤波一样计算贡献。
双边滤波公式如上图所示,其中( i , j )为一个点的坐标,( k , l )为另一个点的坐标,可以看到,其中左边的项就是高斯核,而右边的项则在计算两个点的颜色差异,差异越大则整个减去的值就越大,所以对最终的贡献也就越小,通过这样一个操作就实现了我们上面说的思路。
可以看到,它确实达到了我们保留边界的需求,并且模糊了其它地方。当然它的问题也很明显,虽然可以检测两个点的颜色差距过大的情况,但是我们并不能分清楚这是由于边界造成的还是由于噪声造成的,所以并不能支持我们进行降噪。
既然我们上面提到了双边滤波的问题,自然就要引入新的东西,那也就是联合双边滤波—Cross/Joint Bilateral Filtering。这里仍然有一个观察,我们说高斯核是考虑像素之间的距离做滤波,而双边滤波不仅考虑像素之间的距离,还考虑了颜色的“距离”。那我们自然就可以想到是不是有更多的标准可以来限定我们实现滤波的过程呢。
上篇我们提到了G-Buffer,而G-Buffer提供了非常丰富的信息,并且它们是免费的,包括深度,世界坐标,法线,以及我们上节课提到的object ID等。其次,更好的优点是G-Buffer中的信息是没有噪声的 。所以G-Buffer提供如此丰富的无噪声的信息可以用来指导我们的滤波,为我们的滤波限定提供更多的条件。
其它一些和前面两种滤波相同的,我们提到这些指导滤波的标准本身不需要归一化,用我们上面提到的伪码的思想,它们在滤波过程中就已经归一化了。其次我们并不一定要用高斯核,只要是满足中间到两边衰减的这种滤波核基本上都能满足我们的要求。
说了那么多,我们来看一个具体的例子来了解联合双边滤波是怎么通过G-Buffer提供的信息来指导滤波过程的。首先上图的A点和B点,我们知道它们之间是不应该有互相贡献的,但是从传统的颜色距离上来看,显然它们相差并不大,但是由于我们知道它们俩的深度信息的差异比较大,所以此时就可以不进行相互的贡献考虑。然后是B点和C点,它们的深度差不多,但是它们的法线朝向明显不同,所以也不会互相进行滤波贡献。然后是D点和E点,这两个很简单只需计算颜色的距离就知道它们之间不应该存在贡献。综上,我们三对点都判断成功,使得它们之间的边界没有被模糊掉。
我们实现一个NxN的滤波自然需要考虑范围内NxN的像素,如果N取的比较小那自然没什么问题,但是如果N取64呢?那它的速度就会非常的慢,所以我们需要怎么快速的去做一个大的滤波。这里提供2种人们常用的解决方案。
我们以高斯核为例子,如图,如果我们把NxN的高斯核拆成一个只有水平方向的和一个只有竖直方向的高斯核,然后先做水平滤波,再做竖直滤波,那我们的工作量就从N方变成了2N,这是非常客观的减小。
那么我们为什么可以把一个二维的高斯核拆成两个一维的高斯核呢?首先,数学上就是这么定义的,所以我们可以把它拆分,进而我们可以把整个二维高斯核的卷积拆分成两个单方向上的卷积,如上图所示。
然而,在理论上,我们提到的双边滤波和联合双边滤波的滤波核都极其复杂,我们并不能采用同样的方法进行拆分。(当然如果强行拆开,在滤波范围不是很大的情况下,也不会出现明显的瑕疵)
第二种解决方法是一个逐渐扩大的滤波,是一个多pass的做法,当然这里的扩大并不是简单的增加滤波的范围。如上图所示,假如第一个pass正常做滤波,也就是取5x5范围内的像素进行加权写回操作,然后是第二个pass,我们的滤波范围扩大,但是取的样本数量仍然是5x5,因为去样本的时候是隔着取的,每隔2的 i 次幂取一个样本,之后的pass以此类推,取样本的间隔不断扩大,但是滤波的样本数不变始终为5x5。这样通过计算我们可以知道,如果我们原本是想滤波64x64范围,那么这种方法可以允许我们只需要做5个pass的5x5滤波即可,工作量大大减小。
那么我们分析一下,为什么我们要先用小范围的滤波再用大范围的滤波而不是直接用大的滤波,其次为什么我们可以间隔进行采样。首先我们先回顾一下信号处理的知识,1.我们知道越大的滤波意味着我们要过滤掉越低频的信息。2.采样的本质是对频谱的重复和搬移,采样率对应了搬移的距离,采样率大意味着我们搬移的距离也大,反之则小,小的话就会发生频谱的混叠,也就是走样。
然后让我们来看上面的图,上图左图中就是我们实际增大滤波过程中频谱的变化,pass1先滤波高频部分的信息,然后pass2是更大一些的滤波,去滤波更低频的一些信息。也就是说实际上我们在逐步滤波更加低频的信息。
然后看右图,这是一个频谱搬移的示意图,其实也就是pass2过程中采样的示意图,因为pass2中涉及到扩大滤波范围后对有限个点的查询。如图所示在经过了pass1以后(假设是理想的滤波),我们把pass1内的高频信息全部抹除了,这时候进入到pass2,pass2中的采样就会把剩下的频谱进行一个搬移,而我们之前提到滤波采样点的间隔是2的 i 次方增加,那么频谱搬移的距离就正好是pass1过后剩余频谱最高频率的2倍,那也就正好无缝衔接上了,所以不会发生混叠,如上图右图所示。
这也就解释了我们为什么不能直接取做大范围的滤波,那样直接搬移频谱会导致大量的混叠。当然实际的过程中还是会出现一些错误,因为这里我们假设的是理想的滤波,它可以把高频的信息全部抹除,但是实际中由于我们会有选择的保留一些高频的信息,所以高频抹除的并不干净,图像上会出现许多小格子,当然即使这样我们也可以再用一遍小的滤波把它变得干净一点。
Outlier Removal,也就是异常值去除或者叫离群点去除,那么什么是异常值呢?如上图所示,通常人们在用蒙特卡洛积分方法渲染一幅图的时候会出现一些超亮像素,它们是没有上限的,没有所谓图像限制的255,所以就会非常亮。而这种非常亮的点用滤波很难去除,使用滤波后,即使本身会变暗一些,但由于没有上限,值过大,它的白色会被扩散到周围导致周围也变亮,这显然是我们不希望看到的。
处理这些点我们需要在滤波前就把它们处理掉,因为刚刚说了,滤波过后会导致更严重的错误现象。其次我们如何定义,判定哪些像素是Outlier。
如何判断呢?这里人们同样是对一个像素周围一圈范围内进行计算,以7x7为例那就是计算7x7个像素值的均值μ和方差σ,并且我们认为这里面的所有像素的值都应该在[μ-kσ,μ+kσ]范围内。自然其中如果有像素超过了这个范围,我们就认为这是一个Outlier。
那么怎么去除这些Outlier呢?自然就是Clamp,假设μ=0,σ=0.1,那么范围就是-0.1~0.1,此时假设有一个像素的值为10就把它Clamp到0.1,如果是-1就把它变成-0.1。这样我们就在滤波之前把Outlier去掉了。当然,这只是基础思路,实际上工业界中的Clamp操作更加复杂可能会在某个颜色空间进行,且颜色范围可能会用一个高斯来描述。
在上篇我们在提到Temporal导致的问题的时候,我们提到场景突变导致的残影。解决它的方法是调整α的值,我们提到了取舍问题,但是我们没有说具体如何调整α的值,其实这和我们上面的去除Outlier的方法相同,我们仍然选用上一帧的某个像素(对应当前帧某个像素的)周围去获得一个范围,这样如果当前帧的对应像素发生了突变,我们就检测到了并且可以把它做一个Clamp拉回到我们前面获得的范围。当然,它的本质就是调整α的值,也就会发生我们上篇提到的上一帧和当前帧的取舍问题。
GAMES202_Lecture_13 (ucsb.edu)
Lecture 13 Real-Time Ray-Tracing 2_哔哩哔哩_bilibili