﻿Shader "Lux URP/FX/Box Volume"
{
    Properties
    {
        [HeaderHelpLuxURP_URL(t98mzd66fi0m)]

        [Header(Surface Options)]
        [Space(8)]
        [Enum(UnityEngine.Rendering.CompareFunction)]
        _ZTest                      ("ZTest", Int) = 8
        [Enum(UnityEngine.Rendering.CullMode)]
        _Cull                       ("Culling", Float) = 1
        [Toggle(ORTHO_SUPPORT)]
        _OrthoSpport                ("Enable Orthographic Support", Float) = 0

        [Header(Surface Inputs)]
        [Space(8)]
        _Color                      ("Color", Color) = (1,1,1,1)
        [Toggle(_ENABLEGRADIENT)]
        _EnableGradient             ("Enable Gradient", Float) = 0
        [NoScaleOffset]
        _MainTex                    ("     Vertical Gradient", 2D) = "white" {}

        [Header(Thickness Remap)]
        [Space(8)]
        _Lower                      ("     Lower", Range(0,1)) = 0
        _Upper                      ("     Upper", Range(0,4)) = 1
        //[Space(5)]
        //_SoftEdge                   ("     Soft Edge Factor", Float) = 2.0

        [Space(5)]
        [Toggle(_APPLYFOG)]
        _ApplyFog                   ("Enable Fog", Float) = 0.0
        [Toggle(_HQFOG)]
        _HQFog                      ("     HQ Fog", Float) = 0.0

    }
    SubShader
    {
        Tags
        {
            "RenderPipeline" = "UniversalPipeline"
            "RenderType"="Transparent"
            "Queue"="Transparent+50"
        }
        Pass
        {
            Name "StandardUnlit"
            Tags{"LightMode" = "UniversalForward"}

            Blend SrcAlpha OneMinusSrcAlpha

        //  As we want to be able to enter the volume we have to draw the back faces
            Cull [_Cull]
        //  We fully rely on the depth texture sample!
            ZTest [_ZTest]
            ZWrite Off

            HLSLPROGRAM
            // Required to compile gles 2.0 with standard srp library
            #pragma prefer_hlslcc gles
            #pragma exclude_renderers d3d11_9x
            #pragma target 2.0

            #pragma shader_feature_local _ENABLEGRADIENT
            #pragma shader_feature_local _APPLYFOG
            #pragma shader_feature_local ORTHO_SUPPORT

            // -------------------------------------
            // Lightweight Pipeline keywords

            // -------------------------------------
            // Unity defined keywords
            #if defined(_APPLYFOG)
                #pragma multi_compile_fog
                #pragma shader_feature_local _HQFOG
            #endif

            //--------------------------------------
            // GPU Instancing
            #pragma multi_compile_instancing
            
            #pragma vertex vert
            #pragma fragment frag

            #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
            #include "Packages/com.unity.render-pipelines.core/ShaderLibrary/Color.hlsl"
            #include "Packages/com.unity.render-pipelines.universal/Shaders/UnlitInput.hlsl"

            CBUFFER_START(UnityPerMaterial)
                half4 _Color;
                half _Lower;
                half _Upper;
                //half _SoftEdge;
            CBUFFER_END

            #if defined(_ENABLEGRADIENT)
                TEXTURE2D(_MainTex); SAMPLER(sampler_MainTex);
            #endif
            #if defined(SHADER_API_GLES)
                TEXTURE2D(_CameraDepthTexture); SAMPLER(sampler_CameraDepthTexture);
            #else
                TEXTURE2D_X_FLOAT(_CameraDepthTexture);
            #endif
            float4 _CameraDepthTexture_TexelSize;

            struct VertexInput
            {
                float4 vertex : POSITION;
                UNITY_VERTEX_INPUT_INSTANCE_ID
            };

            struct VertexOutput
            {
                float4 positionCS : SV_POSITION;
                float3 positionWS : TEXCOORD1;
                float2 projectedPosition : TEXCOORD2;
                float3 cameraPositionOS : TEXCOORD3;
                float scale : TEXCOORD4;
                
                #if defined(_APPLYFOG)
                    half fogCoord : TEXCOORD5;
                #endif

                float4 viewRayOS : TEXCOORD6;

                UNITY_VERTEX_INPUT_INSTANCE_ID
                UNITY_VERTEX_OUTPUT_STEREO
            };


            float IntersectRayBox(float3 rayOrigin, float3 rayDirection,
                  out float tEntr, out float tExit)
            {
                // Could be precomputed. Clamp to avoid INF. clamp() is a single ALU on GCN.
                // rcp(FLT_EPS) = 16,777,216, which is large enough for our purposes,
                // yet doesn't cause a lot of numerical issues associated with FLT_MAX.
                float3 rayDirInv = clamp(rcp(rayDirection), -rcp(FLT_EPS), rcp(FLT_EPS));

                // Perform ray-slab intersection (component-wise).
                float3 boxMin = float3(-0.5, -0.5, -0.5);
                float3 boxMax = float3( 0.5,  0.5,  0.5);

                float3 t0 = boxMin * rayDirInv - (rayOrigin * rayDirInv);
                float3 t1 = boxMax * rayDirInv - (rayOrigin * rayDirInv);

                // Find the closest/farthest distance (component-wise).
                float3 tSlabEntr = min(t0, t1);
                float3 tSlabExit = max(t0, t1);

                // Find the farthest entry and the nearest exit.
                tEntr = Max3(tSlabEntr.x, tSlabEntr.y, tSlabEntr.z);
                tExit = Min3(tSlabExit.x, tSlabExit.y, tSlabExit.z);

            //  When the camera is inside the volume we may get negative values so the box from behind the camera gets "mirrored" into the view.
            //  Using max(0, ) suppresses these artifacts.
                tEntr = max(0.0f, tEntr);
                return tExit - tEntr;
            }


            VertexOutput vert (VertexInput v)
            {
                VertexOutput o = (VertexOutput)0;
                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

            //  
                VertexPositionInputs vertexInput = GetVertexPositionInputs(v.vertex.xyz);
                o.positionCS = vertexInput.positionCS;
                o.projectedPosition = vertexInput.positionNDC.xy;
                o.positionWS = vertexInput.positionWS;
                o.cameraPositionOS = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1)).xyz;

                float4x4 ObjectToWorldMatrix = GetObjectToWorldMatrix();
                float3 worldScale = float3(
                    length(ObjectToWorldMatrix._m00_m10_m20), // scale x axis
                    length(ObjectToWorldMatrix._m01_m11_m21), // scale y axis
                    length(ObjectToWorldMatrix._m02_m12_m22)  // scale z axis
                );
                o.scale  = 1.0f / max(worldScale.x, max(worldScale.y, worldScale.z));
                #if defined(_APPLYFOG)
                    o.fogCoord = ComputeFogFactor(o.positionCS.z);
                #endif

                float4 positionVS = mul(UNITY_MATRIX_MV, v.vertex);
                float3 viewRayVS = positionVS.xyz;
                //  NOTE: Fix direction of the viewRay
                float4x4 ViewToObjectMatrix = mul(GetWorldToObjectMatrix(), UNITY_MATRIX_I_V);
                o.viewRayOS.xyz = mul((float3x3)ViewToObjectMatrix, -viewRayVS).xyz;
                //  positionVS.z here acts as view space to object space ratio (negative)
                o.viewRayOS.w = positionVS.z; 

                return o;
            }


            real LuxComputeFogFactor(float z)
            {
                float clipZ_01 = UNITY_Z_0_FAR_FROM_CLIPSPACE(z);

            #if defined(FOG_LINEAR)
                // factor = (end-z)/(end-start) = z * (-1/(end-start)) + (end/(end-start))
                float fogFactor = saturate(clipZ_01 * unity_FogParams.z + unity_FogParams.w);
                return real(fogFactor);
            #elif defined(FOG_EXP) || defined(FOG_EXP2)
                // factor = exp(-(density*z)^2)
                // -density * z computed at vertex
                return real(unity_FogParams.x * clipZ_01);
            #else
                return 0.0h;
            #endif
            }


        //  ------------------------------------------------------------------
        //  Helper functions to handle orthographic / perspective projection  

            inline float GetOrthoDepthFromZBuffer (float rawDepth) {
                #if defined(UNITY_REVERSED_Z)
                //  Needed to handle openGL
                    #if UNITY_REVERSED_Z == 1
                        rawDepth = 1.0f - rawDepth;
                    #endif
                #endif
                return lerp(_ProjectionParams.y, _ProjectionParams.z, rawDepth);
            }

            inline float GetProperEyeDepth (float rawDepth) {
                #if defined(ORTHO_SUPPORT)
                    float perspectiveSceneDepth = LinearEyeDepth(rawDepth, _ZBufferParams);
                    float orthoSceneDepth = GetOrthoDepthFromZBuffer(rawDepth);
                    return lerp(perspectiveSceneDepth, orthoSceneDepth, unity_OrthoParams.w);
                #else
                    return LinearEyeDepth(rawDepth, _ZBufferParams);
                #endif
            }


            half4 frag (VertexOutput input ) : SV_Target
            {
                UNITY_SETUP_INSTANCE_ID(input);
                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
                
                half4 color = half4(1,1,1,0);
    
                float3 rayDir = input.viewRayOS.xyz / input.viewRayOS.w;
                float3 rayStart = input.cameraPositionOS;

                float2 screenUV = input.projectedPosition.xy / input.positionCS.w;

            //  Fix screenUV for Single Pass Stereo Rendering
                #if defined(UNITY_SINGLE_PASS_STEREO)
                    screenUV.x = screenUV.x * 0.5f + (float)unity_StereoEyeIndex * 0.5f;
                #endif

                #if defined(SHADER_API_GLES)
                    float sceneZ = SAMPLE_DEPTH_TEXTURE_LOD(_CameraDepthTexture, sampler_CameraDepthTexture, screenUV, 0);
                #else
                    float sceneZ = LOAD_TEXTURE2D_X(_CameraDepthTexture, _CameraDepthTexture_TexelSize.zw * screenUV).x;
                #endif
                sceneZ = GetProperEyeDepth(sceneZ);
            
                float near;
                float far;
                float thickness = IntersectRayBox(rayStart, rayDir, near, far);

            //  Entry point in object space
                float3 entryOS = rayStart + rayDir * near;
                float distanceToEntryOS = length(entryOS - input.cameraPositionOS);

                float sceneDistanceOS = length(sceneZ * rayDir);
                float sceneToEntry = sceneDistanceOS - distanceToEntryOS;

            //  Nothing to do if the scene is in front of the entry point
                clip(sceneToEntry);

            //  Exit point in object space
                float3 exitOS = rayStart + rayDir * far;

                float maxTravel = distance(exitOS, entryOS);
                float denom = min(sceneToEntry, maxTravel);
                float percentage = maxTravel / denom;

                percentage = rcp(percentage);

                float alpha = thickness * input.scale * percentage;

            //  Smooth falloff
                alpha =  smoothstep(_Lower, _Upper, alpha);
            //  Scene blending - as otherwise we may get 1px wide artifacts at the borders.
                //alpha *= saturate(sceneToEntry / _SoftEdge );

            //  saturate eliminates artifacts at grazing angles
                color.a = saturate(alpha);

                #if defined(_ENABLEGRADIENT)
                    color.rgb = SAMPLE_TEXTURE2D(_MainTex, sampler_MainTex, float2(exitOS.y + 0.5, 0)).rgb;
                #endif

                color *= _Color;
                
            //  Here was a nasty bug!
                #if defined(_APPLYFOG)
                    #if defined(_HQFOG)
                        float3 exitFog = mul(GetObjectToWorldMatrix(), float4(rayStart + rayDir * far * sqrt(percentage), 1)).xyz;
                        float4 FogClipSpace = TransformWorldToHClip(exitFog);
                        float fogFactor = LuxComputeFogFactor( FogClipSpace.z); 
                        color.rgb = MixFog(color.rgb, fogFactor);
                    #else
                        color.rgb = MixFog(color.rgb, input.fogCoord);
                    #endif
                #endif

                return color;
            }
            ENDHLSL
        }
        
    }
    FallBack "Hidden/InternalErrorShader"
}
