写在前面
这是之前在做天空盒的时候同步写的分析博客,结果后面写到一半就忘了继续了,这里先贴出当时写的半成品,有小伙伴问我怎么做的,这里只能尽力把之前的半成品先放出来了(写得很乱,勿怪orz),,后面有机会会完善好的!希望能帮到大家~
前置知识学习
【Unity大气渲染】关于单次大气散射的理论知识
关于卡通渲染的大气散射
因为我想实现的是跟Unity 卡通渲染 程序化天空盒 昼夜变化,而作者实现过程的文章Unity 卡通渲染 程序化天空盒里说了只是想实现一个日出日落的效果,大概做了如下两点简化:
- 只计算了Mie散射
- 光学距离部分只计算了,省去了计算
既然不是基于物理的,那【Unity大气散射】GAMES104:3A中如何实现大气散射里整理的LUT优化方式等等内容就派不上用场了,但也无妨!多了一次学习~
那么接下来计算会尽量参考着这两点去做~ 很大部分的过程参考【实战】从零实现一套完整单次大气散射_四 - 知乎
分析原神的日出
截图来自【原神】好美,原神一天中天空的变化_哔哩哔哩_bilibili
早晨,是地平线的颜色Bloom超突出,伴随着缓缓升起的Mie散射的日出效果:
随着太阳升起,地平线Bloom褪去,留下明显的Mie散射:
日出结束,进入白天时间,太阳始终伴随着一个炫光(猜测是直接叠加了一个炫光图案),加上有随太阳高度角变化的镜头光晕效果:
关于地平线Bloom和太阳炫光、镜头光晕部分这里不提,先实现如此的Mie散射。
1 整体思路
我们需要模拟出人站在地面上看向天空的过程,其实就是人眼发出射线与天空相交的过程,这个过程还须判断是否被物体挡住,挡住了返回黑色;没挡住就调用一个单次散射函数,返回散射的值。
1.2 构建坐标系
- 首先要有一个明确的坐标系:这里将地球顶部作为世界原点建立坐标系,换句话来说,就按照Unity的世界坐标就行!
- 人眼此时就是A了,那么在Unity中相机的位置就是A的世界坐标,从A点出发发出射线与天空相交于点B,这时射线AB就是整个模拟过程中的“视线”
下图是解释图,我觉得画的不错hhh
1.2 函数1:射线与球体求交
数学原理没什么好说的,随便找了个解释原理的文章:
射线与球体/三角面片求交、重心坐标、插值_csu_xiji的博客-CSDN博客_射线求交
函数返回值
返回的是±t,rayOrigin+t*rayDir就能得到两个交点啦。
//-------------------------------------
// RaySphereIntersection
//-------------------------------------
float2 RaySphereIntersection(float3 rayOrigin, float3 rayDir, float3 sphereCenter, float sphereRadius)
{
rayOrigin -= sphereCenter;
float a = dot(rayDir, rayDir);
float b = 2.0 * dot(rayOrigin, rayDir);
float c = dot(rayOrigin, rayOrigin) - (sphereRadius * sphereRadius);
float d = b * b - 4 * a * c;
if (d < 0)
{
return -1;
}
else
{
d = sqrt(d);
return float2(-b - d, -b + d) / (2 * a);
}
}
1.3 计算的部分
就是主要的单次散射函数,主要有两个关键:
- 计算
2 计算T(PA)
参考开头的简化方法,我们不计算,那么整体就是按照下式计算了!其中,海平面的散射系数是已知的,相位函数和光学距离(累加相对大气密度)都需要计算。
2.1 函数2:计算大气密度
//---------------------------------------------------------------------------
// GetAtmosphereDensity
//---------------------------------------------------------------------------
void GetAtmosphereDensity(float3 position, float3 planetCenter, float3 lightDir, out float dpa, out float dpc)
{
// ρ(h)= exp(-h/H)
float height = length(position - planetCenter) - _PlanetRadius;
dpa = exp(-height / _DensityScaleHeight);
// we don't computer the D(PC)
dpc = 0;
}
其中,_DensityScaleHeight为大气厚度。
2.2 函数3:相位函数
//---------------------------------------------------------------------------
// ApplyPhaseFunction
//---------------------------------------------------------------------------
void ApplyPhaseFunction(inout float scatterMie, float cosAngle)
{
// only Mie
float g = _MieG;
float g2 = g * g;
float phase = (1.0 / (4.0 * PI)) * ((3.0 * (1.0 - g2)) / (2.0 * (2.0 + g2))) * ((1 + cosAngle * cosAngle) / (pow((1 + g2 - 2 * g * cosAngle), 3.0 / 2.0)));
scatterMie *= phase;
}
2.3 函数3:计算总散射
//---------------------------------------------------------------------------
// IntegrateInscattering
//---------------------------------------------------------------------------
float4 IntegrateInscattering(float3 rayStart, float3 rayDir, float rayLength, float3 planetCenter, float3 lightDir, float sampleCount)
{
float3 stepVector = rayDir * (rayLength / sampleCount);
float stepSize = length(stepVector);
float2 prevDPA = 0;
float prevTransmittance = 0;
float densityPA = 0;
float densityCP = 0;
float localDPA = 0;
float scatterMie = 0;
GetAtmosphereDensity(rayStart, planetCenter, lightDir, localDPA, densityCP);
densityPA += localDPA * stepSize;
prevDPA = localDPA;
// local Inscattering, densityCP=0
float Transmittance = exp(-(densityCP + densityPA) * _ExtinctionM) * localDPA;
prevTransmittance = Transmittance;
[loop]
for (float i = 1.0; i < sampleCount; i += 1.0)
{
float3 p = rayStart + stepVector * i;
GetAtmosphereDensity(p, planetCenter, lightDir, localDPA, densityCP);
densityPA += (prevDPA + localDPA) * (stepSize / 2.0);
Transmittance = exp(-(densityCP + densityPA) * _ExtinctionM) * localDPA;
scatterMie += (prevTransmittance + Transmittance) * stepSize / 2;
prevTransmittance = Transmittance;
prevDPA = densityPA;
}
// phase function
ApplyPhaseFunction(scatterMie, dot(rayDir, -lightDir.xyz));
float3 lightInscatter = _ScatteringM * scatterMie;
return float4(lightInscatter, 1);
}
其中,为了方便用户调试,cpp里对于Mie散射的S项和T项分别提供可控参数MieScatterCoef和MieExtinctionCoef:
Vector4 MieSct = new Vector4(2.0f, 2.0f, 2.0f, 0.0f) * 0.00001f;
_ExtinctionM = MieSct * MieExtinctionCoef;
_ScatteringM = MieSct * MieScatterCoef;
也就是_ExtinctionM是计算所取的S项海平面处的散射系数,_ScatteringM是计算所取的M项海平面处的散射系数,都是由cpp直接传入,MieSct取值参考: