【Unity大气渲染】Unity Shader中实现大气散射(半成品)
3316 9
实名

通过了实名认证的内容创造者

发布于 2023-8-28 11:57:14

您需要 登录 才可以下载或查看,没有账号?注册

x
写在前面

这是之前在做天空盒的时候同步写的分析博文,结果后面写到一半就忘了继续了,这里先贴出当时写的半成品,有小伙伴问我怎么做的,这里只能尽力把之前的半成品先放出来了(写得很乱,勿怪orz),后面有机会会完善好的!希望能帮到大家~

前置知识学习

【Unity大气渲染】关于单次大气散射的理论知识

关于卡通渲染的大气散射
因为我想实现的是跟Unity 卡通渲染 程序化天空盒 昼夜变化,而作者实现过程的文章Unity 卡通渲染 程序化天空盒里说了只是想实现一个日出日落的效果,大概做了如下两点简化:

只计算了Mie散射
光学距离部分只计算了 image.png ,省去了计算 image.png
既然不是基于物理的,那实现大气散射里整理的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);
    }
}






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取值参考:



————————————————
版权声明:本文为CSDN博主「九九345」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://www.csdn.net/?spm=1001.2100.3001.4476

image.png

评分

参与人数 3元素币 +12 活跃度 +8 展开 理由
wwwxajhcom + 2 + 2 给力
angelanany... + 6 + 1 路过留个脚印
amenjuzi + 4 + 5 【喜欢】看到这么好的帖如同回到初恋的年代!

查看全部评分

要好好学习哦
使用道具 <
yukigo  发表于 2023-8-28 12:15:40  
2#
赞赞赞
回复 收起回复
使用道具
mobai1517  发表于 2023-8-28 16:30:57  
3#
真是 收益 匪浅
回复 收起回复
使用道具
仰空越梦  发表于 2023-8-28 20:55:06  
4#
不错不错,支持哦
回复 收起回复
使用道具
元素界王神  发表于 2023-8-31 12:54:36  
6#
很有意思!
回复 收起回复
使用道具
Mis丨畏美  发表于 2023-9-10 10:15:25  
7#
回复 收起回复
使用道具
晚安少年  发表于 2023-9-11 09:38:35  
8#
感谢分享
回复 收起回复
使用道具
qq_欣欣_wUj  发表于 2023-9-12 14:28:15  
9#
讲的不错
回复 收起回复
使用道具
肆酒清歌a  发表于 2023-9-19 08:43:57  
10#
厉害
回复 收起回复
使用道具
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 返回顶部 返回列表