【Siggraph 2021】Radiance Caching for real-time Global Illumination

Epic团队在Siggraph 2021上对Lumen技术细节做了进一步介绍,这里尝试基于自己的理解转成中文版本。

照例对文章重点做一个总结:

  1. Final Gather是Lumen中用于解决不透明物体的动态GI是如何计算的方案,其中包括屏幕空间probe、世界空间probe等的摆放与射线追踪,屏幕空间像素incoming radiance数据的获取逻辑等
  2. probe都会被转化为多个方向的射线去追踪场景的光照以及命中深度(必要时还需要追踪物体的速度以优化动态移动物体的GI表现),结果存储在一张八面体映射的贴图中,每个像素对应于一根射线
  3. Trace可以支持软光追也支持硬光追
  4. 为了提升质量,每个probe追踪的射线不能太多,此处会通过屏幕空间probe摆放位置的抖动、射线方向的抖动+时域滤波,相邻probe的射线数据复用与空间滤波,基于BRDF+incoming radiance预估射线方向的重要性采样+自适应分辨率的射线密度分布机制,probe数据转SH数据等策略来做降噪处理
  5. 远景数据通过屏幕空间probe就不能覆盖,这里设计了一套低分辨率的世界空间probe来应对这个问题,需要更新的probe由屏幕空间probe插值需要与否来判断
  6. 除了上述主体框架之外,还做了一系列的事情来提升整体的质量,如基于bent normal来添加contact shadow效果,基于命中点reproject后的射线方向夹角来做reject,自适应分辨率的屏幕空间probe摆放,基于footprint范围的远景射线reproject逻辑,远景probe射线复用前的简易遮挡判定逻辑等策略来提升整体的质量。
  7. FinalGather整体运行效率较高,每个像素采样两根射线的情况下,2080TI,1080分辨率耗时为4ms左右,如果降低到0.5像素,还能降低到1ms。
幻灯片1.PNG
幻灯片2.PNG

本文重点有两点:

  1. Radiance Cache,即Lumen中的Radiance是如何实现数据缓存以提升整体计算性能的
  2. Final Gather的实现细节
幻灯片3.PNG
幻灯片4.PNG
幻灯片5.PNG

Lumen有如下几个特点:

  1. 支持动态GI
  2. 面向下次世代主机
  3. 效果上,期望在高端PC上做到第一流
幻灯片6.PNG

下面对实现细节做一下介绍

幻灯片7.PNG

这是经典的光照方程,需要在屏幕空间的每个像素上都执行一遍

幻灯片8.PNG

通俗点来说,最终输出的Radiance是一个积分,对来自各个方向的输入Radiance跟BRDF相乘结果的积分。

幻灯片9.PNG

计算机中,积分通常可以借助蒙特卡罗公式转成离散的累加,而输入的Radiance数据则可以通过Ray Tracing来拿到,而这个在当下要想做到实时,会遇到一个很大的问题。

幻灯片10.PNG

实时Ray Tracing的计算效率极低,主要是因为场景用两层BVH结果来表达(上层是物件实例,下层是三角面片),有如下两方面的问题:

  1. 遍历是不连贯的,cache miss高,访问效率低
  2. instance重叠的区域,会经常需要对重叠的区域做重复的、来回的查询确认
幻灯片11.PNG

要想结果达到满意的效果,每个像素需要发射数百根射线,但实际上我们的硬件最多只能承受0.5根射线的计算成本。

幻灯片12.PNG

户外场景颜色相对一致,场景密度低,对质量要求稍低,每个像素大约需要100根射线

幻灯片13.PNG

室内像素色差大,物件密度高,对质量要求高,每个像素通常需要500+根射线才能保证质量。

幻灯片14.PNG

因此,相对而言,室内场景的表现才是GI效果挑战最大的地方,后续比对主要以室内场景为例进行测验。

幻灯片15.PNG

路在何方?先来看看前人的解决思路,这里主要有两类方法:

  1. Irradiance Fields(辐照度场)
  2. Screen Space Denoiser(屏幕空间降噪器)
幻灯片16.PNG

