UE4新版的大气实现性能上完全碾压基于预计算大气散射方法,它是在论文A Scalable and Production Ready Sky and Atmosphere Rendering Technique的基础上实现的。
从篇幅就可以看出来这篇论文的主要看点在Multi Scatter部分,几乎是剩下三个LUT篇幅和的两倍。
Multiple scattering LUT
其实这个新的方法只是比之前的预计算大气散射多想了一步近似。
为了保持符号相同,后文都用预计算大气散射论文的公式进行分析:
L
=
L
0
+
(
R
+
S
)
L
0
+
(
R
+
S
)
[
(
R
+
S
)
L
0
]
+
.
.
.
.
.
.
L=L_0+(R+S)L_0+(R+S)[(R+S)L_0]+......
L=L0+(R+S)L0+(R+S)[(R+S)L0]+......
=
L
0
+
R
[
L
∗
]
+
S
[
L
∗
]
=L_0+R[L_*]+S[L_*]
=L0+R[L∗]+S[L∗]
看到这个公式,我们容易会有下面的想法,只要知道R+S不就知道全部阶数了吗?
L
1
=
(
R
+
S
)
L
0
L_1=(R+S)L_0
L1=(R+S)L0
L
2
=
(
R
+
S
)
L
1
L_2=(R+S)L_1
L2=(R+S)L1
但是R和S里面还包含了L,R+S没法分离,新方法做的第一步就是把R给忽略,不考虑地面的反射。只剩下S了,S里面还有L,这是个递归的过程,怎么终结这个递归,很简单,把S里面的L设为一个固定数值就可以了。
所以这个方法总结下来就两点,忽略地面反射并设n-1阶的L是个固定的值。
近似1:
L
n
=
R
[
L
n
−
1
]
+
S
[
L
n
−
1
]
L_n=R[L_{n-1}]+S[L_{n-1}]
Ln=R[Ln−1]+S[Ln−1]
L
n
=
S
[
L
n
−
1
]
L_n=S[L_{n-1}]
Ln=S[Ln−1]
近似2:
L
n
=
S
[
L
n
−
1
]
=
∫
T
(
∫
Ω
σ
s
×
p
h
a
s
e
(
)
×
L
n
−
1
d
w
)
d
x
L_n=S[L_{n-1}]=\int T(\int_{\Omega}\sigma_s\times phase()\times L_{n-1}dw)dx
Ln=S[Ln−1]=∫T(∫Ωσs×phase()×Ln−1dw)dx
L
n
=
S
[
L
n
−
1
]
=
L
n
−
1
∫
T
(
∫
Ω
σ
s
×
p
h
a
s
e
(
)
d
w
)
d
x
L_n=S[L_{n-1}]=L_{n-1}\int T(\int_{\Omega}\sigma_s\times phase()dw)dx
Ln=S[Ln−1]=Ln−1∫T(∫Ωσs×phase()dw)dx
L
n
=
S
[
L
n
−
1
]
=
L
n
−
1
∫
T
(
∫
Ω
σ
s
×
1
4
π
d
w
)
d
x
L_n=S[L_{n-1}]=L_{n-1}\int T(\int_{\Omega}\sigma_s\times \frac{1}{4\pi}dw)dx
Ln=S[Ln−1]=Ln−1∫T(∫Ωσs×4π1dw)dx
L n = L n − 1 f m s L_n=L_{n-1}f_{ms} Ln=Ln−1fms
再回到最初的式子
L
=
L
0
+
(
R
+
S
)
L
0
+
(
R
+
S
)
[
(
R
+
S
)
L
0
+
.
.
.
.
.
.
L=L_0+(R+S)L_0+(R+S)[(R+S)L_0+......
L=L0+(R+S)L0+(R+S)[(R+S)L0+......
L
=
L
0
+
(
R
+
S
)
L
0
+
L
2
+
f
m
s
L
2
+
f
m
s
2
L
2
+
.
.
.
.
.
.
L=L_0+(R+S)L_0+L_2+f_{ms}L_2+f_{ms}^2L_2+......
L=L0+(R+S)L0+L2+fmsL2+fms2L2+......
运用几何级数
L
=
L
0
+
(
R
+
S
)
L
0
+
L
2
(
1
+
f
m
s
+
f
m
s
2
+
.
.
.
.
.
.
)
L=L_0+(R+S)L_0+L_2(1+f_{ms}+f_{ms}^2+......)
L=L0+(R+S)L0+L2(1+fms+fms2+......)
L
=
L
0
+
(
R
+
S
)
L
0
+
L
2
(
1
/
(
1
−
f
m
s
)
)
L=L_0+(R+S)L_0+L_2(1/(1-f_{ms}))
L=L0+(R+S)L0+L2(1/(1−fms))
这样就是整个论文的核心了,后文可以不看了,只需要计算出下面的式子就可以了。
f m s = ∫ T r ( ∫ Ω σ s × 1 4 π d w ) d x f_{ms}=\int Tr(\int_{\Omega}\sigma_s\times \frac{1}{4\pi}dw)dx fms=∫Tr(∫Ωσs×4π1dw)dx
是不是很无脑。
大气模型
Raylei和Mie的海拔密度分布公式如下:
d
r
(
h
)
=
e
−
h
8
k
m
d^r(h)=e^{-\frac{h}{8km}}
dr(h)=e−8kmh
d
m
(
h
)
=
e
−
h
12
k
m
d^m(h)=e^{-\frac{h}{12km}}
dm(h)=e−12kmh
臭氧是天空呈现天蓝色的重要因素,它对散射没有贡献,仅仅吸收Light。
d
o
(
h
)
=
m
a
x
(
0
,
1
−
∣
h
−
25
∣
15
)
d^o(h)=max(0,1-\frac{|h-25|}{15})
do(h)=max(0,1−15∣h−25∣)
const float densityMie = exp(Atmosphere.MieDensityExpScale * viewHeight);
const float densityRay = exp(Atmosphere.RayleighDensityExpScale * viewHeight);
const float densityOzo = saturate(viewHeight < Atmosphere.AbsorptionDensity0LayerWidth ?
Atmosphere.AbsorptionDensity0LinearTerm * viewHeight + Atmosphere.AbsorptionDensity0ConstantTerm :
Atmosphere.AbsorptionDensity1LinearTerm * viewHeight + Atmosphere.AbsorptionDensity1ConstantTerm);
论文假设Rayleigh没有吸收,只有散射。
σ
a
r
=
(
5.802
,
13.558
,
33.1
)
\sigma_a^r=(5.802,13.558,33.1)
σar=(5.802,13.558,33.1)
σ
s
r
=
(
0
,
0
,
0
)
\sigma_s^r=(0,0,0)
σsr=(0,0,0)
σ
t
r
=
σ
a
r
+
σ
s
r
\sigma_t^r=\sigma_a^r+\sigma_s^r
σtr=σar+σsr
接着乘上海拔密度
σ
a
r
∗
=
d
r
(
h
)
\sigma_a^r*=d^r(h)
σar∗=dr(h)
最后把Raylei、Mie和臭氧对应系数相加。
s.scatteringMie = densityMie * Atmosphere.MieScattering;
s.absorptionMie = densityMie * Atmosphere.MieAbsorption;
s.extinctionMie = densityMie * Atmosphere.MieExtinction;
s.scatteringRay = densityRay * Atmosphere.RayleighScattering;
s.absorptionRay = 0.0f;
s.extinctionRay = s.scatteringRay + s.absorptionRay;
s.scatteringOzo = 0.0;
s.absorptionOzo = densityOzo * Atmosphere.AbsorptionExtinction;
s.extinctionOzo = s.scatteringOzo + s.absorptionOzo;
s.scattering = s.scatteringMie + s.scatteringRay + s.scatteringOzo;
s.absorption = s.absorptionMie + s.absorptionRay + s.absorptionOzo;
s.extinction = s.extinctionMie + s.extinctionRay + s.extinctionOzo;
Sky-View LUT
用RayMarching进行Sample的消耗还是很大。作者观察到Earth-like sky的效果其实是非常低频的,所以论文将Sky的渲染结果放大一个低分辨率的LUT(192x108)中,在后续的进行upsample以减少消耗。
1.对于给定视点,文章将Sky渲染到一张latitude-longitude坐标的纹理上,上半部分是天空,下半部分是地面,中间是地平线,同时由于在接近地平线的部分是相对高频的信息,文章对latitude 进行非线性映射,使得接近地平线的部分存储更多信息。
v
=
0.5
+
0.5
sign
(
l
)
⋅
l
π
/
2
v=0.5+0.5\operatorname{sign}(l)\cdot\sqrt{\frac{l}{\pi/2}}
v=0.5+0.5sign(l)⋅π/2l
float coord = (acos(viewZenithCosAngle) - ZenithHorizonAngle) / Beta;
coord = sqrt(coord);
uv.y = coord * 0.5f + 0.5f;
2.同时由于分辨率较低以及非线性映射的原因,sun disk在最后与Sky-View LUT进行合成。
output.Luminance = float4(SkyViewLutTexture.SampleLevel(samplerLinearClamp, uv, 0).rgb + GetSunLuminance(WorldPos, WorldDir, Atmosphere.BottomRadius), 1.0);
Aerial Perspective LUT
和Sky-View LUT类似,为了减少消耗,文章将各位置的in-scattering and transmittance进行计算并存储到一张32x32x32的Texture中,可以覆盖32km的场景。
在最后计算时根据这一点的pixel得到对应Pos,对LUT查找得到最终结果。
上一篇文章的基于预计算的方法需要RayMarching来Sample,本文计算时通过实例渲染和GS来进行绘制,相当于计算32x32x32次,效率提升很明显。
const float4 AP = Weight * AtmosphereCameraScatteringVolume.SampleLevel(samplerLinearClamp, float3(pixPos / float2(gResolution), w), 0);