[Rendering] 基于物理的大气渲染

# 要解决的问题


以下我们推导单次散射(single scattering)的气渲染模型,即光线从太阳发出过、只经过一次散射被改变方向后射入我们的眼睛。


下图显示了我们观察大气时的观察路径AB,我们想知道B点的大气颜色:


来源:https://ift.tt/2A7GiMZ


这个过程实际上就是对视线路径AB上每一点的光照贡献进行一个积分,下面我们把问题一步步拆分。


## 问题一:P点的光照是什么?


由于我们关心的是单级散射,那么唯一的一次散射发生在路径AB上的每一点P,那么该点的光照强度Ip要怎么求呢?它可以理解成是,从P点出发沿着光照方向与大气边缘的交点为C,从C点的光照(我们认为大气层边缘的光照就是太阳发出的光照)经过路径CP衰减后到达P点的光照:


来源:https://ift.tt/2A7GiMZ

用公式表示就是:

其中,T项衰减系数(Transmittance),它表示在某段路径上的对光照的衰减程度。该公式也可以被认为是零级散射(zero scattering),即不考虑任何散射事件、直接考虑经过衰减后光强。


## 问题二:在P点发生了什么?


我们已知到达P点的光照强度为Ip,那么光线在P点经过一次散射后、经路径PA其他大气粒子的衰减后,最终到达人眼的光照强度Ipa为:

其中,λ是波长,θ是图中所示的光线入射方向和观察方向的夹角,hP点的高度。通俗解释下,S项散射系数(Scattering),它表示此次散射事件中有多少光被反射到了θ方向上,T项是衰减系数(Transmittance),它表示在PA路径上的对光照的衰减程度。它们之间是有联系的,简单来说正是因为大气对光线不断发生散射(S项)才导致经过一定路径后光线会发生衰减(T项)。接下来对每一项进行解释。


(1) 散射系数: S(λ,θ,h)


散射系数和粒子的大小和折射率有关,这里就得解释一下大气粒子的分类。我们知道大气中有很多不同种类的大气分子/粒子,一般在大气渲染模型里把它们分成两大类。一种是小分子,例如氮气和氧气分子等,另一种是大粒子,例如各种尘埃粒子。之所以要进行分类是因为它们对光线有着不一样的行为(这里忽略对光的吸收,只考虑散射):

  • 小分子:指大小远小于光线波长的粒子。小分子对光的散射在前后方向上分布比较均匀,通常会使用Rayleigh散射(Rayleigh Scattering)对它们进行建模。由于这些分子大小比波长还要小很多,因此光的波长也会影响Rayleigh散射的程度。
  • 大粒子:指大小远大于光线波长的粒子。大粒子在发生散射的时候会把更多的光散射到前向,通常会使用Mie散射(Mie Scattering)对它们进行建模。由于光的波长相较于这些粒子大小来说是可以忽略的,因此我们认为Mie散射跟光线波长无关。


下面以Rayleigh散射为例,解释S(λ,θ,h)的含义。这里直接给出公式,在Rayleigh散射模型中,散射到θ方向上的比例为:

其中,λ是光的波长,n是粒子折射率,N是海平面处的大气密度,ρ(h)是高度h处的相对大气密度(即相对于海平面的密度,可以理解成h处真正的大气密度与海平面处大气密度的比值,因此它在海平面处值为1,随着h增加不断减小),我们一般会使用指数函数对它的真实曲线进行数学拟合(是一种近似拟合,不要和后面提到的指数函数弄混):

其中,H是大气的整体厚度。


可以看出Rayleigh散射大致和波长的4次幂的成反比,波长越小(越靠近紫光)的光被散射得越厉害。所以白天的时候天空为蓝色,因为蓝光在大气里不断被散射,黄昏的时候天空会变红,因为相比于白天,阳光此时要穿越更厚得多的大气层,在到达人眼之前,大多数蓝光都被散射到其他方向,所以剩下来的就是红光了。


(2) 衰减系数:T(PA)


对于大气粒子对光的散射,在传播路径PA上光线会继续衰减。S(λ,θ,h)关心的是被反射到某一特定角度上的光线比例,那么现在我们就需要考虑经过一次散射后,在入射方向上还"剩下来"多少光。


我们先来看一次衰减。假设I0在经过一次散射后损失了β比例的能量,则剩余的能量I1为:

那么经过一段传播路径PA后,余下的能量为:

  • 简单情况:如果β为定值,我们可以直接通过微积分得到衰减一段距离x后的剩余能量:
  • 复杂情况:复杂情况是β是与波长λ和高度h都相关,即不能简单地当成定值。此时,我们就需要用积分来表示了:


那么问题是,β是什么?在简单情况下,也是很多游戏里雾效的实现方法中,就拿一个类似雾效密度的参数来代替β。在复杂情况下,我们就需要考虑真实的β是如何计算的。


之前我们一直在讨论的S(λ,θ,h)是在某方向上的散射系数,那么β就是在所有方向上的总散射系数。我们还是以Rayleigh散射为例,如果我们对Rayleigh散射的S(λ,θ,h)做球面积分,得到总散射系数β(λ,h)为:


终于,我们的公式长度缩短成了两项,不用再看密密麻麻的符号了!上式可以理解成,β(λ)是海平面处的散射系数,随着高度增加,散射强度也不断减小。因为大气相对密度ρ(h)不断减小。据此,我们可以改写T(PA)部分,可以看出,β(λ)其实只跟波长相关,在传播路径上不需要进行积分,因此可以提到积分外部:


我们可以进一步改写其中的积分项。对比简单情况下的公式,积分项对应的其实就是路径长度x,而这部分积分可以理解成光学距离——Optical Depth


同样,我们也可以改写S(λ,θ,h)部分:


其中,P(θ)表示在这些被散射的能量中,有多少比例被反射到了θ方向上,它有一个专有名字——相位函数(Phase Function),也被称为Scattering Geometry。所以对于Rayleigh散射来说,它的相位函数就是:


至此,我们简单推导了S项T项,这样一开始的公式就可以写成:


总结起来,公式表示的含义就是,计算观察路径AB上某一点P的光照贡献:

  1. 光线从太阳出发,到达大气边缘的C
  2. 经过路径CP上的衰减到达P点(0级散射
  3. P点发生一次散射,将一部分光散射到了观察方向上(1级散射
  4. 这部分光又经过AP路径上的衰减最终到达了我们的眼睛


## 问题三:路径AB之间发生了什么?

来源:https://ift.tt/2A7GiMZ


P只是AB上的某一点,我们需要对路径AB上的每一点进行积分:


其中涉及到了散射系数β(λ)、相位函数P(θ)、光学距离D(PA)等。


## 小结


至此,我们完成了单级散射模型的简单推导。但实际情况要复杂得多,在到达人眼之前,光线已经在大气里被散射了无数次,而更高一级的散射总是可以靠它前一级的散射来定义,即是一个递归定义


上式的含义是,在A点、从观察方向v、光线入射方向s贡献的第i+1级散射(左侧)等于,对观察路径AB上的每一点P的贡献度进行积分:

  1. P点处发生最后一次散射,把光线从积分方向v'散射到观察方向v上(β(v,v')),然后经过T(PA)衰减后到达A
  2. 在最后一次散射前,对之前发生在P点、从方向v'、光线入射方向s贡献的第i级散射做球面积分,表示从各个方向v'贡献的第i级散射


回顾我们之前对单级散射的推导过程,其实就是i=0的情况。对于第0级散射来说,由于v'不等于s的方向来说,第0级散射都为0,所以我们省略了公式里做球面积分部分。而在A点、从观察方向v、光线入射方向s最后的光照结果应该是把所有级的散射都加起来:


如果是离线渲染,理论上我们可以求得上述公式的精确解。但对于实时渲染来说,第0级和第1级散射都是可以较为快速的求得精确解的,而更高级的散射要计算起来就非常划不来了。在早期的大气渲染资料(例如GPU Gems 2)里,都是对第0级和第1级散射的求解和渲染。直到2008 Precomputed Atmospheric Scattering的提出,我们也可以实时计算更高级别散射的近似解了。


# 大气渲染模型


具体应用到游戏渲染里,我们一般会把大气渲染分为两个部分:一是天空背景的渲染,即skybox,二是大气透视的渲染,也是我们俗称的大气雾效。在渲染的时候需要考虑光线入射方向s上的遮挡(阴影),通常使用传统的shadow map就可以了。这样我们就可以得到god ray效果。


## 天空背景


我们之前推导的公式可以直接拿来渲染天空盒,这里就不重复了。多说一句,第0级散射可以理解成对"sun disk"的渲染。


## 大气透视


大气透视(aerial perspective)是渲染场景里物体距离摄像机远近的很重要的效果,也是所谓的雾效。与渲染天空盒略微不同的是,我们还需要考虑反射地表颜色等。


依然是考虑在A点、沿着观察路径AB接收到的光照。这部分光照可以分为两个部分:一是B点反射的地表颜色Rb经过路径AB衰减后的光照,也就是第0级反射,二是路径AB上由于散射贡献的光照:

Textinction就是我们之前推导的T(AB)Iinscattter则是之前推导的多级散射模型。


## 优化


核心是使用LUT,例如可以T(AB)存到一张2D纹理里。


## 例子:简化模型


大部分游戏的视角只会在地表附近,因此可以使用简化模型来渲染大气雾效。简化模型有两个最明显的特征:

  • 使用直线距离d直接代替光学距离D(AB)
  • 基于上一点,我们可以使用恒定的散射系数β


(1)Unity的Exponential Fog


最简单的例子应该就是Unity的Exponential Fogdocs.unity3d.com/Manual)了,公式表示就是:


其中的exp部分就是Textinction,即使用恒定的散射系数的简化模型。但后面计算Iinscattter的部分其实是不准确的,可以说是"不怎么基于物理"。


(2)The Blacksmith中的大气散射


Unity的The Blacksmith给出了大气散射部分的代码(blogs.unity3d.com/cn/20assetstore.unity.com/pa)。这部分以后再补吧。


# 参考资料


推荐阅读顺序即为排序顺序:


Unity有很多插件的实现都是基于2008年的Precomputed Atmospheric Scattering,例如uSky(assetstore.unity3d.com/#)、Azure(assetstore.unity.com/pa)等,可以参考和优化。



来源:知乎 www.zhihu.com
作者:Lele Feng

【知乎日报】千万用户的选择,做朋友圈里的新鲜事分享大牛。 点击下载

没有评论:

发表评论