辐照度场是一种基于probe的方案:

  1. 在场景中按照一定的规律(最常见的是将场景做均匀分割,得到3D Grid,上面每个顶点摆放一个probe)布设probe
  2. 对每个probe上的Irradiance做预计算(可以是离线,也可以是运行时)
  3. 在运行时对每个像素,找到其周边最近的一个或若干个probe,采样数据并进行插值,得到当前像素的Irradiance(还是Radiance?)
  4. 有时候为了避免probe因为遮挡而带来的漏光问题,还会在插值的时候考虑probe与像素之间的遮挡关系
幻灯片17.PNG

但现有的实现方案中,都无法很好的解决三个表现问题:

  1. 漏光
  2. 过遮挡
  3. 光照结果过于低频,表现为表面很平,缺少高频的光照细节,这主要是因为Irradiance计算时的分辨率较低

除此之外还有一些工程层面的问题:

  1. 如何摆放probe才能保障质量与迭代、计算效率是一个至今没有定论的问题
  2. 为了避免产生一些瑕疵,各个probe上的光照更新频率不能过快,对一些光照快速变化的场景会造成问题(?)
幻灯片18.PNG

屏幕空间降噪方案的主要原理是,从当前像素向着反射方向发射少量的射线,通常是每个像素只发射一根射线,之后通过降噪的算法将画面质量提升到可以接受的地步,这些算法常用的策略为时域、空域的数据复用。

最近的一些成果借用Recurrent Blur算法可以给出一个非常不错的效果。

幻灯片19.PNG

但是问题是,屏幕空间降噪的输入数据噪声实在是太高了,每个像素发射的射线分布在余弦(根据粗糙度)角度范围内(范围),射线追踪采用的是固定采样率(固定步长?)。

幻灯片20.PNG

噪声强度不是一成不变的,距离光源近的区域射线命中光源的几率大,因此噪声会低一些,反之噪声则高。

而如果想通过增强滤波来消除噪声则会导致光照质量的下降(?)

幻灯片21.PNG

接下来看下Lumen是如何以低成本的方式获得高质量的输入Radiance的。

幻灯片22.PNG

首先来介绍一下屏幕空间的Radiance Cache(辐照度缓存)策略:不再以像素为单位发射射线做光线追踪,而是降低光追的频次,将多个像素作为一个组(每个组对应一个Radiance Probe),以组为单位做光线追踪(这里隐含的假设是,间接光反射,主要是漫反射相对低频,多个像素之间的光照结果复用性高)。

幻灯片23.PNG

虽然场景上各个像素的法线本身并不连贯,但场景的Radiance(实际上是Irradiance吧,Radiance需要考虑入射方向的,每个像素对应的Radiance值有无数个,用一张2D图也表达不了)却是连贯的(听起来是在解释前面复用的原理)

幻灯片24.PNG

而低频的输入Radiance与高频的场景BRDF结合,可以输出高频的光照效果,进一步证明了低频Radiance的可行性。

幻灯片25.PNG

除了光追是发生在低频的Radiance Probe上而非屏幕空间中的之外,滤波过程也是发生在Probe空间的。

幻灯片26.PNG

为了得到更高的光照质量,这里做了重要度采样,即在余弦分布范围内,为靠近光照的方向分配更多的射线。

幻灯片27.PNG

上面介绍的是屏幕空间的Radiance Cache策略,主要解决的是短距离上的输入Radiance的信息采集问题,但这部分数据无法覆盖全场景,一些相对远距离的区域的输入Radiance还得再额外想办法解决。

这里针对这部分数据设计了世界空间的Radiance Cache策略,实际使用的时候,优先获取屏幕空间Cache的数据,miss的时候再取用世界空间的Cache数据。

幻灯片28.PNG

这里对比了两种光追方案的表现,可以看到,采用Radiance Cache方案,即使采样计算消耗降为1/4,效果反而更好。

幻灯片29.PNG

Radiance Cache解决的是输入Radiance怎么获取的问题,接下来看看最终的间接光是怎么计算得到的,采用的是UE所谓的Final Gather逻辑。

幻灯片30.PNG

Final Gather主要借助的就是前面提到的Radiance Cache功能,优先基于屏幕空间的Cache,miss之后转世界空间Cache。

幻灯片31.PNG

Radiance Cache中得到的数据是低分辨率的,世界空间的Cache尤其低,而我们最终是需要得到全分辨率的光照结果的,这里需要借助全分辨率的bent normal数据对低分辨率的数据做整合与插值,之后做一次全分辨率的时域滤波操作来完成。

