Shader "Hidden/Ilumisoft/PostProcessing/Outline"
{
    HLSLINCLUDE
    
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/Core.hlsl"
        #include "Packages/com.unity.render-pipelines.core/Runtime/Utilities/Blit.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareDepthTexture.hlsl"
        #include "Packages/com.unity.render-pipelines.universal/ShaderLibrary/DeclareNormalsTexture.hlsl"

        float4 _OutlineColor;
        float4 _BackgroundColor;
        float _BackgroundColorOpacity;
        float _ReferenceHeight;
        int _OutlineThickness;
        int _IsFadeEnabled;
        float _FadeStart;
        float _FadeEnd;
        float2 _DepthEdge;
        float2 _NormalEdge;
        float _LuminancePower;
        float _LuminanceContrast;

        // Samples the scene normals remapped to [0,1]
        inline float3 SampleNormal(float2 uv)
        {
            return SampleSceneNormals(uv) * 0.5 + 0.5;
        }

        // Samples the scene color
        inline float3 SampleColor(float2 uv)
        {
            return SAMPLE_TEXTURE2D(_BlitTexture,sampler_LinearClamp,  uv).rgb;
        }

        // Samples the scene depth linear01
        inline float SampleDepthLinear01(float2 uv)
        {
            return Linear01Depth(SampleSceneDepth(uv),_ZBufferParams);
        }

        float GetNormalOutline(float3 topLeft, float3 bottomRight, float3 topRight, float3 bottomLeft)
        {
            float3 delta = abs(topLeft-bottomRight) + abs(topRight-bottomLeft);

            float normalOutline = saturate(max(delta.r, max(delta.g, delta.b)));

            return smoothstep(_NormalEdge.x, _NormalEdge.y, normalOutline);
        }

        float GetDepthOutline(float topLeft, float bottomRight, float topRight, float bottomLeft)
        {
            float result = abs(topLeft - bottomRight) + abs(topRight - bottomLeft);
            
            result /= max(topLeft, max(bottomRight, max(topRight, bottomLeft)));

            return smoothstep(_DepthEdge.x, _DepthEdge.y, saturate(result));
        }

        inline float InverseLerp(float a, float b, float t)
        {
            return saturate((t-a)/(b-a));
        }

        float LuminancePowerContrast(float3 color, float power, float contrast)
        {
            float luminance = pow(saturate(Luminance(color)), power);
            float midpoint = pow(0.5, 2.2);
            return saturate((luminance - midpoint) * contrast + midpoint);
        }

        float4 OutlineDepthOnly (Varyings input) : SV_Target
        {
            float2 offset = _OutlineThickness * _BlitTexture_TexelSize.xy*_ScreenParams.y/_ReferenceHeight;
            float2 centerUV = input.texcoord;
            
            // Sample color and depth of the current pixel
            float3 centerColor = SampleColor(centerUV);
            float centerDepth = SampleSceneDepth(centerUV);
            float distance = LinearEyeDepth(centerDepth,_ZBufferParams);
            centerDepth = Linear01Depth(centerDepth,_ZBufferParams);   

            float cornerDepths[4];
            float2 cornerUVs[4];

            // Create corner UVs
            cornerUVs[0] = centerUV + float2(-offset.x, offset.y);
            cornerUVs[1] = centerUV - float2(-offset.x, offset.y);
            cornerUVs[2] = centerUV + offset;
            cornerUVs[3] = centerUV - offset;

            [unroll]
            for(int i=0; i<4; i++)
            {
                // depth
                cornerDepths[i] = SampleDepthLinear01(cornerUVs[i]);

                // Discard values with a lower depth
                cornerDepths[i] = (cornerDepths[i]>centerDepth) ? cornerDepths[i] : centerDepth;
            }

            // Compute outline
            float outline = GetDepthOutline(cornerDepths[0], cornerDepths[1], cornerDepths[2], cornerDepths[3]);

            // Apply distance fade
            outline *= 1.0 - InverseLerp(_FadeStart, _FadeEnd, distance) * _IsFadeEnabled;

            // Apply background color fill
            float3 color = LuminancePowerContrast(centerColor, _LuminancePower, _LuminanceContrast) * _BackgroundColor.rgb;
            color = lerp(centerColor, color, _BackgroundColorOpacity);

            // Apply outline
            color = lerp(color, _OutlineColor.rgb, outline*_OutlineColor.a);

            return float4(color, 1.0);
        }

        float4 OutlineDepthNormal (Varyings input) : SV_Target
        {
            float2 offset = _OutlineThickness * _BlitTexture_TexelSize.xy*_ScreenParams.y/_ReferenceHeight;
            float2 centerUV = input.texcoord;
            
            // Sample color, depth and normal of the current pixel
            float3 centerNormal = SampleNormal(centerUV);
            float3 centerColor = SampleColor(centerUV);
            float centerDepth = SampleSceneDepth(centerUV);
            float distance = LinearEyeDepth(centerDepth,_ZBufferParams);
            centerDepth = Linear01Depth(centerDepth,_ZBufferParams);

            float3 cornerNormals[4];
            float cornerDepths[4];
            float2 cornerUVs[4];

            // Create corner UVs
            cornerUVs[0] = centerUV + float2(-offset.x, offset.y);
            cornerUVs[1] = centerUV - float2(-offset.x, offset.y);
            cornerUVs[2] = centerUV + offset;
            cornerUVs[3] = centerUV - offset;

            [unroll]
            for(int i=0; i<4; i++)
            {
                // sample normal and depth
                cornerNormals[i] = SampleNormal(cornerUVs[i]);
                cornerDepths[i] = SampleDepthLinear01(cornerUVs[i]);

                // Discard values with a lower depth
                cornerNormals[i] = (cornerDepths[i]>centerDepth) ? cornerNormals[i] : centerNormal;
                cornerDepths[i] = (cornerDepths[i]>centerDepth) ? cornerDepths[i] : centerDepth;
            }

            // Compute outline
            float normalOutline = GetNormalOutline(cornerNormals[0], cornerNormals[1], cornerNormals[2], cornerNormals[3]);

            float depthOutline = GetDepthOutline(cornerDepths[0], cornerDepths[1], cornerDepths[2], cornerDepths[3]);

            float outline = max(normalOutline, depthOutline);

            // Apply distance fade
            outline *= 1.0 - InverseLerp(_FadeStart, _FadeEnd, distance) * _IsFadeEnabled;

            // Apply background color fill
            float3 color = LuminancePowerContrast(centerColor, _LuminancePower, _LuminanceContrast) * _BackgroundColor.rgb;
            color = lerp(centerColor, color, _BackgroundColorOpacity);

            // Apply outline
            color = lerp(color, _OutlineColor.rgb, outline*_OutlineColor.a);

            return float4(color, 1.0);
        }

    ENDHLSL
    
    SubShader
    {
        Tags { "RenderType"="Opaque" "RenderPipeline" = "UniversalPipeline"}
        LOD 100
        ZWrite Off Cull Off

        Pass
        {
            Name "OutlineDepthOnlyPass"

            HLSLPROGRAM
            
            #pragma vertex Vert
            #pragma fragment OutlineDepthOnly
            
            ENDHLSL
        }

        Pass
        {
            Name "OutlineDepthNormalPass"

            HLSLPROGRAM
            
            #pragma vertex Vert
            #pragma fragment OutlineDepthNormal
            
            ENDHLSL
        }
    }
}
