找回密码
 立即注册
查看: 826|回复: 0

[未分类] 基于物理的大气散射 in Unity URP

[复制链接]
发表于 2020-11-26 16:58 | 显示全部楼层 |阅读模式
前言

最近一直在研究大气散射,同时也想验证一下Unity的URP是否好用。目前开始能见到一些效果了,如下:
项目效果截图


打算开文章写一下实现细节,实现到哪写到哪,本文将会不定期更新。当前Github工程如下:
要求:
    Unity2019.3.4Universal Rendering Pipeline 7.3.1
大气散射在游戏中的重要性不必多说,不管是营造真实的氛围,还是掩盖细节都不可或缺,往往一个好的全局氛围可以马上提升游戏整体的表现力。
说到大气散射,就必须先了解其理论和模型。这里不作太多解释,有更详细的资料可以学习,我个人十分推荐先从Zucconi的这一系列教程 (共8篇)开始:
Zucconi 的 Volumetric Atmospheric Scattering[1]
关于公式的推导过程,可以参考冯乐乐的这一篇,每一步都讲解的非常清楚:
冯乐乐的基于物理的大气渲染 [2]
仔细看这几篇包括里面引用的论文,并自己推导一遍公式,就可以有一个比较完善的认识了。


这里我简要仅简要总结一下散射理论和模型。
散射理论

根据大气层中粒子的直径分布,散射又分为三种:Rayleigh散射(粒子直径远小于波长)、Mie散射(粒子直径和波长相当)和 Geometry散射(粒子直径大于波长)。
如果不考虑云层的情况,那么大气中散射基本以Rayleigh和Mie散射为主。
Scattering Equation 散射函数用于描述入射光散射到特定方向上的大小,以Rayleight散射为例,其散射函数为:


其中 为光的波长; 为散射角度,即入射光与视线方向的夹角; 表示当前海波高度; 为空气的折射率; 为标准大气压下的分子数密度; 表示粒子密度百分比。
在这个公式里面除了密度百分比和角度以外,其他都是可以提前确定的常数。
粒子密度百分比在海平面上为1,随着高度的上升而指数下降,可以用指数函数来拟合:


其中表示Scale Height,是一个常数。
并且可以看到Rayleigh散射与光的波长的4次幂的成反比,波长越小(靠近光谱的紫色端)的光被散射得越厉害。这也是为什么白天的时候天空为蓝色,因为蓝光在大气里散射后充满整个天空。黄昏的时候天空呈现红色,因为此时阳光需要穿过更厚的大气层,在到达人眼之前,大多数蓝光都被散射到其他方向,剩下来的就是红光了。
Rayleigh Scattering 来源:https://en.wikipedia.org/wiki/Rayleigh_scattering
Mie散射和光的波长无关,但它在某些方向散射更强,它是造成太阳周围形成光晕的主要原因。
显然,对散射函数作球面积分,就得到一次散射过程丢失的总能量,也叫Scattering Coefficient散射系数,记作:


常数部分放一起,就成了常数乘密度函数,显然散射系数和方向是无关的。
将散射函数除以散射系数,就得到了Phase Function(相位函数),他表示散出去的能量中,在每个方向的分布比例,Rayleigh和Mie的相位函数如下:


左:Rayleigh Phase Function。右:Mie Phase Function,从左往右代表g=0.75,0.5,0,-0.5,-0.75的分布
大气散射模型

大气散射模型分为两种:Single Scattering和Multi Scattering。
Single Scattering,即只考虑太阳光经过一次散射后抵达我们的眼睛。
Multi Scattering,考虑光线多次散射过程的模型。显然这更符合现实世界的情况,但下一级的散射取决于与上一级散射的结果,也就是说这是一个递归方程,实现起来并不太容易。Eric Bruneton给出了一种方法 [3]通过使用LUT把当前一级散射结果存起来,下一级散射从上一级LUT读取结果再计算,能够视线实时渲染Multi Scattering,这也是目前主要实现Multi Scattering 的方法。
由于第二次散射以上的能量较少,对最终视觉的影响程度有限,所以目前Single Scattering还是一种相对实现简单效果也不会差很多的模型,本文实现的也是Single Scattering:
来源:https://www.alanzucconi.com/2017/10/10/atmospheric-scattering-1/
假设我们在点A看向点B,Single Scattering指的是路径上的每一个点,比如P0,P1,P2,每个点都会收到太阳穿过大气入射的光线,并将光散射到点A方向后被我们看到,这个过程我们叫做In Scattering。对路径上所有的点的In Scattering的光都累加(P0,P1,P2...) 起来(积分),就形成了我们看到的点B经过散射效应后的样子。
现在我们只看路径上的一个点,如点P0,阳光在抵达点P0的这段路程,会因为散射发生一定的损耗,这叫做Out Scattering,损耗之后还剩下的能量比例,由Beer–Lambert law(比尔-朗伯定律)决定:


指数部分的术语,叫做Transmittance ,或者Optical Length(光学长度),其中 表示散射系数 为大气密度函数。我们把这一项记作T,即:

对于点P来说,太阳光从宇宙空间出发,穿过地球大气层,抵达P(此过程发生Out Scattering),用公式就表示为:


接着,抵达点P的光还要散射到我们的视觉方向,也就是乘以一个散射函数:

然后,这部分光抵达我们的眼睛,路径上还需要经过Out Scattering:

最后,我们要把视线上每个点贡献的都加起来,也就是对路径AB积分,最终得到:


这是Single Scattering的一般式,所谓一般是指大气密度处处不均匀。这时候,光学长度T是一个积分,因此整体很难得到简单的解析式。可以暴力遍历路径上的点计算积分,即Ray Marching,也就是Alan的教程中[1]使用的方法。优化方法就是提前把一些数据比如光学长度计算到Look Up Table中。
多级散射模型

这里顺带提一下Multi Scattering。在前面的推算里,我们简化光线在大气中只进行了一次散射,而精确的情况是光线会散射多次。
多级散射是一个递归定义,即当前i+1级的散射,等于对路径AB上,每一点p的上一级散射总和(公式中蓝色部分)的积分。


对于每个点p来说,它的散射总和,是球面上所有方向散射结果之和。在Single Scattering中,我们的简化结果就是忽略了这个球面积分。
而最终散射的结果就是把所有级别的散射加起来:


要求解这个方程就更复杂了,在实时渲染领域使用,需要进行某些简化,借助LUT存中间结果。更多信息可以参照Eric Bruneton在其论文中[3]的描述,并且附带了完整的源代码。
空中透视模型

在实际人站在地球表面的情况,视线触及的范围(不包括天空)相比地球的规模可以忽略不记,因此我们可以把地表的大气密度看作是常数,这样光学长度就只和距离有关了,经此假设我们可以化简一般式为:


如果同时考虑Rayleigh和Mie散射,最终得到In scattering的公式为:


由于我们假设了场景规模相较于地球忽略不计, 其实就等于Main Light了,是一个可以提前确定的常数。公式中唯一的变量是s,即物体到摄像机的距离。


考虑在近地面观察一个物体,物体本身发出的光是要经过Out Scattering才能达到我们的眼睛的,于是有:
把C的乘数称作Extinction项,In即In Scattering项。即:

本文的实现
用Single Scattering实现的In Scattering作为天空,对地表的物体运用大气密度恒定的空中透视模型。
参考文献:
Rendering Parametrizable Planetary Atmospheres with Multiple Scattering in Real-Time[4]
Rendering Outdoor Light Scattering in Real Time[5]
参考项目:
SlightMad的Atmospheric Scattering for Unity 5[6]
Unity的Atmospheric Scattering in The Blacksmith[7]
SlightMad这一篇是完全全程用不均匀密度的Single Scattering计算的。他的完成度很高,并且提供RayMarching和预计算LUT两种方法,很适合学习。
Unity的这一篇则是典型的空中透视模型。并且它的参数设置比较偏向美术,初次学习看不太懂,但是效果非常不错。此外他还额外叠加了“指数高度雾”,用于弥补大气密度恒定带来的问题。


根据空中透视模型的公式:
Extinction项:
In Scattering项:
叠加结果:
Sun

在In Scattering中渲染太阳的方法,可以叠加多一次Mie Scattering的方法实现[4],Phase Function中的g需要选用较高的值。
上,g=0.99;下:g=0.98
Light Shafts

