Zephus 发表于 2022-7-27 11:58

Unity 卡通渲染 程序化天空盒

分享一下学习过程中的碎碎念,最终效果视频指路:
程序化天空盒,实际上就是写个shader,把这个球从内部展现出来。其实也没什么可说的,搞明白贴图如何正确去映射到球面上就行了。

[*]日月绘制
[*]天空颜色
[*]星星、银河绘制
[*]大气散射
[*]云
日月绘制
可参考下面这篇:
太阳绘制根据方向光的方向计算距离就行,
                float sunDist = distance(i.uv.xyz, _WorldSpaceLightPos0);
                                float sunArea = 1 - sunDist / _SunSize;
                                sunArea = smoothstep(_SunInnerBoundary,_SunOuterBoundary,sunArea);
但是月亮就不能只画区域了吧,我想将月亮贴图搞上去,但天空球这个uv不太好直接用,强行用uv.xz贴上去那么只在中心点的效果比较好,稍微动一下offset就发现没法看:


天空球的uv是按照一个在原点的球体映射的,在球体的面上,上下顶点的地方切面平行于xz平面,采样完看图像不会被明显拉伸,在靠近边缘的地方采样则不能很好对应。既然在原点映射比较好,那么就把月亮固定在原点,但我们要根据光线方向旋转它,那在方向光的空间搞就好了,把天空球的uv转换到方向光的空间,转换矩阵我用脚本从transform去拿:
Matrix4x4 LtoW = sun.localToWorldMatrix;

shader:
float3 sunUV = mul(i.uv.xyz,LToW);
float2 moonUV = sunUV.xy * _MoonTex_ST.xy + _MoonTex_ST.zw;
float4 moonTex = tex2D(_MoonTex, moonUV);
float3 finalMoonColor = (_MoonColor * moonTex.rgb * moonTex.a) * step(0,sunUV.z);到这日月就可以了


天空颜色
比较简单,搞几个颜色lerp就行
float sunNightStep = smoothstep(-0.3,0.25,_WorldSpaceLightPos0.y);
//DAY NIGHT
float3 gradientDay = lerp(_DayBottomColor, _DayMidColor, saturate(i.uv.y))*step(0,-i.uv.y)
                  +lerp(_DayMidColor, _DayTopColor, saturate(i.uv.y))*step(0,i.uv.y);
float3 gradientNight = lerp(_NightBottomColor, _NightTopColor, verticalPos);
float3 skyGradients = lerp(gradientNight, gradientDay,sunNightStep);

//HORIZONTAL
float horWidth = lerp(_NightHorWidth, _DayHorWidth,sunNightStep);
float horStrenth = lerp(_NightHorStrenth, _DayHorStrenth,sunNightStep);
float horLineMask = smoothstep(-horWidth,0,i.uv.y) * smoothstep(-horWidth,0,-i.uv.y);
float3 horLineGradients = lerp(_NightHorColor, _DayHorColor,sunNightStep);

星星、银河
星星贴图搞上去,这张图是我从资源商店搞的,但我现在还不知道怎么去生成或是它有什么专门的名字,如果有知道的请告诉我一下。





搞张密度低点的

随便搞张noise控制范围及闪烁,亮度不要拉太高,密度也不要拉太大,不然会有纹理抖动。


//STAR
float4 starTex = tex2D(_StarTex,i.uv.xz*_StarTex_ST.xy + _StarTex_ST.zw);
float4 starNoiseTex = tex3D(_StarNoise3D,i.uv.xyz*_StarNoise3D_ST.x + _Time.x*0.12);

float starPos = smoothstep(0.21,0.31,starTex.r);
float starBright = smoothstep(0.613,0.713,starNoiseTex.r);

