Spatiotemporal Variance-Guided Filtering简称SVGF,可以看到Spatiotemporal意为时空的,而非单独时间上的或者单独空间上的。SVGF和我们上篇提到的滤波差不多,但又有一些区别。从上图看1spp/使用SVGF/Ground Truth的对比图可以看出,SVGF的效果还是相当不错的。
SVGF通样考虑3个因素来控制滤波,首先是Depth也就是深度,如上图所示。该公式我们可以明显的看到并不是高斯,我们上篇说过滤波核不一定非要用高斯,只要它是一个衰减就可以。上图公式中的分子项很好解释,就是一个深度的差。那么分母项是什么意思呢?首先σz是一个参数,控制衰减的强度,而ϵ是为了防止分母为0的情况加上的一个小的常数。
而中间的则可以理解为两点沿着平面法线垂直方向的距离,为什么要这样计算呢?看上图A,B两点,它们的深度差距应该是很大的,但是它们在同一平面内显然是应该有相互的贡献的,但是如果用之前的思路显然深度的差距会让它们的互相贡献衰减的非常小,这时候我们如果在分母加入垂直于法线方向距离的项就可以解决这个问题,如果深度差距和切平面距离都很大,那么最终的值就会相对较小,那就说明两点有较大的相互贡献。
SVGF的第二个因素同样是法线,并且它也不是用高斯来描述衰减,而是用点积,如上图所示。和布林冯模型的高光相同的,从0截断,并且加上一个指数σz控制衰减速度。
并且如果存在法线贴图的话,人们通常会用原始的法线作为标准,而不是贴了法线贴图之后的,这是为了避免错误计算贡献。
SVGF考虑颜色的方式和我们上篇提到的也差不多,如上图公式所示,σ和ϵ和上面深度提到的作用相同,不再赘述。主要看分母的标准差项。
为什么分母多了个方差的开方呢?可以参考上图中的A,B两点,可以看到如果按我们前面提到的,两点的颜色灰度差异可以判断两点之间没有贡献,但是如果B点恰巧在白色的噪点上,那么A,B将会被误认为是有贡献,这时候就需要B点的方差来进一步判断此时B点的颜色是真的颜色值还是噪点了。
方差的计算步骤如上图所示,首先在一切开始之前先计算一次该点的方差,比如7x7范围内的,之后我们还可以通过temporal,通过在时间上的累计计算平滑的方差,最后在使用之前我们再取一个小范围比如3x3做平均,最后作为颜色差异滤波核的分母根号项。这样方差越大,公式就越不会相信当前的颜色灰度值差异较小带来的错误贡献了。
RAE同样是一个后处理降噪的方式来实现RTRT,它调用了神经网络自动来完成Temporal累加这个过程,其中会用到G-Buffer中的信息。
Recurrent block结构允许每一层的信息留给当前层,这样当前帧结束后跑下一帧的时候就已知了上一帧的信息,当然这里上一帧并没有用Motion vector而是神经网络自己学习如何利用。其中Skip / residual connections的结构方便信息直接流向最后,方便神经网络训练。
可以看到,RAE的效果还是比较不错的,当然同样的它也有问题,有时候会出现Overblur的现象,并且也会有残影,这是时间复用无法避免的。
SVGF质量更好,而残影现象它们都有,包括一些boiling artifact,因为滤波没有处理掉低频噪声。 SVGF基于temporal累计比较快,而RAE需要经过神经网络在刚提出的时候比较慢,现在有所好转但相对来说还是较慢。但是在不同的spp下面,RAE的performance速度是固定的,而SVGF如果多1spp速度就会慢非常多。
TAA是一种反走样的技术,回顾我们为什么会产生走样,因为采样数不够,所以之前才有在GAMES101里提到的MSAA。而TAA就是我们之前讲的时间上的复用来增加采样数增加采样数的方法来反走样的。
对于静止场景,TAA把一个像素内分成4个感知点,如上图所示,每个感知点记录每一帧的信息,如左上角是当前第 i 帧,右上角是 i - 1帧,右下角是 i - 2帧,左下角是 i - 3帧,每一帧的其中一个感知点复用上一帧感知点的信息,是一个递归的过程,这样就把每四帧的信息做了一个平均形成了复用。那么对于运动的场景,自然就需要我们之前提到的Motion vector了,包括TAA有些问题同样需要我们用之前提到的Clamp方法来解决。
关于TAA可以参考:图形学基础 - 着色 - TAA抗锯齿 - 知乎 (zhihu.com)
MSAA和SSAA是两种不同的反走样方式,SSAA是超采样,也就是说它是真的用更高的分辨率去渲染整个场景,最后再通过降采样的方式去恢复图像的大小和显示器分辨率相同,遮挡然会造成问题,如果一个像素被分成4x4采样点,那么计算量就会增加4倍。
而MSAA不同,对于一个像素内的四个采样点,如果采样点在同一个三角形上,那么只会做一次着色,着色位置则为这几个采样点一个有代表性的位置,如上图第一幅图所示。并且MSAA的采样点分布有时候很特殊,如上图的第二幅图,6个绿色的采样点分布在两个像素上,但是中间的两个采样点在两个像素边界,那也就可以被认为是两个像素共有的,可以同时被两个像素复用,那也就相当于6个采样点变成了8个采样点。
Image based anti-aliasing顾名思义就是在有noise的图像上做反走样的方法,其中现在最流行的是SMAA(Enhanced subpixel morphological AA),基于图像的反走样的历史发展是从我们GAMES101中提过的FXAA到MLAA(Morphological AA)再到现在的SMAA。以MLAA为例就是上图所示,先从有锯齿的图像上通过一些匹配方法找出边界,再根据真实边界的占比对像素赋予不同百分比的颜色以达到反走样。
其次G-Buffer的信息是绝对不能做反走样的,无论是深度法线还是其它的东西,这会导致原本记录的信息错误。
DLSS是英伟达提出的Temporal Super Resolution,它可以把一张低分辨率的图变成一张高分辨率的图。DLSS1.0是通过深度学习去猜的,基本每个游戏的每个场景可能都需要单独训练一个神经网络去学习常见物体的边缘,然后才能确保相对正确的把拉大了的图的模糊边缘替换成不模糊的边缘。DLSS2.0则是利用temporal的信息来提高分辨率,因为图分辨率拉大变糊的本质仍然是采样率不足,所以DLSS2.0的核心思想就是利用temporal信息。
但是DLSS2.0与我们之前说的temporal方法有更严重的问题需要解决,我们不能再用Clamp去解决temporal带来的一些经典问题。因为和之前的temporal方法不同的是,我们提高了分辨率,那么对于新的更小的像素,我们对它颜色不能简单的参考它原来周围的点,否则得到的新的高分辨率的图会很模糊。所以DLSS2.0的关键就是需要找到一个更好利用temporal信息的方案。
DLSS2.0本质上是渲染一张低分辨率的图,然后用神经网络拉成高分辨率,这样由于我们实际渲染的分辨率比较低,帧数也就会提高,当然这也需要它跑一遍神经网络的速度非常快,所以可能需要做针对性的优化。
可以看到,和立方卷积插值相比,DLSS2.0的效果非常好。
Deferred Shading—延迟渲染是为例让Shading更高效的一个方法。 传统的渲染管线有一个问题,那就是我们有大量的光栅化着色了的片元在最后可能通不过深度测试,所以计算它们是非常浪费的。而延迟渲染让我们可以只计算能出现在屏幕上的可见片元,减少了计算量。
延迟渲染的基本思路就是两次光栅化,也就是双Pass。Pass1光栅化不进行着色,只用来更新深度缓存。由于Pass1已经得到了场景的所有最浅可见深度,这样Pass2就可以只对与当前深度缓存中相同深度的片元进行着色就可以了。这样就把复杂度变成从frag*light变成了vis.frag*light了。
(这里不知道是不是鄙人认知错误,因为在我之前学到的东西里,闫令琪老师讲的上面所说的过程是Z-prepass,而真正的延迟渲染是相当于在G-Buffer的信息基础上进行类似屏幕空间后处理的计算)
当然延迟渲染也有问题,就是不能做反走样,因为深度缓存依赖于G-Buffer,而我们提到了G-Buffer是不能做反走样的。但是我们仍然可以结合上一帧使用TAA或者是图像空间的做法来进行反走样。
分块渲染—Tiled Shading主要是为了解决Deferred Shading中的多光源问题,它是建立在Deferred Shading之上的。分块渲染顾名思义就是把屏幕分成许多小块,假如每个小块里的像素是32x32,那么每个小块其实对应三维空间的一条立方体如上图所示。这么做的意义是什么呢?我们之前推到过渲染方程从对立体角积分到对光源积分,会有距离衰减,那么光源强度衰减到一定值的时候我们就没有必要考虑它的贡献了,如上图所示的每个球就是一个光源我们对它划定的最大影响范围,这就说明并不是每个光源对每个分块都会有影响,这样就可以对每个分块单独计算着色了。上图每个分块上的数代表了它被几个光源影响。这样我们的复杂度再次降低从vis.frag*light变成了vis.frag*avg light per tile(每块平均光源数量)。
Clustered Shading是在Tiled Shading基础上进一步的优化,它不仅对像素分块,他还对深度进行了切片,使得3D空间变成了许多格子,这样做也是因为在Tiled Shading划分的长条区域内,仍然有区域外的光源会被认为对该长条区域有影响,而此时如果在深度上再做一个切片就可以避免这种情况,这样就更加减少了无意义的shading。复杂度也再次降低。
Level of Detail—层次细节很常见,如我们熟知的Mipmap。我们在渲染的时候不同情况选择正确的level有助于我们节省计算量,在工业界这种方法我们常称作Cascaded方法。
Cascaded方法的一个例子就是Shadow Map,也就是我们常说的CSM。总所周知,物体离我们越远在屏幕上就越小,那我们就越不需要去精细的去表示它们,阴影也是一样,而CSM就是这种思路。离我们越远的物体,我们Shadow Map上记录的就越粗糙。但是我们没法去生成一个便分辨率的Shadow Map,所以一般人们会生成两种Shadow Map,如上图红色就是精细的Shadow Map,而蓝色框就是粗糙的Shadow Map,然后根据深度去选择采用哪个Shadow Map。为什么蓝色区域和红色区域有重叠呢?这是为了避免Shadow Map的突变,使得有一个平滑的过度,它们交叉的区域相当于一个过渡区的Blending操作。
LPV也是一样,我们的次级光源在格子里传播,我们可以一开始用精细的格子传播,而随着传播我们可以用更粗糙的格子来传播,减少计算量。
Cascaded面临的问题就是不同层次的切换问题,一般我们都需要在层之间进行blending平滑过渡来解决。
Cascaded的另一个例子就是网格层次细节划分。也就是说离我们远的物体的网格,我们没必要去那么精细的表示,那么也就可以随距离划分一个不同层次的网格,离我们近就用精细的,远就用粗糙的。甚至我们可以动态调整一个模型的不同部位的网格的精细程度,只需要提供一个标准就好了,比如:任何时候我都希望一个三角形的大小不要超过一个像素。那么切换的突变怎么解决呢?其实完全可以交给TAA处理,我们完全可以通过temporal的复用来达到平滑处理的效果,而这其实就是UE5中Nanite的基本思路。
对于LOD,当然还有许多困难需要解决,这些困难的解决才是技术上最难实现的地方。比如,如何保证不同像素中不同层级的网格能完美的衔接上?以及这么多层级的网格我们真的要全部存储下来吗,如何动态的调度它们呢?还有用几何纹理表示几何会不会是一种更好的方式,因为几何纹理做LOD更简单,而三角形面简化会更复杂。同时裁剪剔除这些也都是工程上常用的优化手段。
我们前面的片幅,除了RTRT没有任何一种方案能完全解决全局光照问题。但是RTRT又受限与速度,没法在所有流程全部都使用RTRT。而工业界通常是把所有办法都杂糅在一起去考虑解决问题。
比如,一个可能的解决方案,我们先用SSR做一次GI,但是当然会有很多failure,比如说我们前面提到的反射缺失等等。然后我们再考虑用其它方法去弥补它出现的问题,或者是用硬件或者是软件去做tracing。
软件做tracing,近处我们就需要高质量的SDF,通过SDF去trace会比BVH快很多,而远处就只需要稍微低质量的SDF去trace了。而对于手电筒这种强的方向光源和点光源,我们之前提到过,可以用RSM,其次还有一些方法如DDGI,是使用场景中分布探针来照亮实现GI的,也是一个3D空间中实现的全局光照方案。
而硬件上,我们算间接光照并不需要太复杂的几何,因为没必要算那么准确,用简化的模型来代替原来复杂的模型,这样就可以一定程度上加速我们的追踪。而另一种RTRT结合Probe的思路叫RTXGI,感兴趣的朋友可以自行了解。而图中高亮的部分就是UE5的Lumen的基本思路。
- Texturing an SDF
- Transparent material and order-independent transparency
- Particle rendering - Post processing (depth of field, motion blur, etc.)
- Random seed and blue noise
- Foveated rendering - Probe based global illumination
- ReSTIR, Neural Radiance Caching, etc.
- Many-light theory and light cuts
- Participating media, SSSSS
- Hair appearance
- …
GAMES202_Lecture_14 (ucsb.edu)
Lecture 14 A Glimpse of Industrial Solution_哔哩哔哩_bilibili