﻿Shader "Hidden/ZibraEffects/Liquids/VisualizeSDF"
{
    SubShader
    {
        Pass
        {
            Cull Off
            ZWrite Off
            ZTest Always

            HLSLPROGRAM
            #pragma multi_compile_local __ HDRP
            #pragma instancing_options procedural:setup
            #pragma vertex VSMain
            #pragma fragment PSMain
            #pragma target 3.5

            #include "UnityCG.cginc"
            #include "UnityStandardBRDF.cginc"
            #include "UnityImageBasedLighting.cginc"

            #define MAX_VIEWING_DISTANCE 1e8

#ifdef HDRP
            float3 LightDirection;
#endif

            struct VSIn
            {
                uint vertexID : SV_VertexID;
            };

            struct VSOut
            {
                float4 position : POSITION;
                float3 raydir : TEXCOORD1;
                float2 uv : TEXCOORD0;
            };

            struct PSOut
            {
                float4 color : COLOR;
            };

            float4x4 EyeRayCameraCoeficients;
            float2 TextureScale;

            sampler2D SDFRender;

            // built-in Unity sampler name - do not change
            sampler2D _CameraDepthTexture;

            float2 GetFlippedUV(float2 uv)
            {
                if (_ProjectionParams.x > 0)
                    return float2(uv.x, 1 - uv.y);
                return uv;
            }

            // See Raytracing Gems 1 chapter 20.3.2.1
            float3 GetCameraRay(float2 uv)
            {
                float2 c = float2(2.0f * uv.x - 1.0f, -2.0f * uv.y + 1.0f);

                float3 r = EyeRayCameraCoeficients[0].xyz;
                float3 u = EyeRayCameraCoeficients[1].xyz;
                float3 v = EyeRayCameraCoeficients[2].xyz;

                float3 direction = c.x * r + c.y * u + v;
                return normalize(direction);
            }

            VSOut VSMain(VSIn input)
            {
                VSOut output;

                float2 vertexBuffer[4] = {
                    float2(0.0f, 0.0f),
                    float2(0.0f, 1.0f),
                    float2(1.0f, 0.0f),
                    float2(1.0f, 1.0f),
                };
                uint indexBuffer[6] = { 0, 1, 2, 2, 1, 3 };
                uint indexID = indexBuffer[input.vertexID];

                float2 uv = vertexBuffer[indexID];
                float2 flippedUV = GetFlippedUV(uv);

                output.position = float4(2 * flippedUV.x - 1, 1 - 2 * flippedUV.y, 0.5, 1.0);
                output.uv = flippedUV;
                output.raydir = GetCameraRay(uv);

                return output;
            }

            float PositionToDepth(float3 pos)
            {
                float4 clipPos = mul(UNITY_MATRIX_VP, float4(pos, 1));
                return (1.0 / clipPos.w - _ZBufferParams.w) / _ZBufferParams.z; //inverse of linearEyeDepth
            }

            float4 RenderSDFSurface(float3 cameraPos, float3 cameraRay, float2 uv)
            {
                float4 sdfout = tex2D(SDFRender, GetFlippedUV(uv * TextureScale));
                float3 sdfPos = cameraPos + cameraRay * sdfout.w;
                float sdfDepth =  PositionToDepth(sdfPos);

                if (sdfout.w < MAX_VIEWING_DISTANCE)
                {
                       // lighting vectors:
                    float3 worldView = -cameraRay;
            #ifdef HDRP
                    float3 lightDirWorld = LightDirection;
            #else
                    float3 lightDirWorld = normalize(_WorldSpaceLightPos0.xyz);
            #endif

                    float3 normal = sdfout.xyz;
                    half3 h = normalize(lightDirWorld + worldView);
                    float nh = BlinnTerm(normal, h);
                    float nl = DotClamped(normal, lightDirWorld);
                    float nv = dot(normal, worldView); 
                    float rough = 0.55;
                    half V = SmithBeckmannVisibilityTerm(nl, nv, rough);
                    half D = NDFBlinnPhongNormalizedTerm(nh, RoughnessToSpecPower(rough));
                    float spec = (V * D) * (UNITY_PI / 4);
                    spec = max(0, spec * nl);

                    return float4(spec + (normal*0.5 + 0.5)*(dot(lightDirWorld, normal)*0.5 + 0.5), sdfDepth);
                }

                return 0.0;
            }
          
            float4 MinIntersection(float4 a, float4 b)
            {
                return (a.w > b.w) ? a : b;
            }

            PSOut PSMain(VSOut input)
            {
                PSOut output;

                float sceneDepth = tex2D(_CameraDepthTexture, input.uv).x;
#if !defined(UNITY_REVERSED_Z)
                sceneDepth = 1.0 - sceneDepth;
#endif

                float3 cameraPos = _WorldSpaceCameraPos;
                float3 cameraRay = normalize(input.raydir);
                
                float4 intersection = RenderSDFSurface(cameraPos, cameraRay, input.uv);

                if (intersection.w == 0.0)
                {
                    //didn't hit anything
                    discard;
                }

                // Move visualization tiny bit closer to the camera
                // To reduce Z fighting
                const float depthOffset = 0.0001f;

                if (intersection.w + depthOffset < sceneDepth)
                {
                    discard;
                }

                output.color = float4(intersection.rgb, 1.0);
                return output;
            }
            ENDHLSL
        }
    }
}