float starColor = starPos*starBright;
starColor = starColor*galaxy.r*3 + starColor*(1-galaxy.r)*0.3;
float starMask = lerp((1 - smoothstep(-0.7,-0.2,-i.uv.y)),0,sunNightStep);starColor用了galaxy.r,一个银河mask里面的星星亮一点,周围暗一点。
银河系绘制首先我们要去....看原神的延时摄影!!(bushi)
不难看出有一个银河系的范围,加两次采样noise,那就很简单了,自己PS画一下范围得了(其实为了方便调用程序生成SDF改的):


r通道是整体,g通道划定中间的一个范围,r-g就能做出中间那一块缺的效果。



就这里

//GALAXY
float4 galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw + float2(0,_Time.x*0.67));
float4 galaxy = tex2D(_GalaxyTex,(i.uv.xz + (galaxyNoiseTex-0.5)*0.3)*_GalaxyTex_ST.xy + _GalaxyTex_ST.zw);
float4 galaxyColor =(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.2,1-galaxy.g);

galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw - float2(_Time.x*0.5,_Time.x*0.67));
galaxyColor +=(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.3,1-galaxy.g);然后我们就可以看到美丽的星空拉


大气散射(一些改动只是个人理解,请斟酌斟酌)
先跟着大哥做一遍大气散射,然后再讨论后面的:
只要跟着这个大哥做一遍,就不难发现,Rayleigh是解释你看到的太阳光的颜色,天为什么是蓝色的,夕阳为什么是红色的,Mie就解释了日出日落halo。
既然做卡通渲染,那就只保留Mie好了,我只想做个日出日落,但是要做天空盒还要按照大哥说的用raymarching计算D(CP) + D(PA)那太浪费了,对于基于物理的渲染有预计算的方法节省时间,视频



王希GAMES104

不过既然是做卡通渲染,就不需要物理正确性了,自然有很多做法。
SkyScattering部分代码:
void ComputeOutLocalDensity(float3 position, float3 lightDir, out float localDPA, out float DPC)
{
        float3 planetCenter = float3(0,-_PlanetRadius,0);
        float height = distance(position,planetCenter) - _PlanetRadius;
        localDPA = exp(-(height/_DensityScaleHeight));

        DPC = 0;
        //DPC = ComputeDensityCP(position,lightDir);
        /*
        float cosAngle = dot(normalize(position - planetCenter), -lightDir.xyz);
        DPC = tex2D(_TestTex,float2(cosAngle,height / _AtmosphereHeight)).r;
        */
}
float4 IntegrateInscattering(float3 rayStart,float3 rayDir,float rayLength, float3 lightDir,float sampleCount)
{
        float3 stepVector = rayDir * (rayLength / sampleCount);
        float stepSize = length(stepVector);

        float scatterMie = 0;

        float densityCP = 0;
        float densityPA = 0;
        float localDPA = 0;

        float prevLocalDPA = 0;
        float prevTransmittance = 0;
       
        ComputeOutLocalDensity(rayStart,lightDir, localDPA, densityCP);
       
        densityPA += localDPA*stepSize;
        prevLocalDPA = localDPA;

        float Transmittance = exp(-(densityCP + densityPA)*_ExtinctionM)*localDPA;
       
        prevTransmittance = Transmittance;
       

        for(float i = 1.0; i < sampleCount; i += 1.0)
        {
                float3 P = rayStart + stepVector * i;
               
                ComputeOutLocalDensity(P,lightDir,localDPA,densityCP);
                densityPA += (prevLocalDPA + localDPA) * stepSize/2;

                Transmittance = exp(-(densityCP + densityPA)*_ExtinctionM)*localDPA;

                scatterMie += (prevTransmittance + Transmittance) * stepSize/2;
               
                prevTransmittance = Transmittance;
                prevLocalDPA = localDPA;
        }

        scatterMie = scatterMie * MiePhaseFunction(dot(rayDir,-lightDir.xyz));

        float3 lightInscatter = _ScatteringM*scatterMie;

        return float4(lightInscatter,1);
}这里面我直接把D(CP)砍掉了,一是因为它计算太耗时了,二是因为对我最终的效果影响不大。