幻灯片32.PNG

先来看下屏幕空间Radiance Cache的具体实现步骤:

  1. 屏幕空间的Radiance Probe是基于GBuffer数据进行布置的
  2. 之后需要针对每个Probe发射射线进行追踪,得到Radiance结果
  3. 上述结果有较多噪声,这里还需要做一次probe空间的滤波处理

下面介绍一下具体细节。

幻灯片33.PNG

先来看看probe的数据结构:

  1. 每个probe用八面体来实现数据存储(而非SH),数据存在在一张分辨率为8x8的2D贴图中
  2. 贴图的每个图素对应于一条射线,也就是每个probe需要发射64根射线,而八面体的布局可以保证射线的方向在世界空间中是均匀分布的,射线按照世界空间均匀分布的方式发射的好处是,相邻probe的数据是吻合的,可以快速查找到相邻probe上同方向的射线追踪结果
  3. 各个probe的八面体数据汇总成一张Atlas,Atlas上的每个图素会存储两个数据:分别是当前射线命中点到probe的HitDistance与输入的Radiance
幻灯片34.PNG

再来看下屏幕空间Probe是如何摆放的,这里采用的是2007年一篇文章的思路:分层优化的自适应摆放策略(Adaptive placement with hierarchical refinement),简单来说就是先按照一个粗粒度的密度做均匀probe的布局,之后根据需要对局部区域做加密处理:

  1. 起始密度,相邻probe之间的像素间距是16,即每16个像素放置一个probe
  2. 经过上述布局后,对像素进行插值判断,找到那些插值失败的像素(如何判断插值失败?),如上图中左边的第一张小图,橙色区域表示的是插值失败的像素
  3. 为这部分区域的probe做加密处理:从图上描点来看,并不是将2x2个probe直接细分为3x3个probe,而是根据需要先挑选一个维度进行加密,即从某个1x2的probe变成1x3的probe,这样的方式更省,但是会需要增加一些额外的判断成本;另外注意观察中间小图左侧树枝上的橙色区域,这里并没有把树枝经行的probe都做了细分,因此推测,这里是先尝试做加密,如果判断加密后probe并不能有效缓解问题,就放弃此次加密,留待下次
  4. 对经过一次加密处理的probe做二次插值判断,找到那些依然不能满足条件的区域,继续加密(过程仿照上一步)

上图只展示了三级密度,注释中说的是将这个过程持续下去,直到不符合条件的像素数目低于某个设定的阈值为止,这里的一个问题是,在密度层级上是无限的,还是最多三级?

幻灯片35.PNG

之所以不需要不停递归到像素级别,确保每个像素都能够插值成功,是因为这样带来的成本会比较高,这里有性价比更高的解决方案:通过flood fill算法来覆盖插值失败的像素(具体如何做呢?)

幻灯片36.PNG

要做到实时,需要控制probe的密度上限,这里用一个具有有限分辨率的atlas来实现:

  1. 加密的probe放到atlas的下面部分,从而可以实现所有probe的同批次处理(不是太理解这里的含义,说的是避免分成两个pass完成?这里怎么得到atlas texture element跟probe之间的映射关系呢,增加一张index table?)
  2. 在加密的过程中,如果发现atlas空间已经用完了,就停止加密,未被覆盖的像素自动采用flood fill算法覆盖(加密与否采用的是BFS还是DFS?从表现来看,应该广度优先,即先完成一层的加密,再考虑下一层,而非针对某个局部区域,一直加密到加无可加为止)
幻灯片37.PNG

上面介绍了probe的整体布局,下面看看单个probe的摆放逻辑:

  1. 为了通过时间复用来平滑噪声,这里针对probe在屏幕上的位置以及八面体像素射线发射的方向都做了时间上的抖动
  2. 为了避免漏光等问题,probe摆放的位置直接重叠到像素对应的世界空间坐标上,而这种做法带来的一个问题是同一个probe cell中的其他像素与当前摆放probe的像素之间可能会有遮挡,如果直接插值取用,会导致效果异常,这里通常需要通过一个时域滤波来缓解。
幻灯片38.PNG

