一、前言
这篇我们开始新的话题—Real-Time Ray Tracing简称RTRT,也就是实时光线追踪,关于光线追踪,我们已经不止一次提到过它的优点,无论是软阴影还是全局光照,光线追踪都很容易做,唯一的缺点就是速度太慢了,所以如何把光线追踪做到实时人们进行了大量的研究。
二、Basic idea
1.sample per pixel
提到RTX显卡大家肯定都不陌生,那么RTX干了什么呢,它其实就是增加了一种硬件架构(RT core)能够让我们能够去进行光线追踪而已。 也就是说它属于人们在硬件上的突破。它每秒能追踪10G根光线,看起来虽然很多,但是实际上还要除以分辨率和帧数,并且在1s内还要留时间进行后处理,所以实际上并不多。最后的结果相当于每秒一个像素只用了一个样本的采样。另外我们还要提到一个概念,sample per pixel,以下简称spp,它代表一个光路的样本。
首先最基础的光路spp就需要四条光线构成,如上图所示,首先是primary ray和它的shadow ray,这构成了直接光照,其次就是secondary ray和它的shadow ray,这构成了一次反射的间接光照。当然后续还会有其它更多次反射的间接光照,但以上提到的4条光路是最基本的构成全局光照的光路,我们把这4个光路当成1个spp的样本。
这里可以看到primary ray的前面写了个rasterization,很容易想到,因为本来primary ray实现的效果和光栅化就是相同的,并且光栅化速度更快,这样我们1个spp就可以只用3条光线了。
2.Denoising
那么我们上面提到了1spp,现代的硬件已经允许我们去做1spp的路径追踪了,但是我们之前介绍过路径追踪,如果spp只有1的话,那么得出的结果是有严重的噪声的。所以RTRT的关键技术其实就是降噪。
如上图为spp=1的噪声极为严重的图和降噪后的对比图。
所以综上,我们的目标可以概况为去降噪一个1spp实现的有噪声的画面,不能过于模糊丢失细节,也不能出现渲染的错误,同时要保证降噪时间小于2ms。显然上图提到的已有方法都不可能实现这个目标。
三、Temporal accumulation / filtering
1.Motion vector
而事实上,RTRT降噪的关键就是Temporal。首先我们是假设画面变化是连续的而没有突变,帧和帧之间有一定的连续性,其次我们认为当前帧的前一帧是已经被滤波好了的,也就是一个递归的思想,这是Temporal filtering的一个基本思路。
并且我们引入一个新概念Motion vector,它的作用就是去对应上一帧和当前帧任意一个点的对应位置,也就是它可以告诉我们物体在画面上是怎么运动的。简单地说就是可以知道世界空间下相同物体在不同帧的时候在屏幕上的不同位置。这样也就有一个好处,我们上面说过我们假设画面是连续的,那么Shading一定程度自然也是连续的,也就是说上一帧已经滤波好了的画面可以拿到当前帧复用,并且由于我们的Motion vector已经帮我们对应了同一个物体的在两帧上的不同位置,所以我们很容易知道当前帧的某个像素的颜色应该参考上一帧的哪一个像素。而这也就是时间上的复用的思路,它间接的增加了spp,并且不是简单的从1增加到2,因为我们前面提到它是一个递归,用来降噪的上一帧一定程度上来源于上上帧,所以每一帧对下面所有帧的贡献是一个指数衰减。
2.G-Buffer
这里我们再引入一个叫做G-Buffer的概念,中文就是几何缓冲区,熟悉延迟渲染的朋友对它一定不会陌生。简单地说其实就是在渲染一幅图的过程中,得到的屏幕空间上储存的一系列信息,如深度图,法线图,世界坐标,基础色(kd)等等。也就是我们渲染的过程中可以免费得到的一些信息,也就是生成储存它们不需要太大的开销。当然,由于它是屏幕空间上的信息自然也会有一些问题。
3.Back Projection
我们前面说的过程中最关键的操作就是找对应,也就是第 i 帧和第 i-1 帧相同世界坐标物体对应的不同像素。也就是当前帧的像素它在上一帧的哪呢?
这里我们设当前帧的屏幕坐标为x,世界坐标为s,那么如果我们有G-Buffer的话我们是可以直接得到x的世界坐标的,当然,没有G-Buffer也没关系,我们知道顶点是通过MVP+视口变换到屏幕坐标的,那么我们知道屏幕坐标x,只需要逆变换就可以了(这里上图写了MVPE四个逆变换,个人觉得大概是闫令琪老师写错了,如果只是回到世界坐标是不需要乘以M矩阵的逆的),同时如果顶点在世界空间移动也没有关系,我们是知道它的变换的,假设它的变换是T矩阵,那么上一帧的世界空间位置s'自然就是s去乘以T的逆,当然如果没有移动那么s=s'。我们如果知道了上一帧的世界空间的位置s',自然就可以通过MVP+视口变换得到上一帧的屏幕空间位置x'(同样的上图最后一行应该是E'P'V',闫令琪老师少写了个E'多写了个M'),这样对应的像素就找到了。
4.Temporal Accum./Denoising
那么找到了对应,如何把两帧结合呢?最简单的操作,把两帧做一个线性的blending。我们设~为没有滤波的有噪声的内容,- 代表滤波后降噪后的内容。如上图所示代表我们先对当前帧做一个空间上的滤波,当然我们并不能只依赖于这个Filter,因为我们提过1spp的图像的噪声无论怎么做这个滤波都是无法恢复的。接下来我们用上一帧已经滤波好了的和当前简单滤波过的帧做一个线性blending— ,通常α被取0.1~0.2,也就是上一帧占的贡献通常在80%~90%。
可以看到1spp再降噪和光线追踪了足够长时间的效果已经非常非常接近了,唯一的瑕疵可能就是在一些AO的地方,因为降噪滤波会导致AO缺失,但是总体来看,效果已经非常不错了。
四、Failure cases
1.switching scenes
说了这么多时间上的复用,我们接着说它会出现问题的情况,首先第一种情况,画面的突变,比如,镜头的切换,场景光照的突然改变,画面的切换,这种两帧之间没有连续性的情况自然会导致时间上的复用出现问题,通常这种情况下要经过一个burn-in period才会恢复正常,也就是一个预热的时间,我们之前时间上累加的复用效果才会逐渐消失。
2.walking backwards in a hallway
第二种情况是倒退着走,如上图的走廊的例子,在以图中视角倒退的时候我们会发现,屏幕上会不断出现上一帧没有的新的信息,而由于是新的信息,我们找不到对应,也就没法进行复用了,我们把这个归结为屏幕空间的问题,因为屏幕空间的信息有限。
3.suddenly appearing background (disocclusion)
第三种情况,也就是突然出现的背景,如上图所示,因为某些物体的运动导致原本被这些物体遮挡的东西在第二帧暴露了出来,但是第一帧里并没有相关的信息,在找对应的时候由于是在图像空间上,所以查询到对应的信息是错误的,这种问题实质上也是屏幕空间的缺陷造成的。
而如果这时我们强行用上一帧的信息,就会产生拖尾,残影的效果。
4.Adjustments to Temp. Failure
那么如何避免上面提到的那些问题呢?出现的问题本身是由于无脑使用上一帧的信息导致的,我们只需要有取舍的使用上一帧信息即可。
Clamping
第一种方法自然是调整α的值,使得当前帧的贡献更大一些,当然噪声自然就会增加。
Detection
另一种方法则是我们进行检测,上一帧的信息到底可不可用。工业界上的一种办法,我们会用一个叫Object ID的东西对不同物体标号,我们会渲染一幅图,不同的物体有不同的颜色,这时候假设我们Motion vector找对应物体的ID不同,我们就不去采用上一帧的信息。而如果检测不能用,我们就需要调整blending中α的值了,让本帧的贡献更高一些,当然,噪声也会更多,我们当然可以对本帧的空间滤波增强,不过换来的自然是更模糊的图像,这依然是一种取舍。
5.More Temporal Failure
事实上,时间复用还有更多问题,这里再简单介绍两种,事实上我们前面提到的都是几何上出现的问题,但是其实在着色上也会出现问题。如上图所示,假如场景中的Camera和柱子以及平面都不动,而我们只移动光源的情况下,我们的Motion vector永远都是0,那么它就会永远复用上一帧的信息,这样就会导致阴影的拖尾现象,工业界人们常常叫做detached shadows。
再有就是Glossy反射的问题,如图中的椅子,假设平面不动,那么由于motion vector为0,平面的着色会永远复用上一帧,这样当我们移动影子的时候,平面上的反射效果也会有拖尾,或者说滞后,延迟的效果,反射效果的变化永远慢于实际椅子的变化。
以上两种情况都是Shading的变化造成的问题,这种问题传统的Motion vector并不能解决。
五、Some Side Notes
本篇Temporal方法和TAA也就是时间上的抗锯齿非常相似,它们本质上都是复用上一帧的信息提高一个像素内的采样数。一切利用Temporal的方法如DLSS都是相似的。简单地说,尽管temporal有诸多问题,但是相对来说还是比较好用的。
参考
GAMES202_Lecture_12 (ucsb.edu)
Lecture 12 Real-Time Ray-Tracing 1_哔哩哔哩_bilibili