Light Shafts,或者叫体积光,由于存在阴影的关系,光在抵达视线的过程中还需要考虑遮挡的问题。
上:不考虑阴影。下:在阴影处形成体积光
一般的实现方法就是在屏幕空间,从Depth Map重建像素的世界坐标,再从世界坐标到摄像机进行Ray Marching N次,每个点转到Shadow map空间后判断可见性,叠加起来后就得到Light Shafts。
这种方法最大的问题就是效率,因为屏幕上的每个点都要计算N次。如果N过小,效果不太好。
一种优化的思路就是进行Down Sample,然后通过Dithering抖动采样,并进行模糊处理,可以在N较小的情况下得到不错的结果。
上:每像素64次采样。中:每像素16次采样。下:每像素16次Dithering采样
不过目前还有一些效率更高的方法,一个是针对采样方法的优化[8]。
还有另外一种通过构造网格实现的方法[9]。
以后有机会实现的话再研究吧。
Look Up Table

根据实际需要。如果是静态的天空盒则很容易,把In Scattering烘焙到Cube Map中作为天空盒:
利用Reflection Probe烘焙成HDRI CubeMap


如果说需要动态的天空,那么为了优化需要将In Scattering计算到LUT中。In Scattering和3个参数有关:视线方向,太阳方向和高度,需要3维的LUT。如果摄像机的高度不会改变,那么可以排除掉高度只需要2维的LUT。这就取决于实际情况了。
确定主平行光和环境光

随着太阳角度的变化,也会跟着变化,并且环境光也会跟着变化。
为了保证整个环境的物理一致性,我们需要控制当前的Main Light Color和Ambient。
这些数据可以利用Compute Shader提前计算出来。
由于我们已经假设了地表上大气密度恒定,因此其实就是阳光经过Out Scattering抵达摄像机处的结果。
对于环境光,我们需要对In Scattering做辐射度积分:


其中N表示法线,可以直接用world up表示。
用模特卡罗方法求解这个积分,利用UnityEngine.Random.onUnitSphere可以快速生成随机的单位方向向量。
在Compute Shader中计算之后,要把结果读取回CPU并序列化起来,以便在后续渲染中直接使用。借助一张临时的Texture2D,从Render Texture中拷贝内容:
RenderTexture currentActiveRT = RenderTexture.active;
RenderTexture.active = src;
dst.ReadPixels(new Rect(0, 0, dst.width, dst.height), 0, 0);
RenderTexture.active = currentActiveRT;
缺点

缺陷在于本来大气密度是随着海平面升高呈指数降低的,这会造成山脚下雾气浓厚的效果,或者也叫“指数高度雾”。在比较大规模的场景中,或者从山上眺望的场景中,恒定的大气密度就会比较假。
Unity的Black Smith[7]专门额外叠加了一个指数高度雾来解决此问题。
关于Universal Render Pipeline

鉴于URP灵活的架构,整体实现来说还是比较方便的。不过目前的URP还是有不少小毛病,我自己就遇到了好几个莫名其妙的问题,并且都是确定或还未确定的bug,等待官方的进一步提高稳定性吧。
关于自定义后处理。目前是还不能嵌入到默认的Volume 系统中。需要自行实现一个ScriptableRenderFeature,可以参考官方的 [Universal Rendering Example]: (https://github.com/Unity-Technologies/UniversalRenderingExamples)
参考


  • ^abZicconi17https://www.alanzucconi.com/2017/10/10/atmospheric-scattering-1
  • ^Feng18https://zhuanlan.zhihu.com/p/36498679
  • ^abEric08https://hal.inria.fr/inria-0028
  • ^aElek09https://old.cescg.org/CESCG-2009/papers/PragueCUNI-Elek-Oskar09.pdf
  • ^Hoffmanhttp://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.192.1514&rep=rep1&type=pdf
  • ^SlightMad16https://github.com/SlightlyMad/AtmosphericScattering
  • ^abUnity15https://blogs.unity3d.com/cn/2015/05/28/atmospheric-scattering-in-the-blacksmith/
  • ^Yusov13https://software.intel.com/sites/default/files/managed/b9/1d/gdc2013-lightscattering-final.pdf
  • ^Hoobler16https://developer.nvidia.com/sites/default/files/akamai/gameworks/downloads/papers/NVVL/Fast_Flexible_Physically-Based_Volumetric_Light_Scattering.pdf

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-12-25 15:18 , Processed in 0.072177 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表