基于URP的程序化天空盒

参考来源: 

 天空盒教程第 1 部分 |开尔文·范·霍恩 (kelvinvanhoorn.com)

【程序化天空盒】过程记录02:云扰动 边缘光 消散效果_九九345的博客-CSDN博客

程序化天空盒实现昼夜变换 - 知乎 (zhihu.com)

一、太阳 

        目标:改变光的方向,使天空球旋转(日夜交替);光的正方向:太阳,光的反方向:月亮;

        步骤一:计算天空球上的uv坐标与光的方向的距离,越与光的方向重合,越接近0,贴图越黑。(天空盒uv为xyz三个方向)

        步骤二:除以半径创建圆形,然后反相。

        smoothstep:可以用来生成0到1的平滑过渡值,它也叫平滑阶梯函数。

代码:

Shader "URP_Skybox"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _SunRadius ("太阳半径", float) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Background" "RenderPipeline"="UniversalPipeline"}
        

        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag



            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            
            CBUFFER_START(UnityPerMaterial)

            float _SunRadius;

            CBUFFER_END

            TEXTURE2D(_MainTex);
            SAMPLER(sampler_MainTex);

            struct appdata
            {
                float4 vertex : POSITION;
                float3 uv : TEXCOORD0;
            };

            struct v2f
            {
                float3 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 posWS : TEXCOORD1;
            };

            

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                o.uv = v.uv;
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                Light light = GetMainLight(TransformWorldToShadowCoord(i.posWS));
                half sunDist = distance(i.uv.xyz, light.direction);
                //smoothstep:大于等于0.7 为0,小于等于1 为1
                half sunArea = 1-smoothstep(0.7, 1, sunDist * _SunRadius);
                return float4(sunArea,sunArea,sunArea,1);
            }
            ENDHLSL
        }
    }
}

二、月亮

        目标:月亮定位、贴图采样等 月亮使用cubemap,可以旋转。

        步骤一:光线追踪球体信息并从中创建模板(使用sphIntersect),如果没有相交就创建遮罩。

        步骤二:计算照明(NdotL),需要法线(视线方向-月亮顶点方向),光线方向为太阳光方向的反向。

         步骤三:添加曝光度控制月亮的亮度,采样月亮cubemap纹理,并使用矩阵在月亮变换的时候自旋转。

Shader "URP_Skybox"
{
    Properties
    {
        [Header(Moon)]
            [NoScaleOffset] _MoonCubeMap ("月亮贴图", Cube) = "_Skybox" {}
            _MoonRadius ("月亮半径", Range(0, 1)) = 0.05
            _MoonMaskRadius("月亮遮罩半径", range(1, 25)) = 10
            _MoonSmooth("月亮边缘平滑度", Range(0, 1)) = 0.7
            _MoonExposure("月亮曝光度", range(0, 1)) = 1
        [Header(Sun)]    
            _SunRadius ("太阳半径", float) = 1.0
    }
    SubShader
    {
        Tags { "RenderType"="Background" "RenderPipeline"="UniversalPipeline"}
        
        Pass
        {
            HLSLPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"
            
            CBUFFER_START(UnityPerMaterial)

            float _SunRadius;
            float _MoonRadius;
            float _MoonMaskRadius;
            float _MoonSmooth;
            float _MoonExposure;

            CBUFFER_END

            TEXTURECUBE(_MoonCubeMap);
            SAMPLER(sampler_MoonCubeMap);

            //参数:射线方向(视角方向) ,球体的位置(月球方向),半径(月球半径)。
            //从光线原点(我们的摄像机)到球面交点的距离,如果没有交点,它返回 -1。然后检查交集是否大于 -1来创建一个mask。
            float sphIntersect(float3 rayDir, float3 spherePos, float radius)
            {
                float3 oc = -spherePos;
                float b = dot(oc, rayDir);
                float c = dot(oc, oc) - radius * radius;
                float h = b * b - c;
                if(h < 0.0) return -1.0;
                h = sqrt(h);
                return -b-h;
            }

            struct appdata
            {
                float4 vertex : POSITION;
                float3 uv : TEXCOORD0;
            };

            struct v2f
            {
                float3 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float4 posWS : TEXCOORD1;
            };

            

            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = TransformObjectToHClip(v.vertex);
                o.uv = v.uv;
                o.posWS = mul(unity_ObjectToWorld, v.vertex);
                return o;
            }

            float4 frag (v2f i) : SV_Target
            {
                float3 posWS = normalize(i.posWS);
                Light light = GetMainLight(TransformWorldToShadowCoord(posWS));
                //月亮
                float3 lDir = normalize(-light.direction);
                half moonIntersect = sphIntersect(posWS, lDir, _MoonRadius);
                half moonDist = distance(i.uv.xyz, lDir);
                half moonMask = 1-smoothstep(_MoonSmooth, 1, moonDist * _MoonMaskRadius);
                //照明 需要NdotL计算法线
                half3 moonNormal = normalize(lDir - posWS * moonIntersect);
                half3x3 correctionMatrix = float3x3(0, -0.2588190451, -0.9659258263,
                                                    0.08715574275, 0.9622501869, -0.2578341605,
                                                    0.9961946981, -0.08418598283, 0.02255756611);
                moonNormal = mul(correctionMatrix, moonNormal);
                float moonNdotL = saturate(dot(moonNormal, lDir))*0.5+0.5;
                half3 moonTex = SAMPLE_TEXTURECUBE(_MoonCubeMap, sampler_MoonCubeMap, moonNormal).rgb;
                half3 moonColor = moonMask * moonNdotL * exp2(_MoonExposure) * moonTex;
                //太阳
                half sunDist = distance(i.uv.xyz, light.direction);//天空盒uv三个方向
                half sunArea = 1-smoothstep(0.7, 1, sunDist * _SunRadius);
                half3 sunColor = float3(sunArea,sunArea,sunArea);
                return float4(sunColor+moonColor,1);
            }
            ENDHLSL
        }
    }
}