不过为什么会这样?我自己的理解是mie散射的相位函数其实是更倾向于直接沿着原方向进行散射,而rayleigh倾向于朝着不同方向,并且对不同波长影响也差别巨大。那么当我不计算D(CP)时,对mie的影响是比较小的,对于rayleigh散射影响较大,散射出的不同颜色也会变得相同。




用后处理基于物理大气散射看一下?


最后,在天空盒里计算,脚本控制下颜色
//Mie scattering
float3 scatteringColor = 0;

float3 rayStart = float3(0,10,0);
rayStart.y = saturate(rayStart.y);
float3 rayDir = normalize(i.uv.xyz);

float3 planetCenter = float3(0, -_PlanetRadius, 0);
float2 intersection = RaySphereIntersection(rayStart,rayDir,planetCenter,_PlanetRadius + _AtmosphereHeight);
float rayLength = intersection.y;

intersection = RaySphereIntersection(rayStart, rayDir, planetCenter, _PlanetRadius);
if (intersection.x > 0)
        rayLength = min(rayLength, intersection.x*100);

float4 inscattering = IntegrateInscattering(rayStart, rayDir, rayLength, -_WorldSpaceLightPos0.xyz, 16);
scatteringColor = _MieColor*_MieStrength * ACESFilm(inscattering);

插一嘴:
拿到后处理效果能更好一些,我们用了raymarching,自然能想到体积雾体积光什么的对吧,其实之前计算density就是计算的大气雾,最后再乘以mie相位函数才有了最终效果,那么在计算的时候加上高度雾,就能做到地面雾效和mie散射结合的结果。如果仔细研究下PC上原神的日落日出可以发现地面雾效和散射的结合很好,如果飞得高一些会发现mie散射会有变化,大气散射和高度雾应该是结合在一起去做的。手机上的原神就少了地面雾的散射变化只有mie,可能是为了节约性能?
用后处理raymarching做下高度雾和大气散射:




这个好像没啥好说的吧,原神是在崩三经典云的基础上加了SDF做出消散的效果。
资源准备:
参考文章,请:
在最后这个兄弟实现的基础上,生成图片后再改成多张SDF混合lerp,(忘了哪篇文章了说的好像有问题,就这个混合地方被误导了挺久)
例如4张进行lerp:
    for (int y = 0; y < height; ++y) {
      for (int x = 0; x < width; ++x) {
            int t1 = sdf1[(y * width + x) * 3];
            int t2 = sdf2[(y * width + x) * 3];
            int t3 = sdf3[(y * width + x) * 3];
            int t4 = sdf4[(y * width + x) * 3];

            int result = 0 ;


            if (t1 < 128 && t2 < 128 && t3 < 128 && t4 < 128)
            {
                result = 0;
            }
            else if (t1 > 128 && t2 > 128 && t3 > 128 && t4 > 128)
            {
                result = 255;
            }
            else
            {
                for (int i = 0; i < 200; i++)
                {

                  float weight = (float)i / 200;
                  if (weight < 0.33333)
                  {
                        float w1 = weight * 3;
                        result += ((1 - w1) * t1 + w1 * t2) < 128 ? 0 : 255;
                  }
                  else if (weight < 0.66666)
                  {
                        float w2 = (weight - 0.33333) * 3;
                        result += ((1 - w2) * t2 + w2 * t3) < 128 ? 0 : 255;
                  }
                  else
                  {
                        float w3 = (weight - 0.66666) * 3;
                        result += ((1 - w3) * t3 + w3 * t4) < 128 ? 0 : 255;
                  }

                }
                result /= 200;
            }
            

            if (result < 0)
            {
                result = 0;
            }
            else if (result > 255)
            {
                result = 255;
            }
            output =result;
      }
    }最终结果就是这张图:



阈值图


