【高质量渲染】—随机屏幕空间反射(SSSR)

随机屏幕空间反射(SSSR)

  • 概述

随机屏幕空间反射由屏幕空间反射演化而来,是SSR+IBL的合并产物。其思想都在于使用已有的图像来弥补BRDF或者说着色器渲染中对间接光照的不足。区别IBL中使用的方法是天空球,而SSSR使用的是屏幕。但也正因如此IBL中体面的妥协在SSSR中却成了最麻烦的累赘。而SSSR的出现可以说是对IBL的补完,IBL补充了环境光照,SSSR补充细节环境反射光照,而GI则是在IBL的基础上增加了细节漫反射光照。

  • 技术细节
  1. Screen Space stack
  2. Ray Marching
  3. Screen Space Reflect
  4. BRDF(GXX)
  5. IBL-
  6. 随机,重要性采样

  • 实现原理

3.1 Ray Marching 采样

屏幕空间反射使用一种称为光线行进(ray Marching)的技术来确定每个(Pixel)片段的反射。射线行进是迭代扩展或收缩某个矢量的长度或大小的过程,以便探测或采样某些空间以获取信息。屏幕空间反射中的光线是围绕法线反射的位置矢量。

直观地说,光线击中场景中的某个点,反弹,沿与反射位置矢量相反的方向行进,从当前片段反弹,沿位置矢量的相反方向行进,并击中相机镜头,让您看到场景中某个点颜色反映当前片段中。SSR 是反向追踪光线路径的过程(Ray Trace。Ray Trace 是Ray marching的一种)。它试图找到光线反弹并击中当前碎片的反射点。每次迭代时,该算法都会沿反射光线对场景的位置或深度进行采样,每次询问光线是否与场景的几何体相交。如果存在交叉点,则场景中的该位置是当前片段反射的潜在候选项。(IBL,Image Base Render 原理)。-

图3.1.1 行径演示


理想情况下,会有一些分析方法可以准确确定第一个交点。第一个交点是当前片段中唯一要反映的有效点(实际上过于乐观)。你不知道交叉点在哪(如果有的话),所以你从反射射线的底部开始,并在你向反射方向行进来对坐标进行迭代。每次迭代,您都会比对当前坐标与像素深度。如果你确实撞到了什么东西,你会尝试该区域周围的点,希望找到确切的交叉点。(二次迭代精确详细目标点,属于一种优化方案)

扫描二维码关注公众号,回复: 15550018 查看本文章

图3.1.2 对红色区间进行二次迭代得到准确区间

3.2 Screen Space Reflect

Ray Marching是用于比对空间坐标关系的一种手段,而为了实现Reflect,我们需要满足利用Ray Marching去满足Reflect的需求,也就是我需要在Ray Marching的同时得到反射源。(当然这个阶段还只是传统的SSR,AMD使用的一款仿真渲染器至今还在使用这种反方式,因为他的效果与我们之后将提到的SSSR十分近似,只是不是PBR的而已)。


为了满足反射源这个性质,我们只需要稍微动一点手脚即可,也就是沿着反射方向进行迭代即可:

                                                   图3.2.1 Ray Marching采样反射源

但需要注意的是,我们现在使用的是Screen Spaced Stack,这意味着我们手头的数据只有二维的平面数据,这也将成为SSR最大的缺陷和遗憾。这点我们在完成后就可以明显体会到。

就像 SSAO 一样,SSR 在屏幕和视图空间之间来回切换。您需要相机镜头的投影矩阵将视图空间中的点转换为剪辑空间。从剪辑空间中,您必须再次将点转换为 UV 空间。进入UV空间后,您可以从场景中对顶点/片段位置进行采样,该位置将是场景中离样本最近的位置。这是屏幕空间反射中的屏幕空间部分,因为“屏幕”是映射到屏幕形状矩形上的纹理UV。

整个原理很简单,我们的Ray沿着Reflect Direction(反射方向)每步进一步,我们就采样深度纹理,让Ray的深度和当前pixel(像素)的深度进行比对,如果撞击到,就说明这个点就是反射源。当然这个想法是很美满的,但是效果是很骨感的。因为我们无法保证每个点都能完美无缺地击中,所以我们需要增加一个Trickness(容差厚度)。当然本文只做原理介绍,起始实现细节上面有很多点,但是数量繁多,你也可以看参考页看源码。