为了将probe数据应用到像素上,需要对每个像素找到其周边的probe,做插值处理,这里的插值算法是这么设计的:

  1. 计算每个像素的插值平面,插值平面由像素的位置以及法线构成
  2. 计算相邻probe到插值平面的距离(投影距离),以这个距离作为插值权重

采用这种算法,可以避免前景的光照漏到后景上,即probe到相机的距离近于像素到相机的距离的情况下,probe对像素的贡献会被忽略(应该也不是忽略,而是会根据平面的方向找到正确的入射radiance,避免漏光?)

幻灯片39.PNG

这里对插值时像素的偏移还做了抖动(所谓的偏移是说偏移像素的插值位置,还是偏移像素在probe贴图上的uv?):

  1. 需要确保抖动后的位置跟原始像素处于同一个平面(看起来是对像素插值位置做偏移),这样做的目的是避免因为抖动导致原来能成功插值的,现在却失败,即不改变需要用到的probe的可用关系
  2. 为了保证抖动后结果的稳定,这里还复用了TAA的相邻数据有效性clamp的逻辑

上述抖动可以将多个probe之间针对该像素的光照差异做一个分散

幻灯片40.PNG

为了验证思路是否是正确的,这里通过增加probe上的射线数目以及probe的密度来进行效果确认,这里给出了这种模式下与path tracer结果(Ground Truth)的比对,可以看到,效果是差不多的,也就是目前是走在正确的道路上的。

幻灯片41.PNG

但是如果将射线数目降低到我们预算的0.5根射线,效果就很不如意了。

幻灯片42.PNG

接下来看看如何在有限的预算下降低噪声,提升整体的品质,头一个要介绍的就是importance sampling(重要性采样)方案。

幻灯片43.PNG

再来回顾一下蒙特卡罗计算公式,蒙特卡罗公式是将针对某个函数的积分转化为该函数除以某个概率分布函数(除以再乘以,将被除部分跟原函数看成一个整体)的期望。

而重要性采样则是找到一个方便给出估计的概率分布函数,从而可以用较少的样本来实现更多样本(做均匀采样)时计算精度。

那么,我们要怎么才能得到这个概率分布函数,进而找到符合需要的射线方向呢?尤其是输入Radiance我们还不知道的情况下,下面来看看Lumen是如何解决这个问题的。

幻灯片44.PNG

针对光照的公式中,输入Radiance,可以从上一帧的屏幕空间的Radiance来获取(将像素,其实是当前待计算的probe,从当前帧reproject回到上一帧,基于对应位置的相邻四个probe进行采样并做插值计算),如果因为遮挡原因,屏幕空间的Radiance不可用(比如reproject得到的上一帧像素位置处于屏幕之外等,或者被遮挡了),则可以退化成世界空间的Radiance。

针对某个具体的probe,数据的获取就变得比较简单,给出采样的位置与方向,就行。

上图右下角的小图展示了某个probe对应的八面体贴图的数据,看得出来,输入Radiance主要分布在三块区域。

幻灯片45.PNG

某个probe对应的各个方向的BRDF可以直接从所有需要用到该probe的像素上获取,这部分数据本身已经存储到GBuffer中了,对于一个probe而言,会向四面八方发射射线找到交点,并获取对应GBuffer中的BRDF数据,对于一个处于平面上的probe而言,只有承载此probe的平面上有数据,因此BRDF表现为只有下半部分是有值的,上半部分是0(如上图右下小图所示)

幻灯片46.PNG

上面是将公式拆成两部分,分别做importance sampling,但是更准确的做法是将两者看成一个整体来做importance sampling。

幻灯片47.PNG

而上述思路就是Structured Importance Samplling的做法:

  1. 根据采样点的概率分布函数,为不同的区域分配不同的采样密度(变化快的区域应该获得更多采样密度?)
  2. 这个算法可以很好的兼顾全局的采样点分布,但是不足在于采样点的分布计算逻辑过于复杂,需要离线计算。

这里可以考虑借鉴该算法的分层阈值的思想。

幻灯片48.PNG

而上述思想跟Lumen用到的八面体贴图结构具有极高的契合度:

  1. 上图中,左边的小图展示的是incoming radiance跟brdf的乘积,其中白色区域表明该区域的光照作用大,应该提供更多的样本(应该按照数值来吗?而不是按照变化来?)
  2. 再看右边的小图,这里基于上面乘积的数值来对八面体贴图的各个tile做进一步细分
