以前由于硬件限制,很多游戏的天空和地面颜色主要是用贴图模拟,近来硬件的发展,越来越多的游戏开始采用基于比较真实的大气散射模型来实时计算。很多文章的计算最终都将眼睛高度和角度作为参数,这里主要按照Sean O’Neil系列的方法来(其实它也是Nishita的改进)。

    原理可以简单归结为:光从大气外圈,散射之后进入眼睛。散射本身是一种衰减行为,由于星球通常离太阳比较远,所以我们近似认为在大气的"顶端"光照相等,这是一个重要的近似,让相机在任何地方的时候,都可以用同样的方式来计算,所以只要是往地面的路径,则散射增加,只要是玩天空的路径,则散射减少。

特定波长的光的亮度散射公式是:

       Iv(λ) = Is(λ) * K(λ) * F(θ,g) * PbPa(exp(-b/H0) * exp(-t(PPc, λ) – t(PPa, λ)))ds;

波长的影响主要由散射系数K(λ)表现,其中Is(λ)是太阳亮度,F(θ,g)是散射函数,主要有Rayleigh散射和Mie散射两种,Rayleigh散射主要是对空气中较小的粒子,主要散射较小波长的波,散射的能量对于光的入射比较对称,Mie散射主要散射空气中较大的粒子,主要散射较长的波,散射的能量在逆光部分比较少。


   
Rayleigh散射的近似能量分布

 


  
较大波长的Mie散射的能量分布

一般可以用Henyey-Greenstein函数来近似以上两种散射公式,即:

       F(θ,g) = (3 * (1 – g2) * (1 + cos2θ)) / (2 * (2 + g2) * (1 + g2 – 2 * g * cosθ))3/2;

角度θ为入射光与观察点之间的夹角。g是取值常量,当为0时公式近似为Reyleigh散射,当取-0.99左右时近似为Mie散射。

    亮度公式中的积分部分

PbPa(exp(-b/H0) * exp(-t(PPc, λ) – t(PPa, λ)))ds;

属于比较麻烦的地方,后面的-t(PPc, λ) – t(PPa, λ)表示的是光从大气顶部到眼睛的路径的衰减的和。积分曲线变化不是很大,所以对于积分用分段计算再取和来逼近积分的值,对于这里来说,并不是简单的求和,而是计算分段的中点(SamplePoint)的整个路径的optical depth,然后求通过这个点的路径的散射,再求根据长度比例求和。其中b是观察点高度在大气层中的比例,H0是大气中平均空气强度的那个高度在大气层中的比例,一般取0.25即可。t(PaPb, λ)即是表示单独在P1P2这条路径上的能量衰减。公式是

       t(PaPb, λ) = 4 * π * K(λ) * PbPaexp(-b/H0)ds;

PaPb分别为观察点和太阳离计算的采样点最近的大气位置,如果观察点在大气中则Pa就是观察点的位置。公式中的积分称为空气的视觉深度(optical depth),以前一般用预计算查找表来进行,这样不利于在GPU计算,gpugems2Sean O’Neil通过图形曲线发现在观察角度固定的时候,每个高度上(01)的积分值可以用地面的值(高度为0时候的值)乘于exp(-b/H0)来近似表示,但对于不同角度的缩放量曲线是一条类似指数函数的曲线,没有很好的表示方式,Sean O’Neil自己根据图形曲线用了一条逼近的公式来计算

Scale(ξ) = H0 * exp(-0.00287 + (1 - cos *ξ)(0.459 + (1 - cos *ξ)

(3.83 + (1 - cos *ξ)(-6.80 +(1 - cos *ξ)5.25))));

式中ξ表示观察角度,0为正上方,1为正下方,不过只有星球大气比例跟平均密度高度固定的时候才可以(0.25、0.25)。

    这样全部问题就可以放到GPU中解决了,对于每个顶点首先从camera到该点(PaPb)做一些采样(次数越多越能逼近,但计算量也越大),然后对每个采样点根据角度计算Scale(ξ),再根据高度计算optical depth以及t(PaPb, λ),然后根据各段采样长度就可以计算最终的强度。

    地面颜色的计算与此类似,不过增加了一次与原颜色的混合。

           Ig(λ) = Iv(λ) + Isrc(λ) * exp(-t(PaPb, λ));


    另外说下这个方法的问题,最大的问题是没有考虑多重散射,因此天空正上方的颜色实在太暗了,天空正中央看起来像是晚上,除非将hdr曝光调的极大,但这是不正确的,因此我自己在输出颜色的时候,对rayleigh散射输出的颜色乘了一个1+cos(cameradir,upvetor)*K(取2左右),看起来才正常一些,用这个因子来模拟多重散射。另外由于这个问题所以mie散射的影响太小了,在这里面仅仅只是对太阳产生一个光晕,导致太阳在天空中很大范围内移动的时候,天空的颜色几乎不变,如果有多重mie散射,太阳附近的亮度应该亮一些,而且会产生大小不一的光晕,目前也可以用角度差别来近似。