URP-based programmatic skybox

Reference source: 

 Skybox Tutorial Part 1 | Kelvin Van Hoorn (kelvinvanhoorn.com)

[Procedural Skybox] Process Record 02: Cloud Disturbance Edge Light Dissipation Effect_99345's Blog-CSDN Blog

Programmatic skybox realizes day and night transformation- Zhihu (zhihu.com)

1. The sun 

        Goal: Change the direction of the light to make the sky ball rotate (alternate day and night); the positive direction of the light: the sun, the opposite direction of the light: the moon;

        Step 1: Calculate the distance between the uv coordinates on the sky sphere and the direction of the light, the closer to 0 the more coincident with the direction of the light, and the darker the texture. (Skybox uv is three directions of xyz)

        Step 2: Divide by the radius to create the circle, then invert.

        smoothstep: It can be used to generate a smooth transition value from 0 to 1, which is also called a smooth step function.

code:

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
        }
    }
}

Two, the moon

        Goal: moon positioning, texture sampling, etc. The moon uses cubemap, which can be rotated.

        Step 1: Raytrace the sphere information and create a template from it (using sphIntersect), and create a mask if there is no intersection.

        Step 2: Calculate the lighting (NdotL), which requires the normal (line of sight - direction of the vertex of the moon), and the direction of the light is the opposite of the direction of the sun.

         Step 3: Add exposure to control the brightness of the moon, sample the moon cubemap texture, and use the matrix to rotate when the moon is transformed.

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
        }
    }
}

3. Sky Gradient + Horizon Gradient

        Target: Day + Night Night→Day: Sunrise, Day→Night: Sunset

        The implementation code uses a bunch of lerp, and different color mixing methods are tried for the horizon and sky background. If they are superimposed, they will be very bright and the color will be impure;

         Perform lerp based on the values ​​of y and z. For the difference between lerp, smoothstep and step, refer to this article: Shader Lab: smoothstep function - Zhihu (zhihu.com)

Unity Shader Minimalist Practice 3 - Application of step, lerp and smoothstep - Short Book (jianshu.com)

Join the horizon:

                //天空渐变
                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;

 4. Sun Moon Bloom

        The mixing method of the glow color and the background color has been studied for a long time, and it is also the same as the mixing problem of the sky and the horizon, which will become brighter and brighter, but it will have a strong sense of fragmentation when using lerp (too good, it has not been changed for a long time, and it will be improved in the future)

        See the third part above for the renderings.

//太阳月亮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));

5. Gao Tianyun

        Basic operation plus uv animation

//高天云
                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;

        Output background color so far:

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

6. Galaxy

        Use two channels as a mask, use noise to disturb the sampling UV, and then use lerp to set the color; I saw that the big guys sampled the texture twice and performed two UV disturbances. I think it is a bit expensive, but there is no better way to change it.

 

                //银河
                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;

Seven, the stars

                //星星
                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;               

Eight, cloud

        After reading the big guy’s tutorial, I used sdf to make clouds. I didn’t find a suitable texture. I drew it myself but didn’t bring a hand-painted board... I was embarrassed, and finally took the original cloud texture. It’s so beautiful and very satisfying, art yyds.

        Firstly, position the patch cloud in the modeling software.

         For comparison, in order to set different dissipating progress, it is divided into several groups of clouds.

                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);

        The implementation of diffuse reflection color is the same as that of sky color, using a bunch of 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;

Rim Light:

        The edge mask is pasted;: In the image channel g, the edge light uses 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;

The most important sdf clouds dissipate:

        lerpCtrl is the threshold, and uses the sin function to set the dissipation and restoration of the cloud. After reading the master tutorial, the dissipated transparency effect is realized by smoothstep, and cloud.b is the sdf channel of the cloud.

        But in the process of realization, I found that when restoring to the original image, it will flicker, and finally multiplied by the a channel, there will be no problem.

                //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);

Guess you like

Origin blog.csdn.net/weixin_56784984/article/details/129328376