三、天空渐变 + 地平线渐变

        目标:白天+夜晚  夜晚→白天:日出,白天→夜晚:日落

        实现代码用了一堆lerp,地平线和天空背景尝试了不同的颜色混合方法,叠加的话会很亮并且颜色不纯;不叠加的话(lerp),又会使天空割裂的感觉……算了,先叠加以后又更好的方法再改进。

         根据y和z的数值进行lerp。关于lerp、smoothstep和step的区别参考这篇:Shader实验室: smoothstep函数 - 知乎 (zhihu.com)

Unity Shader 极简实践3——step,lerp 和 smoothstep 应用 - 简书 (jianshu.com)

加入地平线:

                //天空渐变
                float4 dayColor = (0,0,0,1);
                float4 nightColor = (0,0,0,1);
                float3 lDirRamp = (light.direction + 1) * 0.5;

                float4 horizonMask = smoothstep(_HorizonHeight,0,i.uv.y) * smoothstep(_HorizonHeight,0,-i.uv.y) * _HorizonIntensity;
                
                float4 dayHorizonColor = (0,0,0,0);
                float4 nightHorizonColor = (0,0,0,0);
                float4 daymixColor = (0,0,0,0);
                float4 nightmixColor = (0,0,0,0);
                
                if(lDirRamp.y > 0.5 && lDirRamp.y < 1)
                {
                    dayColor = lerp(lerp(_MorningColor, _NoonColor, smoothstep(0,_MorningDuration,lDirRamp.z)), 
                                                    _NightfallColor, smoothstep(_NightfallDuration, 1, lDirRamp.z)) * step(0.5, lDirRamp.y); //y值大于0.5返回color,否则返回黑色
                    dayHorizonColor = lerp(lerp(_MorningHorizonColor,_NoonHorizonColor,smoothstep(0,_MorningDuration,lDirRamp.z)),
                                                                _NightfallHorizonColor,smoothstep(_NightfallDuration, 1, lDirRamp.z))* step(0.5, lDirRamp.y) 
                                    * pow(saturate(horizonMask), _HorizonPow); 
                    //daymixColor = lerp(dayHorizonColor, dayColor, posWS.y);
                    daymixColor = dayHorizonColor + dayColor;
                }   
                else
                {
                    nightColor = lerp(_MorningColor,lerp(_NightColor, _NightfallColor, smoothstep(_NightfallDuration,1,lDirRamp.z)), 
                                                                                        smoothstep(0, _MorningDuration, lDirRamp.z)) * step(-0.5, -lDirRamp.y);
                    nightHorizonColor = lerp(_MorningHorizonColor,lerp(_NightHorizonColor,_NightfallHorizonColor,smoothstep(_NightfallDuration,1,lDirRamp.z)),
                                                                                        smoothstep(0, _MorningDuration, lDirRamp.z)) * step(-0.5, -lDirRamp.y)
                                    * pow(saturate(horizonMask), _HorizonPow); 
                    //nightmixColor = lerp(nightHorizonColor, nightColor, posWS.y);
                    nightmixColor = nightHorizonColor + nightColor;
                }
                float4 skyColor = daymixColor + nightmixColor;

 四、太阳月亮Bloom

        光辉颜色和背景颜色的混合方法研究了好久,也是和天空及地平线的混合问题一样会越叠越亮,但是用lerp又会有很强的割裂感(太菜了,改了好久没改出来,以后再改进吧

        效果图看上面第三部分。

//太阳月亮Bloom
                half moonBloomMask = (1 - smoothstep(0, 1, moonDist *3 )) * smoothstep(0.4, 1, lDir.y);
                moonColor *= lerp(0.1, 0.5, smoothstep(0.1, 1, posWS.y));
                half4 moonBloomColor = moonBloomMask * skyColor * 2* _MoonBloomColor;   
                
                float Mask = saturate(dot(light.direction, posWS));
                //float Mask1 = lerp(_SumBloomMask1,_SumBloomMask,smoothstep(0, _MorningDuration, lDirRamp.z));
                float sunBloomMask = pow(Mask, _SumBloomMask);
                float sunBloomMask1 = pow(Mask, _SumBloomMask1);
                float sunBloomMask2 = pow(Mask, _SumBloomMask2);
                float sunBloomMask3 = pow(Mask, _SumBloomMask3);

                half4 sunBloomColor = lerp(lerp(_SunBloomColor1 * sunBloomMask1 + _SunBloomColor3 * (sunBloomMask-sunBloomMask1), _SunBloomColor* sunBloomMask, smoothstep(0, _MorningDuration, lDirRamp.z)),
                                                                            _SunBloomColor2* sunBloomMask2 + _SunBloomColor4 * (sunBloomMask3 - sunBloomMask2), smoothstep(_NightfallDuration, 1, lDirRamp.z));

五、高天云

        基本操作加uv动画

//高天云
                float2 highCloudUV = TRANSFORM_TEX(i.uv.xy, _HighCloudTex);
                highCloudUV.x = i.uv.z < 0 ? 1-highCloudUV.x : highCloudUV.x;
                highCloudUV.x += _Time *0.1*_HighCloudSpeed;
                highCloudUV.y = smoothstep(0,1,highCloudUV.y);
                float highCloudTex = SAMPLE_TEXTURE2D(_HighCloudTex, sampler_HighCloudTex, highCloudUV).a;
                highCloudTex = smoothstep(0,1,highCloudTex) * abs(i.uv.z) * 10;

        输出至今的背景色:

float4 SumColor = float4(sunColor + moonColor,1) + skyColor * (1-sunBloomMask*0.5) + sunBloomColor + moonBloomColor;

六、银河

        使用两个通道作为遮罩,用noise扰乱采样UV,再用lerp设置颜色;看了大佬们的是采样两次贴图,进行两次uv扰动,我觉得有点费,但是没有更好的改法,又菜又爱玩,以后再改。

 

                //银河
                float2 galaxyNoiseUV = TRANSFORM_TEX(i.uv.xz, _GalaxyNoise);
                galaxyNoiseUV.x += _Time*0.1;
                float4 galaxyNoise = SAMPLE_TEXTURE2D(_GalaxyNoise, sampler_GalaxyNoise, galaxyNoiseUV);
                float2 galaxyUV = TRANSFORM_TEX(i.uv.xz, _Galaxy);
                galaxyUV += (galaxyNoise-0.1) *0.8* galaxyUV.xy;
                float4 galaxy = SAMPLE_TEXTURE2D(_Galaxy, sampler_Galaxy, galaxyUV)* smoothstep(0,1,i.uv.y-0.3);            
                float4 galaxyColor = lerp(lerp(_GalaxyColor,_GalaxyColor2,smoothstep(0.2,_ColorPos,galaxyUV.x)), _GalaxyColor,smoothstep(_ColorPos1,0.7,galaxyUV.x))
                                                    * (galaxy.g - galaxy.r*2) + _GalaxyColor1* galaxy.r ;
                galaxyColor *= 0.4;

七、星星

                //星星
                float2 starTexUV = TRANSFORM_TEX(i.uv.xz,_StarTex);
                float starTex = smoothstep(0.1,0.2,SAMPLE_TEXTURE2D(_StarTex, sampler_StarTex, starTexUV).r) * smoothstep(0,1,i.uv.y-0.3);
                
                float2 starNoiseTexUV = TRANSFORM_TEX(i.uv.xz, _StarNoiseTex);    
                starNoiseTexUV.x += _Time*0.1;
                float starNoiseTex = smoothstep(0.4,0.45,SAMPLE_TEXTURE2D(_StarNoiseTex, sampler_StarNoiseTex, starNoiseTexUV).r);
                
                float starColor = starTex*starNoiseTex;
                //银河遮罩内的星星更亮
                starColor = starColor *galaxy.g*5 + starColor*(1-galaxy.g)*0.5;

                float4 starAndGalaxy = (galaxyColor+float4(starColor, starColor, starColor, 1)) * smoothstep(0,1,lDir.y);
                
                return SumColor + starAndGalaxy + float4(highCloudTex,highCloudTex,highCloudTex,1) * skyColor;               

八、云

        看了大佬教程是使用sdf做云,本来没有找到合适的贴图,自己画但是没有带手绘板……尬住,最后拿了原的云朵贴图,真好看很满意,美术yyds。

        首先在建模软件里摆好面片云的位置。

         比较菜,为了设置不同的消散进度,分成了好几组云。

                float3 posWS = normalize(i.posWS);
                Light light = GetMainLight(TransformWorldToShadowCoord(posWS));
                float3 lDirRamp = (light.direction + 1) * 0.5;

                //采样扰动贴图
                float2 cloudNoiseUV = TRANSFORM_TEX(i.uv.xy, _CloudNoiseTex);
                cloudNoiseUV.x += _Time * _CloudSpeed;
                float4 cloudNoiseTex = SAMPLE_TEXTURE2D(_CloudNoiseTex, sampler_CloudNoiseTex, cloudNoiseUV);
                
                //采样云朵贴图
                float2 cloudUV = TRANSFORM_TEX(i.uv.xy, _CloudTex);
                cloudUV += float2(cloudNoiseTex.x-1, cloudNoiseTex.y-0.25)*0.02;
                float4 cloudTex = SAMPLE_TEXTURE2D(_CloudTex, sampler_CloudTex, cloudUV);

        漫反射颜色的实现和天空颜色实现方法一样,用了一堆lerp。

                //漫反射颜色
                float4 brightColor = (0,0,0,0);
                float4 shadowColor = (0,0,0,0);
                float4 dayMixColor = (0,0,0,0);
                float4 nightMixColor = (0,0,0,0);
                if(lDirRamp.y > 0.5 && lDirRamp.y < 1)
                {
                    brightColor = lerp(lerp(_BrightMorningColor, _BrightNoonColor, smoothstep(0,_MorningDuration,lDirRamp.z)), 
                                                            _BrightNightfallColor, smoothstep(_NightfallDuration, 1, lDirRamp.z)) * step(0.5,lDirRamp.y);
                    shadowColor = lerp(lerp(_ShadowMorningColor, _ShadowNoonColor, smoothstep(0,_MorningDuration,lDirRamp.z)), 
                                                            _ShadowNightfallColor, smoothstep(_NightfallDuration, 1, lDirRamp.z)) *step(0.5,lDirRamp.y);
                    dayMixColor = lerp(shadowColor, brightColor, cloudTex.r);

                }
                else
                {
                    brightColor = lerp(_BrightMorningColor,lerp(_BrightNightColor, _BrightNightfallColor, smoothstep(_NightfallDuration,1,lDirRamp.z)), 
                                                                                                            smoothstep(0, _MorningDuration, lDirRamp.z))
                                                                                                            *step(-0.5,-lDirRamp.y);
                    shadowColor = lerp(_ShadowMorningColor,lerp(_ShadowNightColor, _ShadowNightfallColor, smoothstep(_NightfallDuration,1,lDirRamp.z)), 
                                                                                                            smoothstep(0, _MorningDuration, lDirRamp.z))
                                                                                                            *step(-0.5,-lDirRamp.y);
                    nightMixColor = lerp(shadowColor, brightColor, cloudTex.r);
                }
                float4 beseCol = dayMixColor + nightMixColor;

边缘光:

        边缘遮罩在贴;:图通道g中,边缘光使用NdotL。

                //边缘光
                float3 worldNormal = normalize(i.normalWS);
                float NdotL = saturate(dot(-light.direction, worldNormal));
                float rimIntensity = lerp(0, _RimColorIntensity, smoothstep(0.3,1,lDirRamp.y));
                float4 _rimColor = light.direction.z < 0.5 ? _RimMoringColor : _RimNightfallColor;
                float4 rimColor = _rimColor * NdotL * cloudTex.g * rimIntensity;

最重要的sdf云消散:

        lerpCtrl为阈值,并且用sin函数设置云的消散和复原。看了大佬教程,消散的透明效果由smoothstep实现,cloud.b为云的sdf通道。

        但是在实现的过程中,发现在复原到原来的图像时,会闪,最后乘以了a通道后就没问题了。

                //SDF云 透明度
                float lerpCtrl = lerp((sin((_Time.x + _LerpTimeOffset)*_LerpSpeed)*0.78+0.78),2,_LerpCtrl);
                float cloudStep = 1-lerpCtrl;
                //cloudStep 云层的分层层数
                float alpha_SDF = smoothstep(saturate(cloudStep-0.3), cloudStep, cloudTex.b*cloudTex.a);
                float alpha = lerp(alpha_SDF, cloudTex.a, smoothstep(0.99,1,lerpCtrl));

                float3 finalColor = (beseCol+ rimColor)*alpha;

                return float4(finalColor,alpha);

猜你喜欢

转载自blog.csdn.net/weixin_56784984/article/details/129328376