Shader "Unlit/parallaxCloud"
{
    // The properties block of the Unity shader. In this example this block is empty
    // because the output color is predefined in the fragment shader code.
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _DetailTex ("Detail Texture", 2D) = "white" {}
        _RenderTex ("Parallax Cloud Render Texture", 2D) = "white" {}
        detailWeight ("Detail Weight", Range(0.0,1.0)) = 0.3
        _ParallaxScale("Parallax Scale", float) = 0.0
        numLayers("number of layers", float) = 10
        planeSize("size of plane", float) = 10
        baseAlpha("alpha",float) = 0.0
        alphaScale("scale of alpha", float) = 10
        speed("speed", Vector) = (0.0,0.0,0.0)
        density("density of cloud",float) = 0.66
        cloudColor("cloud color", Color) = (1,1,1,0)
        heightColor("height color", Color) = (1,1,1,0)
        lowColor("low color", Color) = (1,1,1,0)
        colorSpeed("color change speed", float) = 1
        lightColor("light color", Color) = (1,1,1,0)
        backlightColor("backlight color", Color) = (1,1,1,0)
        colorWeight ("Color Weight", Range(0.0,1.0)) = 0.5
    }

    // The SubShader block containing the Shader code. 
    SubShader
    {
        // SubShader Tags define when and under which conditions a SubShader block or
        // a pass is executed.
        Tags
        {
            "RenderType" = "Transparent" 
            "Queue" = "Transparent"
            "RenderPipeline" = "UniversalRenderPipeline"
        }

        Pass
        {
            Tags{"LightMode" = "ParallaxCloud"}

            // The HLSL code block. Unity SRP uses the HLSL language.
            HLSLPROGRAM
            // This line defines the name of the vertex shader. 
            #pragma vertex vert
            // This line defines the name of the fragment shader. 
            #pragma fragment frag

            #pragma multi_compile __ _Interpolation_On

            // The Core.hlsl file contains definitions of frequently used HLSL
            // macros and functions, and also contains #include references to other
            // HLSL files (for example, Common.hlsl, SpaceTransforms.hlsl, etc.).
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"        
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Lighting.hlsl"

            // The structure definition defines which variables it contains.
            // This example uses the Attributes structure as an input structure in
            // the vertex shader.
            struct Attributes
            {
                // The positionOS variable contains the vertex positions in object
                // space.
                float4 position   : POSITION;
                float2 uv : TEXCOORD0;
                float3 normal     : NORMAL;
                float4 tangent    : TANGENT;
            };

            struct Varyings
            {
                // The positions in this struct must have the SV_POSITION semantic.
                float4 position  : SV_POSITION;
                half2 uv : TEXCOORD0;
                half3 viewDirWS : TEXCOORD1;
                half3 viewDirTS : TEXCOORD2;
                half3 lightDirTS : TEXCOORD3;
                half densityMulStepLength : TEXCOORD4;
            };            

            TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
            half4 _MainTex_ST;
            
            TEXTURE2D(_DetailTex); SAMPLER(sampler_DetailTex);
            half4 _DetailTex_ST;

            TEXTURE2D(_NoiseTex); SAMPLER(sampler_NoiseTex);
            half4 _NoiseTex_ST;

            half4 _Parallax_Cloud_Rt_size;

            TEXTURE2D_X_FLOAT(_CameraDepthTexture);     SAMPLER(sampler_CameraDepthTexture);

            half _ParallaxScale;
            half numLayers;
            half planeSize;
            half alphaScale;
            half4 speed;
            half density;
            half4 cloudColor;
            half4 heightColor;
            half4 lightColor;
            half4 backlightColor;
            half4 lowColor;
            half colorSpeed;
            half baseAlpha;
            half detailWeight;
            half colorWeight;
            half noiseWeight;
            half testValue;

            // The vertex shader definition with properties defined in the Varyings 
            // structure. The type of the vert function must match the type (struct)
            // that it returns.
            Varyings vert(Attributes i)
            {
                // Declaring the output object (OUT) with the Varyings struct.
                Varyings o;
                // The TransformObjectToHClip function transforms vertex positions
                // from object space to homogenous space
                VertexPositionInputs vertexInput = GetVertexPositionInputs(i.position.xyz);
                o.position = vertexInput.positionCS;
                o.viewDirWS = GetCameraPositionWS() - vertexInput.positionWS;

                VertexNormalInputs normalInput = GetVertexNormalInputs(i.normal, i.tangent);

                float3x3 WorldToTangent = float3x3(normalInput.tangentWS, normalInput.bitangentWS, normalInput.normalWS);

                o.viewDirTS = mul(WorldToTangent,o.viewDirWS);

                Light mainLight = GetMainLight();
                float3 lightDir = mainLight.direction;

                o.lightDirTS = mul(WorldToTangent, lightDir);
                o.lightDirTS.x = mul(lightDir, normalInput.tangentWS);
                o.uv = i.uv;

                half stepLength = 1.0h / numLayers;
                o.densityMulStepLength = -density * stepLength;
                
                return o;
            }

            float rand(float value){
                return frac(sin(value * 78.233) * 43758.5453);
            }

            half2 random (half2 st) 
            {
                return frac(sin(st.xy * half2(12.9898,78.233))* 43758.5453123);
            }

            #define SAMPLE_TEXTURE2D_TRANSFORM(uv, tex) SAMPLE_TEXTURE2D(tex,sampler##tex,(uv.xy) * tex##_ST.xy + tex##_ST.zw)
            #define SAMPLE_TEXTURE2D_TRANSFORM_ANIMATION(uv, tex, direct) SAMPLE_TEXTURE2D(tex, sampler##tex, mad(direct, _Time.y, mad(uv.xy, tex##_ST.xy, tex##_ST.zw)))
            #define SAMPLE_TEXTURE2D_ANIMATION(uv, tex, direct) SAMPLE_TEXTURE2D(tex, sampler##tex, mad(direct, _Time.y, uv.xy))
            half4 SampleCombineMainDetail(half2 uv)
            {
                half4 mainValue = SAMPLE_TEXTURE2D_TRANSFORM_ANIMATION(uv,_MainTex,speed.xy);
                half4 detailValue = SAMPLE_TEXTURE2D_TRANSFORM_ANIMATION(uv,_DetailTex,speed.zw);
                
                return lerp(mainValue, detailValue, detailWeight);
            }

            half4 ComputePerPixelHeightDisplacement(half2 texOffsetCurrent)
            {
                // return SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, TRANSFORM_TEX(param.uv + texOffsetCurrent,_MainTex)).a;
                half4 value = SampleCombineMainDetail(texOffsetCurrent);
                // value.a += 0.01;
                return value;
            }

            half3 GetDisplacementObjectScale()
            {
        
                half3 objectScale = half3(1.0, 1.0, 1.0);
                half4x4 worldTransform = GetWorldToObjectMatrix();
        
                objectScale.x = length(half3(worldTransform._m00, worldTransform._m01, worldTransform._m02));
                objectScale.z = length(half3(worldTransform._m20, worldTransform._m21, worldTransform._m22));
        
                return objectScale;
            }

            // This is implementation of parallax occlusion mapping (POM)
            // This function is compatible with tiled uv.
            // viewDirTS is view vector in texture space matching the UVSet
            // ref: https://www.gamedev.net/resources/_/technical/graphics-programming-and-theory/a-closer-look-at-parallax-occlusion-mapping-r3262
            half4 ParallaxOcclusionMapping(half depth, Varyings i)
            {
                half3 viewDirTS = normalize(i.viewDirTS) * GetDisplacementObjectScale().xzy;
                half NdotV = max(viewDirTS.z,0.0001h);
                    
                // Transform the view vector into the UV space.
                viewDirTS = half3(viewDirTS.xy * _ParallaxScale, viewDirTS.z);

                float depthTransform = NdotV / (_ParallaxScale * planeSize);
                depth = saturate(1.0h - depth * depthTransform);

                // Convention: 1.0 is top, 0.0 is bottom - POM is always inward, no extrusion
                half stepSize = 1.0h / numLayers;

                // View vector is from the point to the camera, but we want to raymarch from camera to point, so reverse the sign
                // The length of viewDirTS vector determines the furthest amount of displacement:
                // half parallaxLimit = -length(viewDirTS.xy) / viewDirTS.z;
                // half2 parallaxDir = normalize(Out.viewDirTS.xy);
                // half2 parallaxMaxOffsetTS = parallaxDir * parallaxLimit;
                // Above code simplify to
                float2 parallaxMaxOffsetTS = (viewDirTS.xy / -viewDirTS.z);

                // Do a first step before the loop to init all value correctly
                half3 texStart = half3(i.uv,1.0h);
                half3 texEnd = half3(i.uv+parallaxMaxOffsetTS,0.0h);
                half4 currValue;
                half4 sum = half4(0.0h,0.0h,0.0h,0.0h);
                half cloudDepth = 0.0h;
                half maxDepth = -1.0h;
                half accumulateTransmittance = 1.0h;

                half densityMulStepLength = i.densityMulStepLength / NdotV;
                half transmittance;
                half ratio;
                half ratioWeight = numLayers * 0.3h;

                // Linear search
                [loop] for (half stepIndex = 0.0h; stepIndex <= numLayers; ++stepIndex)
                {
                    // if(stepIndex > testValue) break;

                    half3 texOffset = lerp(texStart, texEnd, stepIndex/numLayers);

                    // Sample height map which in this case is stored in the alpha channel of the normal map:
                    currValue = SampleCombineMainDetail(texOffset);
                    currValue.w -= texOffset.z;
                    half depthDiff = texOffset.z - depth;

                    // get depth of cloud for Occlusion in next pass.
                    cloudDepth = max(cloudDepth,texOffset.z*step(0.0h,currValue.w));
                    maxDepth = max(maxDepth,currValue.w - step(depthDiff,0.0h));
                    depthDiff = lerp(depthDiff,1.0h,half(depthDiff < 0.0h && maxDepth < -0.1h));

                    ratio = saturate(currValue.w * ratioWeight);
                    ratio *= saturate(depthDiff * numLayers) * saturate(mad(-4 , length(texOffset.xy - half2(0.5,0.5)),2));

                    transmittance = exp(densityMulStepLength * ratio);
                    currValue.w = exp(currValue.w * -colorSpeed);
                    sum = mad(currValue * accumulateTransmittance,(1.0h - transmittance),sum);
                    accumulateTransmittance *= transmittance;
                }

                // get sum value without transmittance.
                sum /= max(1 - accumulateTransmittance, 0.001h);

                half3 normal = sum.xyz;
                normal = normalize(mad(normal, 2.0h, 1.0h));
                normal.y = normal.y * -1.0h;

                sum.x = mad(dot(normal.xyz , i.lightDirTS), 0.5h, 0.5h);
                sum.y = 1 - sum.w;
                sum.w = 1.0h - accumulateTransmittance;

                sum = saturate(sum);
                sum.z = (1.0h - cloudDepth)/depthTransform;

                return sum;
            }

            // Returns the forward (central) direction of the current view in the world space.
            half3 GetViewForwardDir()
            {
                half4x4 viewMat = GetWorldToViewMatrix();
                return -viewMat[2].xyz;
            }

            // The fragment shader definition.            
            half4 frag(Varyings i) : SV_Target
            {
                // Defining the color variable and returning it.
                half4 customColor;
                half textureDepth;
                i.viewDirTS = normalize(i.viewDirTS);

                half depth = LinearEyeDepth(SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, (i.position.xy / _Parallax_Cloud_Rt_size.xy)).r,_ZBufferParams);
                half planeDepth = LinearEyeDepth(i.position.z,_ZBufferParams);
                half viewDotForward = abs(dot(normalize(i.viewDirWS), GetViewForwardDir()));
                depth -= planeDepth;
                depth /= viewDotForward;

                customColor = ParallaxOcclusionMapping(depth,i);

                // transform depth to view space.
                customColor.z *= viewDotForward;
                customColor.z += planeDepth;

                // get alpha.
                customColor.a = (customColor.a + baseAlpha) * alphaScale;
                customColor.a = saturate(customColor.a);

                return customColor;
            }
            ENDHLSL
        }

        Pass
        {
            Blend SrcAlpha OneMinusSrcAlpha

            // The HLSL code block. Unity SRP uses the HLSL language.
            HLSLPROGRAM
            // This line defines the name of the vertex shader. 
            #pragma vertex vert
            // This line defines the name of the fragment shader. 
            #pragma fragment frag
            
            // The Core.hlsl file contains definitions of frequently used HLSL
            // macros and functions, and also contains #include references to other
            // HLSL files (for example, Common.hlsl, SpaceTransforms.hlsl, etc.).
            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"   

            TEXTURE2D(_RenderTex); SAMPLER(sampler_RenderTex);
            TEXTURE2D_X_FLOAT(_CameraDepthTexture);     SAMPLER(sampler_CameraDepthTexture);

            half4 heightColor;
            half4 lightColor;
            half4 backlightColor;
            half4 lowColor;
            half colorWeight;
            half4 cloudColor;

            // The structure definition defines which variables it contains.
            // This example uses the Attributes structure as an input structure in
            // the vertex shader.
            struct Attributes
            {
                // The positionOS variable contains the vertex positions in object
                // space.
                float4 position   : POSITION;
            };

            struct Varyings
            {
                // The positions in this struct must have the SV_POSITION semantic.
                float4 position  : SV_POSITION;
                half3 viewDirWS : TEXCOORD1;
            };

            // The vertex shader definition with properties defined in the Varyings 
            // structure. The type of the vert function must match the type (struct)
            // that it returns.
            Varyings vert(Attributes i)
            {
                // Declaring the output object (OUT) with the Varyings struct.
                Varyings o;
                // The TransformObjectToHClip function transforms vertex positions
                // from object space to homogenous space
                VertexPositionInputs vertexInput = GetVertexPositionInputs(i.position.xyz);
                o.position = vertexInput.positionCS;
                return o;
            }

            // Returns the forward (central) direction of the current view in the world space.
            half3 GetViewForwardDir()
            {
                half4x4 viewMat = GetWorldToViewMatrix();
                return -viewMat[2].xyz;
            }

            half4 frag(Varyings i) : SV_Target
            {
                half2 uv = i.position.xy/_ScreenParams.xy;
                half4 preCloudValue = SAMPLE_TEXTURE2D(_RenderTex, sampler_RenderTex, uv);

                half3 NdLColor = lerp(backlightColor,lightColor,preCloudValue.x);
                half3 altitudeColor = lerp(heightColor,lowColor,preCloudValue.y);
                half3 finalColor = cloudColor * lerp(NdLColor,altitudeColor,colorWeight);
                finalColor *= _MainLightColor.rgb;

                half depth = LinearEyeDepth(SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_CameraDepthTexture, uv).r,_ZBufferParams);
                preCloudValue.w *= step(preCloudValue.z,depth);

                return half4(finalColor.xyz,preCloudValue.w);
            }

            ENDHLSL
        }
    }
}