幻灯片49.PNG

在具体实现的时候,需要做一些处理:

  1. 在做具体的光线追踪的时候,需要记录下哪些tile需要trace几条射线,这里需要增加一个indirection table
  2. 追踪完成后,最终存储下来的,还是每个tile一个数值,这里需要做一个composite逻辑
幻灯片50.PNG

这里来对probe tracing的射线的分布做一个总结:

  1. 首先计算BRDF跟Lighting部分的乘积
  2. 根据乘积对各个像素(对应一个tile)进行排序
  3. 默认状态下是每个像素一根射线,不过这里会剔除掉PDF为0(BRDF为0)的像素,将这部分像素的射线分配给PDF更大的区域
幻灯片51.PNG

这个是采取上述importance sampling算法的效果对比。

幻灯片52.PNG

针对前面的算法,这里还做了两点改进:

  1. 放弃用乘积做PDF,而是直接改用BRDF,因为BRDF是准确的,而incoming radiance则是预测的,不准确,所以与其用一个不准确的数值,还不如直接用一个准确的数值
  2. 采用更激进的射线culling策略,将前面小于等于0的PDF剔除改为小于某个>0的值剔除,从而可以将射线分配给更重要的区域,进一步提高质量。为了避免因此导致的corner darken问题,这里在空间滤波的时候,还需要对这些像素赋予更低的权重
幻灯片53.PNG

结果对比

幻灯片54.PNG

同样的成本,更高的质量

幻灯片55.PNG

对importance sampling思路做一个总结:

  1. 基于上一帧的probe的光照数据来得到PDF(其实还算上了BRDF)
  2. 将射线的均匀分配做成以probe为单位,可以实现probe内部射线的更精细设计
幻灯片56.PNG

接下来看看如何对屏幕空间的Radiance做空间滤波的,即如何在低频的probe密度下,缓解采样的噪声

幻灯片57.PNG

为了以低成本实现大尺寸kernel的滤波效果,这里将滤波逻辑放到了Radiance Cache空间(基于probe)而非屏幕空间,即针对Radiance的Atlas进行读跟写。

从效果对比来看,3x3的radiance cache空间的滤波效果可以对应于48x48尺寸的屏幕空间滤波效果。

在滤波的时候,需要对相邻数据采样给一个权重,这里的权重仅仅是基于depth的差异给出,而不需要考虑相邻probe的法线差异,这是因为滤波的结果并不是直接作用于像素,而是写入到probe,不需要考虑接收者的法线匹配程度。

幻灯片58.PNG

由于Radiance Cache中存储了probe的采样方向与命中点的距离,因此在滤波的过程中,我们可以很方便的从相邻probe找到需要的数据,比如就找相邻probe中相同index的点(方向一致)来做滤波。

这里更重要的是如何舍弃掉那些容易导致问题的异常数据,这里的方法是将相邻probe上的‘匹配点’reproject到世界空间,之后连接当前probe与该点,并与当前probe待评估的方向做比较,当两者角度差异过大,即认为相邻probe的数据不可用。

上图中左边小图解释了这个策略,相邻probe(上方黄色虚线圆形)同方向(蓝色虚线)命中点连接到当前probe的方向与当前probe评估方向(白色实线)相差过远,则应该舍弃。

同时右边的图介绍了如果没有遮挡的情况下,可以借用相邻Probe远处的数据来做滤波,从而可以保障近景的阴影遮挡效果,同时也能采用远景的数据来滤波,消除噪声。

幻灯片59.PNG

看下效果,经过上述算法作用后,即使不添加额外的采样射线也能做到很好的降噪:如右侧的效果所示,结果会看起来更干净,不会有各种斑点

幻灯片60.PNG

但是这里会引入一个新的问题:会丢失一些遮蔽细节,比如毛巾褶皱以及毛巾与建筑墙体之间接触部分的阴影。

幻灯片61.PNG

之所以会有这个问题,是因为在复用相邻probe对应方向的数据的时候,前面的角度差异没有将相邻probe的数据设置为非法,而实际上相邻probe的射线命中的是很远的一个点,这个方向上没有遭遇遮挡,所以表现为漏暗(本应该偏暗的,实际偏亮)。

