查看原文
其他

UE4新版大气实时渲染,这些创新点你知道多少?

Rcying 腾讯GWB游戏无界 2022-08-30


文丨Rcying

腾讯互动娱乐 工程师



本文为Sébastien Hillaire在Eurographics Symposium on Rendering 2020发表的论文A Scalable and Production Ready Sky and Atmosphere Rendering Technique的导读。据论文介绍,文章中的方法已经在Fortnite和UE4中实装。
论文作者Sébastien Hillaire应该很多人都会熟悉,之前在Frostbite的部门工作,Frostbite的体积渲染的Siggraph course(Towards Unified and Physically-Based Volumetric Lighting in Frostbite)就是他做的。现在到了Epic,负责的渲染相关工作。
这篇论文的提供的思路非常棒,简单来说使用了一定程度的近似,最后达到的效果为:
· 在保证效果跟path tracing的GT接近的情况下,比以前的预计算LUT,实时采样LUT(Bruneton08)的效率只稍微慢一点;· 有一定的LUT要素,但是LUT完全可以实时计算(大气参数可以实时修改,而且不是分帧更新之类的伪实时,是真的一帧搞定);· 在iPhone6s上可以在每帧1ms以内搞定(包括了LUT计算);· 做了一些近似假设,从而可以用数值方法计算所有高阶散射之和;而不是像之前的方法只计算一定的阶数就结束。在大气稀薄的情况下(高阶散射更加重要)效果提升非常明显。
总结一下,在效率、效果方面,可以说是把以前的方法拉起来全部A了。
[ 给老方法拍张遗照 ]

当然因为采用了一定的近似,在结论部分作者也提供了一个明显偏差的情况,即大气设置非常厚重(比地球密55倍)时会出现偏差。
但是即便存在这个缺点,该方法依然是比之前的方法存在大量的优越性的。
作者官网以及paper链接:https://sebh.github.io/publications/sebh.github.iohttps://sebh.github.io/publications/egsr2020.pdfsebh.github.io
我是第一次写这种论文导读类型的文章,欢迎指正。

在阅读这篇论文前,读者应该对老方法有一定的认识,如果能读懂Bruneton08的论文,本篇论文就非常好理解了。另外Bruneton在17年做了一个新的开源实现,使用了C++做验证实现(相同的glsl代码通过宏定义的方式编译成C++代码),并且代码做了非常多的注释(甚至有图),看了代码就基本上不用看那篇论文了。代码被作者托管在http://github.io上,可以直接看带图的注释,链接如下:
https://ebruneton.github.io/precomputed_atmospheric_scattering/ebruneton.github.io
知乎上也有大佬对大气散射相关原理的解析,这里我推荐一篇 @Lele Feng大佬的文章,最早的时候就是这篇文章让我明白了大气散射的原理:
https://zhuanlan.zhihu.com/p/36498679zhuanlan.zhihu.com
此外,论文中前4章也一定程度上说明了大气散射的原理,可以作为补充。
下一章开始说明Hillaire20的内容。因为本文主要为讲解论文的创新点,因此对于大气散射的原理、计算方法等不会做过多说明。这里假设读者已经完全理解了Scattering、Absorption、Rayleigh、Mie、Henyey-Greenstein、Phase Function、Transmittance等名词的意思,并且清楚大气散射的计算方法。如果发现自己看不懂,建议先阅读上述内容。
另外,开始之前我对一些论文中的名词做出本篇论文语境下个人的解释,可能存在偏差。

Extinction Coefficient、Absorption Coefficient、Scattering Coefficient:湮灭系数、吸收系数、散射系数。描述发生吸收或者散射事件的概率,与发生位置的气体浓度、性质有关。
Phase Function , :相位函数,描述光子在Scattering后朝着各个方向出射的概率,单位为  。论文中的  分别代表了Rayleigh散射、Mie散射和各向同性散射的Phase Function;Transmittance, :两个点a、b之间的透光度,通过积分路径上的Extinction Coefficient  得到; Scattering,:在某点x上,各个方向的入射光朝着某方向v出射散射强度之和。原论文中没有给这个量一个名字,但是后面会提到很多,我这里擅自叫它Scattering。并且按照Bruneton论文中的习惯记为  ;In-Scattering,  :在某点上朝着某方向到达另一点的散射强度,比Scattering多乘了Transmittance;Luminance,  :在路径上对In-Scattering做积分得到的光照强度,同时也是相机实际感受到的强度。 此外,  中的下标n表示了属于第几阶散射。

论文中介绍具体方法的第5章分别讲述了:
1.对大气散射特性的观察结论,大意即为天空中大部分的散射现象是低频的,除了靠近地平线部分会变得高频。2.Transmittance LUT的计算,其实就是老方法,不提3.Sky-View LUT4.Aerial Perspective LUT5.Multiple Scattering LUT 重点
// Sky-View LUT

在实际的渲染流程中,首先根据相机位置渲染至一张低分辨率的Sky-View LUT上,后期再合成到Render Target中。
Sky-View LUT中包含了当前相机位置接收到的各个角度的Luminance。计算时根据像素对应的视线方向直接做Raymarch得到结果(这里每一步Raymarch中的每一步采样都包含了所有阶的Scattering,如何计算看后文),论文中的例子如下图。

如上面提到,在靠近地平线部分大气散射会变得高频,因此UV的映射不是线性,而是靠近地平线部分会分配更多像素,具体公式在论文中写明了,这里就不复读了。
在论文中的测试,PC上只需要200*100的分辨率效果就足够。另外太阳本身是不会渲染在图里的,因为属于高频特征。会在后面再合成上去。
需要注意的是,这里跟Bruneton08已经不一样了,Bruneton08中不会有任何实时的Raymarch,都是LUT查找搞定;而这篇论文中,不管怎么样,在这一步实时Raymarch都是少不了的(其他的Raymarch步骤可以看情况跳过),这也是相比老方法会稍微慢一点的原因。
// Aerial Perspective LUT

