以前由于硬件限制,很多游戏的天空和地面颜色主要是用贴图模拟,近来硬件的发展,越来越多的游戏开始采用基于比较真实的大气散射模型来实时计算。很多文章的计算最终都将眼睛高度和角度作为参数,这里主要按照Sean O’Neil系列的方法来。
特定波长的光的亮度公式是:
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;
属于比较麻烦的地方,好在积分曲线变化不是很大,所以对于积分用分段计算再取和来逼近积分的值。其中b是观察点高度在大气层中的比例,H0是大气中平均空气强度的那个高度在大气层中的比例,一般取0.25即可。t(P1P2, λ)是一个出射函数,表示光在P1P2这条路径的能量减少。公式是
t(P1P2, λ) = 4 * π * K(λ) * ∫PbPaexp(-b/H0)ds;
Pa和Pb分别为观察点和太阳离计算的采样点最近的大气位置,如果观察点在大气中则Pa就是观察点的位置。公式中的积分称为空气的视觉深度(optical depth),以前一般用预计算查找表来进行,这样不利于在GPU计算,gpugems2中Sean O’Neil重新提出了一种方法来近似计算这个积分,他用图形观察的时候发现在观察角度固定的时候,每个高度上(从0到1)的积分值可以用地面的值(高度为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为正下方。
这样全部问题就可以放到GPU中解决了,对于每个顶点首先从camera到该点(即Pa到Pb)做一些采样(次数越多越能逼近,但计算量也越大),然后对每个采样点根据角度计算Scale(ξ),再根据高度计算optical depth以及t(P1P2, λ),然后根据各段采样长度就可以计算最终的强度。
地面颜色的计算与此类似,不过增加了一次与原颜色的混合。
Ig(λ) = Iv(λ) + Isrc(λ) * exp(-t(PaPb, λ));