这个问题的解决方案是,在对相邻probe的射线结果进行复用之前,先将相邻probe的射线距离clamp到当前probe的射线命中点对应距离,之后再对该点做reproject(不是太理解这里的逻辑,不应该直接reject掉距离相差过大的数据吗?)

幻灯片62.PNG
幻灯片63.PNG

通过上述方法,可以较好的实现远景光照的降噪平滑,同时还能保障近景的阴影遮蔽效果。

幻灯片64.PNG

跟之前没做滤波前效果做对比

幻灯片65.PNG

接下来看看世界空间的Radiance Cache。

幻灯片66.PNG

远景光照有几个问题:

  1. 随着追踪距离的拉长,噪声会变得更多
  2. 长距离的trace会比较慢

此外也观察到远景光照变化频率较低,因此是可以缓存下来的,而且对远景光线的trace的结果也可以复用到屏幕空间上相邻的radiance上的,一鱼多吃。

幻灯片67.PNG

这里采取的方案是“The Technology of The Tomorrow Children”中的方案,类似于VLM,就是在规则grid上布点放置probe,因为是世界空间的,所以其上的异常更容易被隐藏。

幻灯片68.PNG

这里展示了屏幕空间probe跟世界空间probe是如何协同的,在屏幕空间采样的时候,会借用世界空间probe的数据来覆盖远景光照效果。

幻灯片69.PNG

这里会对世界空间的probe做同样的trace,得到数据之后,相邻的屏幕空间的probe会对世界空间probe的光照数据进行插值来得到远景的光照效果(不知道这里具体是怎么插值的?)

幻灯片70.PNG

下面用一条线来说明这个插值是怎么完成的

幻灯片71.PNG

世界空间的probe在计算各个ray的命中点光照的时候,需要跳过footprint范围,这是因为这个范围内的probe数据会采样当前probe的光照,造成自引用了,会增加误差。

幻灯片72.PNG

与之相对的是屏幕空间的probe的ray在采样的时候,是需要获取其自身的footprint之内的数据,同时还要覆盖到世界空间probe跳过的那部分(距离)区域

幻灯片73.PNG

但是,按照这种方式直接应用远景光照数据(如何应用?),如上图所示,会导致漏光问题,这是因为屏幕空间的射线跟世界空间的射线路径并不重合,其上的遮挡关系因而也就并不相同

幻灯片74.PNG

这里的解决方案比较简单,就是将屏幕空间的射线方向改为指向世界空间射线与footprint范围圆弧的交点(其实这个结果应该需要转化为两个分部,一个是从该点到世界空间probe位置的遮蔽,另一个是从世界空间probe到屏幕空间probe的遮蔽,不过这里就简化处理了)。

虽然这种做法不一定非常精准,但是能够很好的解决漏光的问题。

幻灯片75.PNG

世界空间probe只会摆放到屏幕空间需要的区域,这里是通过clipmap分布的多个grid来实现的。

在数据存储方面,会通过一个indirection将数据放置到一个大的atlas上面。

因为有clipmap的结构在,可以使得整体的世界空间probe的数据量不会太大。

幻灯片76.PNG

世界空间probe存储的格式跟屏幕空间probe是一样的,不同的是,由于世界空间probe的密度较低,因此相对应的,其分辨率会更高一些(屏幕空间是8x8,世界空间是32x32)。

幻灯片77.PNG

介绍了世界空间probe的摆放方式,接下来看看这些probe数据是如何更新的:

  1. 首先需要整理出哪些probe需要更新,这个是在屏幕空间probe采样计算的过程中统计并标记出来的,这些数据会被标记到clipmap的indirections中(类似于Virtual Texture的indirection吗,难道clipmap的数据还不是按照VLM的方式组织的,而是按需写入到atlas中?)
  2. 被标记为需要更新的probe会有两种状态,根据状态的不同,处理逻辑会有所不同:
  • 如果probe已经存在了,那么这时候会利用上一帧的数据作为基地(这是cache的意义),之后按需分配一些预算做新的trace,以跟随动态光源的变化(推测这里会标注更新时间,每次都只更新那些太久没更新的部分,或者还有其他更精细的策略)
  • 对于不存在的probe,直接做一次全量的trace更新
  1. 更新完成的probe按惯例做一次空间滤波降噪处理
