本文主要内容包括:光追硬件、RTRT应用、降噪的基本思路、Filtering、SVGF、RAE
1. Real Time Ray Tracing
1.1 硬件上的提升
RTX其本质上只是硬件的提升,并不涉及任何算法部分。
GAMES101中提到光线要经历BVH或者KD-tree这种加速结构,也就是做一个树的遍历从而快速判断光线是否与三角形求交,这部分对于GPU来说是不好做的。所以NVIDA在显卡加装了一个用来做光追的硬件,用于光线与场景的求交,来帮助我们每秒可以trace更多光线。
即使每秒能trace 100亿根光线,但在除以分辨率和帧数后,再抛去其它后处理和其它Shader的消耗后,只能做1spp:
1spp 做了以下内容:
- primary hitpoint(从camera出发打出一根光线打到的交点)
- shadow ray(primary hitpoint 和光源之间连接进行light sampling并判断是否有遮挡)
- secondary ray(在Hitpoint根据材质采样出一个方向打出一根光线,他会打到一个物体上从而得到secondary hitpoint)
- secondary shadow ray(从secondary hitpoint与光源连接判断是否会被光源看到)
蒙特卡洛路径追踪在64 spp得到的生图仍然有很多噪点,1spp完全没法看。
所以RTRT最核心的技术是: 降噪。
1.2 RTRT 的应用
一张图概况:
2. 降噪的基本思路
下图是1spp降噪之后得到的结果:
因为降噪之后不能糊成一片,也不能消耗太多时间,所以排除了一些以前的方法:
- Sheared filtering serise(SF,AAF,FSF,MAAF,...)Sheared方法不行
- Other offline filtering methods (IPP,BM3D,APR,...)离线方法不行
- Deep learning series(CNN,Autoencoder,...)深度学习不行
工业界最终采用的方法是基于temporal的降噪,核心方法:
首先假设整个看的场景的运动是连续的,就是camera以某种轨迹看向不同的物体,帧与帧之间有大量的连续性;
motion vector,它是用来告诉我们物体在帧与帧之间是如何运动的,也就是图中的A点在motion vector下可以知道在上一帧里A点对应的位置B点。
- 我们认为当前帧是需要去进行filtering,前一帧时已经filtering好的;
- 利用motion vector来知道当前帧某一点在上一帧的对应位置;
- 由于认为场景运动是连续的,所以shading也是连续的,上一帧得到降噪好的结果比如颜色之类的,可以在当前帧复用,相当于增加了spp,但并不是简单的上一帧1 spp+当前帧1 spp,由于是一个递归,因此上一帧也复用了上上一帧的结果,因此spp其实是很多的,可以理解为一个指数衰减,每一帧都有百分比贡献到下一帧;
2.1 G-Buffer
G-Buffer,全称Geometric Buffer。将屏幕采样时可以拿到的信息存到纹理中,这些纹理称为几何缓冲区
2.2 Back Projection
透过当前帧(frame i)中蓝点这个像素我们所得到的点的世界坐标,投影到上一帧(frame i-1)中对应的是哪个像素。
首先计算当前像素的世界坐标:
如果G-Buffer里有,可以采样得到;没有的话可以通过MVP和视口变换的逆矩阵得到:
当前帧世界坐标乘以帧移动的逆矩阵就得到了上一帧中这个点的世界坐标:
将世界坐标转换回屏幕坐标:
现在可以结合当前帧(noisy的图)和上一帧(没有noisy的图),最简单的方法就是线性blending在一起,比如上一帧的结果 0.8 + 这一帧的结果 * 0.2得到新的结果,公式如下:
~ : unfiltered 表示没有filter噪声挺多的内容
- : filtered 没有噪声或者噪声比较小的
2.3 Temporal Failure
时间上的复用存在的问题
2.3.1 Switching scenes
场景切换或者换光源的时候,完全没有上一帧信息,即使有也是不准确的
2.3.2 Walking backwards in a hallway
向后走,屏幕空间上下左右四个边会有屏幕之外的内容进入,这些部分是没有上一帧信息的
2.3.3 Suddenly appearing background
本来被遮挡的部分突然能看见了,同上
2.3.4 残影
移动摄像机,被储物柜挡住的墙面出现,上一帧参考比例较大,摄像机移动速度较快,这种残影比较明显。
工业界有两种解决办法:Clamping和Detection,可以单独使用也可以结合。
Clamping方法主要思想是降低颜色差异,将上一帧的颜色与当前帧的颜色差控制在一定范围内:
Detection方法主要思想是判断到底要不要采用上一帧的结果,判断前后两个次屏幕采样是否来自同一个物体,如果不是就不用了。
残影问题同样存在于阴影。
3. Filtering
滤波所做的概括起来就是做了一个模糊的操作,把一些噪声消除,也就是将一个比较noisy的图,降噪从而得到一个干净的图:
上图中使用的是低通滤波,用于光追降噪来说有两个问题:
- 高频信息中可能包含有用的信息,抹掉会导致信息丢失;
- 低频信息中也可能包含噪声;
3.1 高斯滤波
一般来说我们为了给光线追踪由于蒙特卡洛产生的噪声降噪时,用的是高斯的滤波器,它类似于正态分布,中心值高,向两边衰减:
伪代码:
For each pixel i
sum_of_weights = sum_of_weighted_values = 0.0
For each pixel j around i
Calculate the weight w_ij =G(|i - j|, sigma)
sum_of_weighted_values += w_ij *C^{input}[j]
sum_of_weights += w_ij
C^{output}[I] = sum_of_weighted_values / sum_of_weights
3.2 双边滤波
高斯滤波可以得到了一个整体都被模糊的结果,但是我们想让边界仍然锐利,因此除了高斯我们需要其他的方法来帮助我们保留下边界的这些高频信息,因此引入了双边滤波(Bilateral filtering)
双边滤波认为颜色变化特别剧烈的地方认为是边界,如果二者差距不是特别大我们继续用高斯处理j到i的贡献。如果像素i和像素j之间的颜色值差距过大,我们认为这两个像素分别在边界的两边,从而让像素j给i的贡献变少,具体做法是在高斯的滤波核上加一些东西:
在这里i和j代表一个像素,k和l代表另一个像素;I(i,j)表示第一个像素的值,I(k,l)表示第二个像素的值;分母是距离的平方。等式的第二部分的意义在于,如果两像素差异过大,则减少像素(k, l)对(i, j)的贡献。
上图右边是经过双边滤波后结果,可以发现,既保留了边缘的高频信息,有抹平了山峰、湖泊、树木的低频信息。
3.3 联合双边滤波
Joint Bilateral filtering,可以结合出了颜色之外的更多信息进行滤波,比如法线方向、场景深度、Shadow Map等。
联合双边滤波使用的滤波核不局限于Gaussian,可以使用指数或者Cos等,每个人都可以有不同的操作:
下图中A和B可以通过深度判断不接近、B和C可以根据法线、D和E根据Shadow Map,以此类推:
3.4 滤波效率优化
滤波核越大、消耗越大;一个64*64的滤波核在不优化的情况下每个像素都需要采样4096次。工业界流行两种解决办法
3.4.1 Separate Passes
拆分实现,将滤波拆到两个Pass,水平方向,垂直方向各一次;这样将采样数从\(N^2\)降到了\(N+N\):
高斯函数在数学上就是拆开定义的:
但是理论上这种做法只适用于高斯,双边滤波或者联合双边滤波\(x\)和\(y\)不容易拆出来;实际上只要范围不是太大,可以硬拆。
3.4.2 Progressively Growing Sizes
大体方法是先用一个逐步增大的filter,比如先用一个小的filter,然后用中号的filter,最后是大号的filter,通过多趟的filter得到N*N的filter得到的结果:
也是多Pass操作,假设每趟都是5*5的filter,第一趟步长\(2^0\)、第二趟间隔\(2^1\)、第三趟间隔\(2^2\)、第四趟间隔\(2^3\)、第五趟间隔\(2^4\)。第五次的时候因为filter size是5,所以相当于最终得到了filter size 64的结果。
逐步增大filter而不是直接用一个pass做间隔16的样本式为了保留低高频信息(用更大的filter == 除掉低频信息);
采样 = 重复的搬移频谱;采样间隔越大频谱间隔越小,但是因为Pass1去除了\(5*5\)范围内的高频信息,Pass2的采样正好覆盖了了Pass1频谱的收尾,所以避免了加大步长的走样问题。
3.5 Outlier Removal
用蒙特卡洛方法渲染一张图时,得到的结果会出现一些点过亮或者过暗,这些过亮或者过暗的点如果经过使用filter这种降噪的方法去处理的话,会导致画面有亮斑或暗斑,所以必须在filter之前处理掉。
3.5.1 Outlier Detection and Clamping
Detection:首先通过给对每个像素周围的一定区域采样,计算方差,如果方差超过一定范围就认为它需要进行处理;
Clamping(outlier removal):对筛选出来的像素进行Clamp
3.5.2 Temporal Clamping
公式如下:
对于这个公式的解释在上文2.3.4 残影部分,这种方法并不是解决Outlier的办法,是一个对noisy和残影的tradeoff。
4. RTRT采用的滤波方法
4.1 Spatiotemporal Variance-Guided Filtering
SVGF方法与时空上降噪的方法差不多,加了一些方差分析和tricks,也是一种联合双边滤波:SVGF-Joint Bilateral Filtering。
4.1.1 Depth
由于exp(x)是返回e的x次方,由于公式返回的是-x次方,所以差异越大,贡献越小;
分母中的\(|\nabla z(p) \cdot(p-q)|\) 代表 深度的梯度 * 两点间的距离;
\(\epsilon\) 的作用是因为我们filter时候考虑一个点Q周围所有的点P,也包括这个点Q,因此它是有可能为0的,避免分母为0的情况,而且如果两点足够接近会导致最后的数值太大,为了避免一些数值上的问题加上一个\(\epsilon\);
\(\sigma_{z}\) 是一个用来控制指数衰减的快慢的参数,或者理解为控制深度的影响大还是小。
理论上A和B互相会有很多贡献,但是如果单纯看深度,可能会导致贡献偏差变大。
4.1.2 Normal
\(\sigma_{n}\) 是一个用来控制指数衰减的快慢的参数,或者理解为控制法线的影响大还是小;
这里采样的法线并不是应用了凹凸纹理的效果,因为应用之后同一个面的法线方向都有较大差异。
4.1.3 Luminance
在考虑颜色差异时,最简单就是应用双边滤波里给的颜色差异来考虑,比如我们将RGB转换为grayscale(灰度),这种颜色我们称其为luminance,只是一个名字,我们就认为其是颜色。
比对任意两点间的颜色差异,如果颜色差异过大,则认为靠近与边界,此时A和B的贡献不应该互相混合起来,A不应该贡献到B,B也不应该贡献到A。
分母中的\(\operatorname{Var}\left(l_{i}(p)\right)\) 表示计算点P周围一定范围内的方差,比如7x7;同时可以利用temporal得到上一帧的variance 进行一个差值;
\(\sqrt{g_{3 \times 3}\left(\operatorname{Var}\left(l_{i}(p)\right)\right)}\) 则表示对周围3x3区域内的方差做一次空间上的滤波得到最终的variance;
总结来说就是spatial filter → temporal filter → spatial filter 从而得到P点精准的variance
最终SVGF得到的结果不错,但它无法解决temporal导致的残影问题。
4.2 Recurrent AutoEncoder
RAE是指Recurrent AutoEncoder,用RAE这么一种结构对Monte carlo路径追踪得到的结果进行reconstruction,也就是对RTRT做一个filter从而得到一个clean的结果。
这种方法基于神经网络,输入noisy的图和G-buffer,自动利用temporal的结果。
下面式一张两种滤波手段的对比图:
引用
GAMES 202
图片来自GAMES 202 PPT
https://zhuanlan.zhihu.com/p/387619811
https://zhuanlan.zhihu.com/p/389335568
https://zhuanlan.zhihu.com/p/397575842