而实现了SSR后你将看到

                                                           图3.2.2 Reflect反射结果


这是传统SSR将得到的效果,因为迭代次数和容差空间,你可以看到他给出的效果会有非常明显的条带。传统方法会使用一些模糊技术来让条带变成颗粒。但这同时也让反射的精度有所下降。这也就是为什么我们说SSR过于理想化的一大原因。想要减少条带唯一的方法就是提高步进精度,但是同时也会带来严重的性能消耗:

图3.2.3 步数提高一倍Reflect反射结果

  但即便如此也无法解决掠射角问题(同shadow map,无法解决晶格状问题),但不得不承认的是,SSR的效果是非常惊艳,他可以在无光追的情况下实现一些平面的简单反射效果,这对于提升在整个场景质量是不可或缺的元素。

3.3  Mip/Blur Map Smapler

接下来还是传统SSR的版块,也是SSR相较于SSSR最大的区别所在—如何计算反射光的光照。SSR使用的计算方式,是类似与IBL,采用多级MipMap,然后基于粗糙度来得到对应的反射图像。他不是PBR的一种渲染,因此具有一些不真实的问题。而SSSR则结合了IBL和SSR的渲染方式在,虽然也沿用了Mipmap,但是此时的Mipmap是用于模糊远处光照的,而SSSR计算粗糙反射图像的方式是使用GXX采样,换句话说就是Ray Tracing那一套(PBR),这意味着得到的数据是真实打到的数据,而非模糊的数据,也因此传统的SSR是在最后的采样图像上面动手脚,而SSSR则是在UV层面做处理。

而接下来我们将会介绍SSR的处理方式,SSSR的处理方式将会在SSSR的板块中一并讲解,因为这是一体的内容。