幻灯片78.PNG

这个方案效果表现是不错,只是会导致性能上的毛刺:在相机快速移动,或者进入到一个新的区域(转角)的时候,就会导致大量的probe需要更新。

幻灯片79.PNG

这里的解决方案是限制每一帧的计算消耗:

  1. 给全分辨率probe(这是啥?)一个固定的更新预算
  2. 在进入新的区域导致需要的probe不存在的时候,会分配额外的预算做这部分的保障,不过这个保障只会用于低分辨率的更新(是probe的贴图分辨率低,还是probe摆放分辨率低?)
  3. 预算不足时,跳过那些因为光照动态变化而做的cache数据的更新
幻灯片80.PNG

同样的,世界空间的probe也会做重要性采样,不过由于这里拿不到精确的BRDF数据(没有上一帧?),所以只能通过相邻的屏幕空间的probe的BRDF进行累加来做估计。

有了BRDF之后,就可以调整trace tile(将probe分割成多个trace tile,这个跟之前屏幕空间probe的方式貌似并无区别?)的分辨率。

在相机周围会根据需要调高probe的分辨率,最高能到64x64,从而可以给出一个非常稳定的远景效果。

幻灯片81.PNG

再来看看远景部分的空间滤波逻辑,同样是借用相邻probe的数据来增加有效射线数据,不过这里的一个问题是,相邻probe之间的距离会远一些,因此这里不能直接给出两者之间区域是否被遮挡的正确假设,这个该怎么处理呢?

幻灯片82.PNG

为了避免直接假设为可见导致的漏光问题,这里还需要做一些处理。

理想情况下,可以直接基于相邻probe的射线的命中点与当前probe的连线,做一次遮挡检测来得到相对精确的结果,但是这样做消耗就高了。

这里实际测试发现,其实不用那么精确,只需要做一次粗糙的检测即可。

如上图所示,假设我们现在需要借用相邻probe(上)往右方向的射线结果,而使用之前需要判断命中点对当前probe是否可见,不过这里为了降低trace的成本,可以考虑直接借用当前probe上其他射线的trace命中点的depth,怎么用呢?就是沿着这条射线继续往前找到与待复用的射线的交点,判断两点的dpeth远近即可。

幻灯片83.PNG

这是最终的结果,其中2m范围内用的是屏幕空间probe的数据,之后用的就是世界空间probe的数据了。

幻灯片84.PNG

有了世界空间probe之后,可以极大的保障结果在时域上的稳定性。

幻灯片85.PNG

世界空间的probe数据还有一些其他的作用:

  1. 用于给屏幕空间的重要性采样提供输入(作为参考的incomming radiance)
  2. 用作头发的间接光数据(头发不用屏幕空间数据)
  3. 前向渲染中的半透物体也用这部分数据(头发不用屏幕空间数据)
  4. 还能用于提升multi-bounce的质量
幻灯片86.PNG

最后看看全分辨率工作,即怎样将probe数据应用到最后的像素上

幻灯片87.PNG

主要包含bent normal、插值以及时域滤波三个部分

幻灯片88.PNG

首先,光照计算公式中的incoming radiance已经在probe空间中完成了计算,不过计算结果是低分辨率的,还需要将之转换到像素空间,得到高分辨率版本,这个过程是通过插值实现的。

幻灯片89.PNG

插值解决的是数据采集的问题,我们最终需要的是来自多个方向的incoming radiance,而这就需要对相邻的radiance cache做多次采样,假设每个像素需要8个incoming radiance,每次需要对周边四个probe做插值,那就总共是32个采样,此处因为8个方向是分散的,采样的结果会带来很多的噪声。

为了消除噪声,这里尝试通过对mipmap数据进行采样(所谓的filtered importance sampling),不过这种方式因为mipmap实际上是覆盖较广范围的光照的累加,因此会导致self-lighting(即将semisphere后方的光照拉扯到前方,导致的一种漏光问题)

幻灯片90.PNG

前面出现噪声的原因,是多个采样方向的数据是离散的,彼此并不一致(coherent),因此这里的思路是将屏幕空间的probe转化为3阶的SH,因为SH中存储的数据是连续的平滑的。

之后像素采样的时候就从SH获取结果,这样拿到的结果就想对平滑,不会出现前面的噪声。