先PS画好几个阶段的效果,这块就是美术的东西了,不会美术,画得不好!!!!,最后全扔给Visual Studio生成SDF后混合去。混合后的图片应该塞到原图的alpha通道里去,不过我调不好效果就没搞。这几张图可以私我b站发给你。
到这里资源准备还没完,我要做的是一个天空盒,可是这个云怎么放上去啊,采样采上去也不是不行,但是我总不能一个一个云去采样把,那消耗太大了。那在unity里面一个面片一个面片摆上去?这么多面片对应的云形状不是一样的吧?而且消散的程度不能是一样的吧?那岂不是每片云我都得整个Material去控制,太麻烦了。还有一个问题,怎么让rim边缘光正确对应到太阳周围?
这么多问题,把所有的云整成一个多面片的模型就得了,调整uv0让每个面片的云对应图片中的不同片云,再整个uv1将云片平铺,将uv1对应到世界坐标x轴旋转角、y轴旋转角,消散信息放到顶点色。
Maya搞一下:



面片



uv1



uv0

代码准备:
云贴图应该做成的几个通道:r暗部1、g暗部2、b高光、aSDF。
云的亮部=1-暗部之和
边缘光部分要结合SDF,图没做好用了一堆lerp、smooth,美术苦手:
v2f vert(a2v v)
{
    v2f o;
    o.pos = UnityObjectToClipPos(v.vertex);

    o.uv.xy = v.texcoord.xy;

    o.uv1 = v.uv1;

    return o;
}

float computeCloudSize(float4 lerpTex, float4 cloudTex,half _LerpCtrl)
{   
    half cloudStep = 1- _LerpCtrl;
    half cloudLerp = smoothstep(0.95,1,_LerpCtrl);
    half a1 = smoothstep(saturate(cloudStep-0.1),cloudStep,lerpTex.r);

    return lerp(a1,cloudTex.a ,cloudLerp);
}

fixed4 frag(v2f i):SV_Target
{
    //local(cloud sets center) to World
    float4x4 LToW = float4x4(_LtoW0,_LtoW1,_LtoW2,_LtoW3);

    //距离太阳范围
    #define PI 3.1415926
    float3 objLightDir = mul(_WorldSpaceLightPos0.xyz,LToW);
    float2 cossin0 = normalize(objLightDir.xz);
    float theta = acos(cossin0.x)*step(0,cossin0.y) + (2*PI - acos(cossin0.x))*step(cossin0.y,0);
    theta /= 2*PI;


    float temp = abs(i.uv1.x - theta);
    float xDis = temp * step(temp,0.5) + (1-temp)*step((1-temp),0.5);
    float yDis = abs(i.uv1.y - (objLightDir.y*0.37+0.49));
    //根据距离计算边缘光
    _RimColor += (1-smoothstep(0,0.008*_RimSize,xDis*xDis + yDis*yDis))*_RimStrenth;

    //根据距离太阳位置进行颜色的lerp
    float colorLerp = (smoothstep(0,0.1*_ColorLerpSize,xDis*xDis + yDis*yDis))*_ColorLerpStrenth;

    _BrightColor = lerp(_BrightColor,_BrightColor1,colorLerp);
    _DarkColor = lerp(_DarkColor,_DarkColor1,colorLerp);
    _SecDarkColor = lerp(_SecDarkColor,_SecDarkColor1,colorLerp);
    _RimColor = lerp(_RimColor,_RimColor1,colorLerp);

    fixed4 cloudEdgeNoise = tex2D(_CloudEdgeNoise,i.uv.xy*_CloudEdgeNoise_ST.xy + _CloudEdgeNoise_ST.zw*_Time.x);
    float2 cloudNoiseUV = i.uv.xy + float2(cloudEdgeNoise.x-1,cloudEdgeNoise.y-0.25)*0.02;
    cloudNoiseUV = cloudNoiseUV * _CloudTex_ST.xy + _CloudTex_ST.zw;

    fixed4 cloudTex = tex2D(_CloudTex,cloudNoiseUV);
    fixed4 cloudLerpTex = tex2D(_CloudLerpTex,cloudNoiseUV);

    /*
    half3 finalColor = _BrightColor * (1 - cloudTex.r - cloudTex.g)
                        + _DarkColor * cloudTex.r
                        + _SecDarkColor * cloudTex.g
                        + _RimColor * cloudTex.b;
    */
    half finalAlpha =computeCloudSize(cloudLerpTex,cloudTex,lerp((sin((_Time.x + _LerpTimeOffset)*_LerpSpeed)*0.78+0.78),1,_LerpCtrl));
   
    half3 finalColor = _BrightColor * (1 - cloudTex.r - cloudTex.g)
                        + _DarkColor * cloudTex.r
                        + _SecDarkColor * cloudTex.g
                        + (_RimColor-0.7) * (cloudTex.b*0.5 + 1 - smoothstep(0.5,1,finalAlpha));
   
    return fixed4(finalColor ,finalAlpha);
}