除了背景的大气,相机和远处物体之间的大气散射也是画面的重要组成部分。不过其实也是老方法了。
1.分割Camera Frustum(和Cluster Rendering中的分割一样),计算每个格子到Camera方向的In-Scattering和Transmittance,保存在Volume Texture中;2.在Volume Texture的z轴方向上根据Transmittance累加In-Scattering,使得每一个单元格保存的是该单元格到Camera的Luminance;3.在Opaque渲染之后,做一次Post Processing,采样上述的Volume Texture,对场景中的物体添加Aerial Perspective;4.Transparent物体渲染时在VS中采样Volume Texture添加Aerial Perspective。
// Multiple Scattering LUT

这篇论文中最神奇的地方就是Multiple Scattering的计算了。在Bruneton08中,Multiple Scattering的计算需要一阶一阶网上算,比如1阶Scattering、Luminance,然后计算2阶Scattering、Luminance……
论文作者在计算Multiple Scattering时,认为高阶散射非常低频,从此出发使用了一些简化,包括:
1.大于等于2阶的散射,将Rayleigh散射和Mie散射视为为各向同性的散射。2.计算某点的大于2阶的Scattering时,认为该点周围任意一点的Illuminance与其相同。

第1点的意思是,对于1阶散射,我们认为Rayleigh、Mie散射是正常的,但是在之后的阶数,我们认为相位函数对于任何方向都是相同的值  。于是我们计算Multiple Scattering LUT时,LUT的维数就少了2维。
第2点,原文的描述有点神秘,写的是“Neighbouring points”,在问了作者之后其实意思是在计算某点的Scattering时,空间中任意一点的某一阶的Scattering都视作和该点相同。因为参与计算的点需要与该点有Line of sight,从整个地球的视角来看确实是Neighboring points。这一条近似看上去是非常粗略的,但是it works。
实际流程中,Multiple Scattering LUT的计算可以一个pass输出到一张32*32的2D LUT完成。只需要2D的原因是因为上述简化中我们使用了各向同性Phase Function,不需要存储相机跟光照方向的关系,只需要存储不同高度、不同光照角度的情况即可。
在pass中,论文中提到,首先计算  ,这里的是在LUT当前像素代表的点上,对整个球面做Phase Function * Luminance的积分得到,按照本文开头提到的习惯记为 。具体计算方式公式为如下:
其中  计算部分的  为地面的反射光,  为阳光到达x点的照度。这里的  是一个单位值的阳光照度,实际的照度数值是在最后乘上去的。
// 转移函数 
第二步,在上述计算完毕后,计算当前像素的Transfer Function  (不是真的Function,对于当前计算的像素来说是一个固定的数值),可以将  转换为更高阶的Scattering。个人认为这是这篇文章中第二Amazing的地方。论文中直接给出了Transfer Function的计算方法:使用  ,有:这里可能会对  的计算稍微感到迷惑。其实这是在上述1、2点简化的情况下,将原本的计算散射的式子做了化简的结果。我们可以简单地推导一下。首先是传统的  的计算方法:根据简化1、2,我们认为在计算当前像素时,  在任意一点都是固定的数值, 式可以改写为:即  ,再代回去(6),两边同除  ,将  替换为  ,得:细心的读者可以发现,相比(2)式,(7)式中省去了地面反射。这里我咨询了作者,作者表示确实是忽略了多重散射照射到地面形成反射的贡献。不过保留的话也没办法化简出这么一个 。好在这一部分贡献并不是很多,对最终结果影响不大。
// 多重散射结果
有了  ,我们就可以计算任意  了。接下去就是论文第一Amazing的地方。
根据数学公式,因为  小于1.0(论文中直接说的,我没有算过),我们可以直接得到无穷阶数的  之和:最后,就可以将  写入二维的LUT中保存了。之后采样这张LUT,就可以得到2阶及以上阶数的散射之和。

[ 论文中的Multiple Scattering LUT的例图 ]
论文中提到,在PC或者iPhone6s上,该LUT的计算都是在64个方向上做Raymarch,每个方向20个step得到,乍一看很多,但是LUT大小只有32*32,问题不大,论文后面给出的测试iPhone6s上也只消耗0.12ms。
// 最终计算
根据上文,我们可以得到任意点的所有阶数的Scattering之和,包括一阶Scattering和高阶Scattering之和(  为太阳光对该点的直接贡献):在计算某个方向上的Luminance时,一路积分过去就行。这也是上文中Sky-View LUT中每个像素的Luminance计算方法。到此整个算法的流程就已经讲完了。下图是论文中的效果图,包括了和Path tracing、Bruneton的对比。



这篇论文中,作者通过做出一些对结果影响不大的简化假设,大幅度地降低了Multiple Scattering计算的消耗,并且使用数值方法计算无穷阶Scattering之和变为可能。这毫无疑问是需要非常多的实践经验、数学直觉和大胆尝试才能做到的,让人不得不佩服。
此外在工程方面,这篇文章也是尽可能压缩了各个LUT的大小以得到最好的效率,包括200*100的Sky-View LUT、32*32的Multiple Scattering LUT等等。尽管最后写成论文的就是表中的几个数字,但是这些数据毫无疑问是经过了大量试验得到的。最终在iPhone6s上能跑到1ms以内更是amazing。并且不需要任何的3D贴图,就在VS-PS里直接算就完事儿了。
本文只是挑选了我认为最具亮点的一部分来讲。论文原文中还有许多细节内容,如根据高度自动切换Raymarch方法和Sky-View LUT等,建议有兴趣的读者详读一遍。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存