基于SH的incoming radiance,可以用低成本得到高质量的漫反射结果。

幻灯片91.PNG

上述方法不但适用于漫反射,同时还适合粗糙度较高的镜面反射。

对于粗糙度高的镜面反射,如果采用射线追踪的方式来计算,会消耗较多的算力与时间,而如果采用上述方式,则可以用非常低的成本得到相对不错的质量。

幻灯片92.PNG

具体做法是:

  1. 先从GGX lobe中算出重要性采样的方向
  2. 之后基于粗糙度采样对应屏幕空间probe的SH mipmap数据
  3. 由于SH的mipmap本身相当于做了一次滤波,因此采样结果本身就是做了一遍滤波

看效果还是不错的

幻灯片93.PNG

这里遇到的另一个问题是,降采样的tracing(这个是?)会导致contact shadow的丢失。

幻灯片94.PNG

这个问题,可以通过一个全分辨率的bent normal来解决,bent normal可以提供一个directional occlusion(AO)效果,用于补足contact shadow的缺失问题。

而bent normal可以通过一个快速的屏幕空间的trace来得到,trace的距离跟屏幕空间probe的距离有关(看起来是复用了屏幕空间probe上各个方向的trace depth数据)

幻灯片95.PNG

叠加了bent normal之后,我们就有了两套irradiance,其中:

  1. 屏幕空间probe提供的irradiance可以看成是远景的数据
  2. bent normal提供的则可以看成是局部的数据

两者之间的融合可以借鉴Mayaux 2018的Horizon-based indirect lighting策略

幻灯片96.PNG

有了bent normal处理之后,就能够找回之前丢失的contact shadow的效果了。

幻灯片97.PNG

地上的小物体看起来就不那么悬空了。

幻灯片98.PNG

接下来看看时域滤波。

前面说过,屏幕空间的probe的摆放位置,会有个每帧的抖动,且不是每个像素都会有个对应的probe,这就会带来效果的跳变,需要时域滤波来隐藏因此导致的噪声。

这里采取的是基于深度的rejection策略,而非基于neighbor的clamp策略。

基于深度reject输出的结果会相对稳定,但是带来的问题是不能快速响应光照的变化,具体表现为:移动物体背后会出现物体拖尾的残影,这是因为间接光更新频率不够导致。

幻灯片99.PNG

这里给出的解决方案是,在计算probe的trace命中点的时候,除了获取depth,还顺带获取velocity,这样就可以计算出快速移动的物体的probe所覆盖的区域。

幻灯片100.PNG

而对于这部分区域,就从慢速更新逻辑变更为快速更新逻辑。

即在这些区域里,噪声的处理更多会依赖于空间滤波而非时间滤波。

从效果上来看,是好一些,但是也并不那么完美。

幻灯片101.PNG

最后看下Final Gather的计算消耗。

幻灯片102.PNG

数据给出如上,PS5上总耗时接近4ms,计算大头还是在tracing部分。

幻灯片103.PNG

而如果我们相应调低probe的摆放分辨率,这个消耗也会相应下降。

幻灯片104.PNG

调低后的表现看起来也可以接受。

幻灯片105.PNG

来回切换对比的话,确实也是能看出差距的:

  1. 低配版本时域不够稳定
  2. contact shadow质量低
幻灯片106.PNG
幻灯片107.PNG

给了更多的效果展示(高配版本)。

幻灯片108.PNG
幻灯片109.PNG

高配版本在室内的表现也很不错。

幻灯片110.PNG
幻灯片111.PNG
幻灯片112.PNG

如果把单个像素的射线数目从0.5调高到2,品质还可以进一步提高,且消耗在2080Ti上为4ms

幻灯片113.PNG

Final Gather方案是Lumen方案的一部分,主要解决的是不透明物件的GI计算逻辑,透明物体的计算方案则采用的另一套。

幻灯片114.PNG

Final Gather方案同时支持软光追与硬光追。

幻灯片115.PNG

Lumen目前还有几个问题需要进一步优化:

  1. 环境遮蔽效果还有所不足
  2. 动态物件的GI更新与质量还不是太好
  3. 屏幕空间的Radiance Cache跟Surface Cache并没有结合起来
幻灯片116.PNG
幻灯片117.PNG
幻灯片118.PNG
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容