SSR的思路很简单,就是依据不同的Roughness程度,去采样不同的MipMap,Mipmap的生成本身就说一个降采样的过程,而现在再进行一次超采样,就会变成一张模糊的图像(这也是常用的快速图像模糊算法。而依据模糊的图像来得到对应的光照(直接贴上去),就完成了这么一个过程:

图3.3.1 模糊场景采样得到的反射结果

水面的倒影就是我们最后得到的结果,当然,你也发现了,SSR如何建立粗糙度和反射的关系可是个非常头疼的问题,即便我们能够找到一个合适的值去采样不同的贴图但无法避免的一个问题就是我们没有办法找到一个合理的方式去论证我们得到的反射结果是否正确(physical Base),如果留下这个问题势必会对之后的光照完善埋下隐患。不过至少从结果上来看,SSR和SSSR其实相差不大(因为人眼对次级光照并不敏感),同时SSSR的性能要求和计算过程都是SSR所不能比拟的。这也就是许多渲染器还在沿用SSR的原因。

3.4 IBL对reflect的光照理解

IBL类似于Unity当中的反射探针和光照探针。

SSSR本质是对IBL的一个补完,其中很多理念都涉及到了IBL的内容。当然我们不会过多的关注IBL,而我们只把其中的反射项单独拉出来讲述.

如果我们了解IBL就知道IBL有很多“体面的妥协”,但这些体面的妥协却在SSSR中被无限放大成为了致命的弱点,所以有必要去了解一下IBL的内容,才能理解SSSR的思想。

IBL的反射项为  


而为了去实现这个多维积分,UE的方法是使用近似,积分的乘积等于乘积的积分:

 

 
同时因为反射瓣性本身就具有方向性,使得不同视角看到的反射光不应该一致,而IBL为了弥补这个问题,采用的方法是视而不见,也就是和Diffuse一样正常采样,这也为SSSR的实现造成了麻烦。

图3.4.1 反射瓣形

而在IBL本身就是SSSR的一个核心思想,在SSSR中也会使用同样的方式去采样得到环境光照,只不过现在光源从Cube map变成了屏幕。同时为了使他合理,SSSR更是采用了Trace的方式来实现,但是为了兼顾性能,我们常会使用Filter(滤波)和multisample(复用采样)以及重要性采样,去实现更少的步数实现更好的效果,当然由于这三种本质上都是一种mix(混合)的算法,所以他们所能达到的精度是有限的,并且需要次数无线逼近才能达到Trace的效果。

顺带一提,IBL是一个大的概念,其本质都是动态规划(重复利用),在屏幕空间栈中,我们使用SSSR和SSGI去弥补IBL的在细节上的缺陷。

3.5 寒霜提出的SSSR(Stochastic Screen-Space Reflections)

寒霜提出IBL的初衷是为了使得SSR品质提高的同时,利用PBR来减少参数控制,于是借鉴了Ray Tracing的方案。同时为了能够降低Ray的条数,使用了multisample(复用采样)和重要性采样混合双打,同时寒霜认为multisample(复用采样)是Color resolve的一部分。所以SSSR的本质是Ray Tracing的漫反射“阉割版”,当然相对于离线的Tracing,SSSR的算法更加复杂,毕竟离线状态你只需要无限逼近就行。

3.5.1 Importance Sampling 重要性采样

重要性采样(Importance Sampling),其中文翻译过于不准确,我们更喜欢称之为对“重要的“采样,当然,为了学术规范化表达还是称之为重要性采样。重要性采样的精髓在于概率密度分布函数,在重要性采样当中,我们为了逼近暴力结算(穷举),需要给得到的数据乘以一个权重,最后再加起来得到平均值作为最后的采样结果。其核心思想和IBL一致。

GXX 重要性PDF:

而为了实现随机采样传统方法会将PDF分解为立体角二位向量与PDF的边缘分布概率:

and

而边缘概率分布是可以平均采样的,这样就形成了非常重要性采样的基本参数。表示为:


该式子的意义是随机打出射线,去得到环境光照平均辐射量,因为光照辐射度恰好和表面半圆相符,我们可以认为这一个点受到的环境光为每一个方向的立体角对应的光照不断累加,夸张的说假如我们只打到了一个光源L,那么这个所受到的环境光就应该是L*辐射半圆面积。

    图3.5.1.1 重要性采样

       当然,因为我们这里使用的是GXX和Reflect作为采样基准,所以得到的反射的积分区域会是一个瓣形,如图3.4.1一样。

3.5.2 Multi sample 多重采样


       多重采样是寒霜在2015年提出的解决重要性采样(Importance Sampling)积分次数不够所导致噪点过多(一般4个Ray在实时渲染中就逼近极限),其思路是重新利用已得到的Hit UV/Light(有用UV的也有用最后结果的,但是用uv更符合物理,真实性更强,不过会加大瑕疵)作为采样数据,通过采样相邻点的数据作为假装自己发射的光线,来得到更加多的样本数量,它是一种从时间上解决问题的方法,假设我们使用了4个点,一个点发射4个Ray,就可以使得最后得到的发射数量从4变成16个Ray,但是我的每个像素点实际只发射了4个ray。

图3.5.2.1寒霜的重复采样


       这个思路的初衷是非常好的,但是很明显这个想法是延续了IBL当中对反射光的“体面妥协”,也就是所有点的所放出的反射瓣形都是一致的,但是很明显在光源比较小,而反射面积比较大的时候就会出现明显问题,如图3.5.2.2,我们假设表面无限光滑则反射瓣形应该是一条线(为了更好表达我们不考虑粗糙度):

图3.5. 2.2实际反射情况
   那么实际的情况可能和我们想的相差甚远,相邻区域的像素在掠角,小光源的情况下其反射方向差异也越大,其差异值和掠角与反射物体距离有关,这就导致我们得到的相邻重要性权重(PDF)可能完全不是该像素所应该具有的权重:


   就和公式中一样。我们相邻射线的Pk可能并不是无偏的,这将会导致画面出现很多噪点。(这里我们直接使用寒霜的截图,因为一开始实现的时候就知道有这个缺陷没有去实现):

图3.5.2.3寒霜所提供的结果

   而为了弥补这个缺陷,寒霜动了一点“歪脑筋”,一般也被称之为Spatiofilter(空间滤波)

3.5.3 patiofilter 空间滤波

   名字叫做空间滤波,但是和空间并没有太大关系。空间滤波最核心的地方就在于权重的替换。

   寒霜发现由于掠角的反射向量差异过大而使得PDF差异过大,使得最后积分并不是无偏的,所以寒霜想出了一个办法,既然是PDF造成的差异,那么干掉PDF,或者削弱它的影响就好了,碰巧的是这里的PDF刚好和IBL的反光项PDF都是GXX的PDF,由此和IBL正式搭上了关系。

   我们原本的积分公式表示为:

   而为了干掉Pk(消除pk),我们同时乘以除以了IBL反射的反光项:


   最后可以表达为:

 

   因为PK和IBL一样都是GXX的PDF,这样通过积分的近似就巧妙地把PK给消去(当然只是减少了影响)

这样我们就巧妙地降低了噪点(但是还是有)。当然,寒霜也提到了另外一个办法,既然你是因为法线引起的差异,那么我就去求这块区域的平均法线就行,但是这个方法始终是有一点Hack的方法,因为你没有依据证明平均法线的反射就是该点的反射。

伪代码表示为:

result    = 0.0

weightSum = 0.0

for pixel in neighborhood:

      weight = localBrdf(pixel.hit) / pixel.hitPdf

      result += color(pixel.hit) * weight

      weightSum += weight

result /= weightSum

其效果如下图:

图3.5.3.1 空间滤波效果


可以看到相较于图3.5.3.2 的效果以及有了非常大的变化:

图3.5.3.2 未增加滤波SSR效果

3.5.4Temporalfilter 时间滤波

    滤波和空间滤波没有太大关系,只是它的数据源是空间滤波之后的图像,但是时间滤波和多重采样(Multi sample )有很大的关系。在多重采样当中,我们是在同一个时间维度,使用不同空间维度使得采样光线数量从N变成了N*M,而在时间滤波当中,我们希望它能从N*M进化到N*M*X,也就是三个维度。

其思路也很简单,我们把上一帧的数据存储下来,可以是Hit UV(击中点)数据,也可以是最终色彩数据(End Color),如果是Hit UV就将他作为一个额外的Hit点进行计算,这样只要我摄像机是不动的,我的采样数量就可以一直累加。而是最终色彩数据,则是认为一段时间内打出的射线收集到的反射光的平均值,此时只需要按照速度线性混合即可。

而之前说到两种方法都需要去考虑摄像机不动的情况,所以为了使得更加泛用,还需要考虑空间连续性,我们需要记录摄像机的速度,来计算像素点偏移量。你可以依据深度和FOV推算,也可以通过转换相机空间得到,总之方法有很多。

float4 OldColor//上一帧该点所在位置的颜色

float4 NewColor//这一帧该点的颜色

float3 Velocity

NewColor = AA_Filter(NewColor…)//AA滤波

float Tompweight = ResolveVelocity…//依据你的权重算法得到混合权重

return lerp(OldColor, NewColor, Tompweight);


同时为了减少画面的噪点,我们还需要增加一点Filter来减少画面噪点。伪代码表示为:

当然以上只是伪代码,实际AA滤波和权重计算也是非常麻烦的问题,具体可以看项目源码。

得到的效果如下:

图3.5.3.1 时间滤波滤波效果

  • 未来的工作

4.1 抗锯齿

在SSSR中,我们使用PBR的方法,按照IBL的思路去采样环境光照形成反射光。但是由于线条的不足往往会形成黑白噪点,虚幻为了同时兼顾性能和表现,使用的方法是低精度采样如何直接模糊。而我们实现的时候如果同时要达到高精度,则需要对最后渲染的反光图像进行一次模糊,再输入到屏幕,而另外一种方法就是使用屏幕抗锯齿,而两种混合双打的可能会有更好的效果。

4.2 SSSR缺陷

   SSSR和SSR有一样东西通病,就是屏幕没有的地方就无法照亮,这个问题是解决不了的,同时它也具有Ray Tracing,IBL和Raymarhing的缺陷,IBL的是掠角效果差,Ray Tracing是相邻积分结果屏幕空间反射不同形成白噪点,Raymarhing则是精度不足形成条带。这几个问题并没有特别好的方法,我们往往只能像抗锯齿一样把他们给弄糊来解决这个问题。

 参考(均为有用的精简内容)

【1】SSR Screen Space Reflection | 3D Game Shaders For Beginners (lettier.github.io)

【2】寒霜2015 Siggraph SSSR:Stochastic Screen-Space Reflections.pptx (live.com)

【3】IBL:图形学基础|PBR回顾_桑来93的博客-CSDN博客

【4】重要性采样(Importance Sampling) - 知乎 (zhihu.com)

猜你喜欢

转载自blog.csdn.net/qq_42999564/article/details/127631258