云的效果

ProceduralSkybox,凑活看,比较乱:
fixed4 frag(v2f i):SV_Target
{
    //light坐标系转换矩阵
    float4x4 LToW = float4x4(_LtoW0,_LtoW1,_LtoW2,_LtoW3);
    float verticalPos = i.uv.y*0.5 + 0.5;
    float sunNightStep = smoothstep(-0.3,0.25,_WorldSpaceLightPos0.y);
    float3 sunUV = mul(i.uv.xyz,LToW);
   
    //SUN
    //dist of skybox suface pixel and sun point
    float sunDist = distance(i.uv.xyz, _WorldSpaceLightPos0);
        float sunArea = 1 - sunDist / _SunSize;
        sunArea = smoothstep(_SunInnerBound,_SunOuterBound,sunArea);
    float3 fallSunColor = _SunColor.rgb*0.4;
    float3 finalSunColor = lerp(fallSunColor,_SunColor.rgb,smoothstep(-0.03,0.03,_WorldSpaceLightPos0.y)) * sunArea;
    //MOON
    float2 moonUV = sunUV.xy * _MoonTex_ST.xy * (1/_MoonSize+0.001) + _MoonTex_ST.zw;
    float4 moonTex = tex2D(_MoonTex, moonUV);
    //float moonHaloArea = max(0,1 - length((moonUV - 0.5) * 1/_MoonHaloSize));
    //float lerpmoonHaloArea = lerp(0,moonHaloArea,smoothstep(0.02,0.1,-_WorldSpaceLightPos0.y));
   

    //step z 两个月亮问题
    float3 finalMoonColor = (_MoonColor * moonTex.rgb * moonTex.a) * step(0,sunUV.z);

    //sunInfluence
    float sunMask2 = smoothstep(-0.4,0.4,-mul(i.uv.xyz,LToW).z) - 0.3;
    float sunInfScaleMask = smoothstep(-0.01,0.1,_WorldSpaceLightPos0.y) * smoothstep(-0.4,-0.01,-_WorldSpaceLightPos0.y);
    float3 finalSunInfColor = _SunInfColor * sunMask2 * _SunInfScale * sunInfScaleMask;

    //Mie scattering
    float3 scatteringColor = 0;
   
    float3 rayStart = float3(0,10,0);
    rayStart.y = saturate(rayStart.y);
    float3 rayDir = normalize(i.uv.xyz);

    float3 planetCenter = float3(0, -_PlanetRadius, 0);
    float2 intersection = RaySphereIntersection(rayStart,rayDir,planetCenter,_PlanetRadius + _AtmosphereHeight);
    float rayLength = intersection.y;
   
    intersection = RaySphereIntersection(rayStart, rayDir, planetCenter, _PlanetRadius);
        if (intersection.x > 0)
                rayLength = min(rayLength, intersection.x*100);

    float4 inscattering = IntegrateInscattering(rayStart, rayDir, rayLength, -_WorldSpaceLightPos0.xyz, 16);
    scatteringColor = _MieColor*_MieStrength * ACESFilm(inscattering);

    //DAY NIGHT
    float3 gradientDay = lerp(_DayBottomColor, _DayMidColor, saturate(i.uv.y))*step(0,-i.uv.y)
                        +lerp(_DayMidColor, _DayTopColor, saturate(i.uv.y))*step(0,i.uv.y);

        float3 gradientNight = lerp(_NightBottomColor, _NightTopColor, verticalPos);
        float3 skyGradients = lerp(gradientNight, gradientDay,sunNightStep);

    //HORIZONTAL
    float horWidth = lerp(_NightHorWidth, _DayHorWidth,sunNightStep);
    float horStrenth = lerp(_NightHorStrenth, _DayHorStrenth,sunNightStep);
    float horLineMask = smoothstep(-horWidth,0,i.uv.y) * smoothstep(-horWidth,0,-i.uv.y);
    float3 horLineGradients = lerp(_NightHorColor, _DayHorColor,sunNightStep);

    //GALAXY
   
    float4 galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw + float2(0,_Time.x*0.67));
   
    float4 galaxy = tex2D(_GalaxyTex,(i.uv.xz + (galaxyNoiseTex-0.5)*0.3)*_GalaxyTex_ST.xy + _GalaxyTex_ST.zw);

    float4 galaxyColor =(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.2,1-galaxy.g);

    galaxyNoiseTex = tex2D(_GalaxyNoiseTex,(i.uv.xz )*_GalaxyNoiseTex_ST.xy + _GalaxyNoiseTex_ST.zw - float2(_Time.x*0.5,_Time.x*0.67));
    galaxy = tex2D(_GalaxyTex,(i.uv.xz + (galaxyNoiseTex-0.5)*0.3)*_GalaxyTex_ST.xy + _GalaxyTex_ST.zw);

    galaxyColor +=(_GalaxyColor * (-galaxy.r+galaxy.g) + _GalaxyColor1*galaxy.r)*smoothstep(0,0.3,1-galaxy.g);
    galaxyColor *= 0.5;


   
    //STAR
    float4 starTex = tex2D(_StarTex,i.uv.xz*_StarTex_ST.xy + _StarTex_ST.zw);
    float4 starNoiseTex = tex3D(_StarNoise3D,i.uv.xyz*_StarNoise3D_ST.x + _Time.x*0.12);

    float starPos = smoothstep(0.21,0.31,starTex.r);
    float starBright = smoothstep(0.613,0.713,starNoiseTex.r);

    float starColor = starPos*starBright;
    starColor = starColor*galaxy.r*3 + starColor*(1-galaxy.r)*0.3;
    float starMask = lerp((1 - smoothstep(-0.7,-0.2,-i.uv.y)),0,sunNightStep);

    //FINAL
    float3 finalSkyColor = skyGradients * (1 - horLineMask)
                        + horLineGradients * horLineMask * horStrenth ;


    float3 finalColor = finalSunColor + finalSunInfColor + finalMoonColor + finalSkyColor + (starColor + galaxyColor) * starMask + scatteringColor;


   
    fixed cosRh = cos(_CloudRotate + _CloudHighSpeed * _Time.x);
    fixed sinRh = sin(_CloudRotate + _CloudHighSpeed * _Time.x);
    float4x4 rotateh = float4x4(fixed4(cosRh,0,sinRh,0),fixed4(0,1,0,0),fixed4(-sinRh,0,cosRh,0),fixed4(0,0,0,1));
    float4 rotateUVh = mul(i.uv.xyz,rotateh);
   
    float4 cloudHighTex = tex2D(_CloudHighTex, rotateUVh*_CloudHighTex_ST.xy + _CloudHighTex_ST.zw);
    finalColor += cloudHighTex.r * 0.1;


    return fixed4(finalColor,1.0);
}
页: [1]
查看完整版本: Unity 卡通渲染 程序化天空盒