Shader "Custom/SSGIShader"
{
   Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _AlbedoTex ("Texture", 2D) = "white" {}
        _PrevTex ("Texture", 2D) = "white" {}
    }
    SubShader
    {
        Cull Off ZWrite Off ZTest Always

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"

            #define PI 3.14159

            struct appdata {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };

            struct v2f {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
            };

            v2f vert (appdata v) {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                return o;
            }
              UNITY_DECLARE_DEPTH_TEXTURE( _CameraDepthTexture );


            sampler2D_half _CameraMotionVectorsTexture;
            sampler2D _MainTex;
            sampler2D _CameraDepthNormalsTexture;
            sampler2D _AlbedoTex;
            sampler2D _PrevTex;
            uniform float4 _MainTex_TexelSize;
            uniform int FramesSinceStart;
            float4x4 camtoworld;
            float4x4 caminvproj;
            float4x4 camproj;
            float4x4 worldtocam;
            float3 campos;
            void CreateCameraRay(float2 uv, inout float3 origin, inout float3 direction) {
                // Transform the camera origin to world space
                origin = mul(camtoworld, float4(0.0f, 0.0f, 0.0f, 1.0f)).xyz;

                // Invert the perspective projection of the view-space position
                direction = mul(caminvproj, float4(uv, 0.0f, 1.0f)).xyz;
                // Transform the direction from camera to world space and normalize
                direction = mul(camtoworld, float4(direction, 0.0f)).xyz;
                direction = normalize(direction);
            }

            float2 sample_disc(float u1, float u2) {
                float a = 2.0f * u1 - 1.0f;
                float b = 2.0f * u2 - 1.0f;
                if (a == 0.0f) a = 0.00001;
                if (b == 0.0f) b = 0.00001;

                float phi, r;
                if (a * a > b * b) {
                    r = a;
                    phi = (0.25f * PI) * (b / a);
                }
                else {
                    r = b;
                    phi = (0.25f * PI) * (a / b) + (0.5f * PI);
                }

                float sin_phi, cos_phi;
                sincos(phi, sin_phi, cos_phi);

                return float2(r * cos_phi, r * sin_phi);
            }

            float3 sample_cosine_weighted_direction(float u1, float u2) {
                float2 d = sample_disc(u1, u2);
                return float3(d.x, d.y, sqrt(abs(1.0f - dot(d, d))));
            }

            float3 GetWorldPos(float2 uv) {
                float depth = LinearEyeDepth( SAMPLE_DEPTH_TEXTURE( _CameraDepthTexture, uv) );
                float3 origin;
                float3 direction;
                CreateCameraRay(uv * 2.0f - 1.0f, origin, direction);
                return direction * depth + origin;
            }

            float3x3 GetTangentSpace(float3 normal) {
                // Choose a helper floattor for the cross product
                float3 helper = float3(1, 0, 0);
                if (abs(normal.x) > 0.99f)
                    helper = float3(0, 0, 1);

                // Generate floattors
                float3 tangent = normalize(cross(normal, helper));
                float3 binormal = cross(normal, tangent);

                return float3x3(tangent, binormal, normal);
            }

            uint hash_with(uint seed, uint hash) {
                // Wang hash
                seed = (seed ^ 61) ^ hash;
                seed += seed << 3;
                seed ^= seed >> 4;
                seed *= 0x27d4eb2d;
                return seed;
            }
            uint pcg_hash(uint seed) {
                uint state = seed * 747796405u + 2891336453u;
                uint word = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
                return (word >> 22u) ^ word;
            }

            float2 random(uint samdim, uint pixel_index) {
                    uint hash = pcg_hash((pixel_index * (uint)204 + samdim));

                    const static float one_over_max_unsigned = asfloat(0x2f7fffff);


                    float x = hash_with(FramesSinceStart, hash) * one_over_max_unsigned;
                    float y = hash_with(FramesSinceStart + 0xdeadbeef, hash) * one_over_max_unsigned;

                    return float2(x, y);
            }

            float2 viewSpaceToScreenSpace(float3 position) {
                position = normalize(position - campos)*(_ProjectionParams.y + (_ProjectionParams.z - _ProjectionParams.y))+campos;
                fixed3 toCam = mul(worldtocam, position);
                fixed camPosZ = toCam.z;
                fixed height = 2 * camPosZ / camproj._m11;
                fixed width = _ScreenParams.x / _ScreenParams.y * height;
                float2 uv;
                uv.x = (toCam.x + width / 2)/width;
                uv.y = (toCam.y + height / 2)/height;
                return 1.0f - uv;
            }

            float4 frag (v2f i) : SV_Target {   
                float2 uv = i.uv;
                float3 throughput = tex2D(_AlbedoTex,uv).xyz;
                int steps = 200;
                float3 Lum = 0;//max(tex2D(_MainTex, uv).xyz - tex2D(_AlbedoTex,uv).xyz,0);
                float2 motionvectors = tex2D(_CameraMotionVectorsTexture, uv);
                float3 PrevCol = tex2D(_PrevTex, uv - motionvectors).xyz;
                float3 origin;
                float3 direction;
                CreateCameraRay(uv * 2.0f - 1.0f, origin, direction);
                for(int i2 = 0; i2 < 1; i2++) {
                float3 CenterPos = GetWorldPos(uv);
                float depth = LinearEyeDepth( SAMPLE_DEPTH_TEXTURE( _CameraDepthTexture, uv));
                // float3 XOffPos = 
                float3 normal = normalize(mul((float3x3)camtoworld, DecodeViewNormalStereo(tex2D(_CameraDepthNormalsTexture, uv))));
                float2 rand = random(53 + i2, (i.uv.x * 1920) + (i.uv.y * 1080) * 1920);
                direction = reflect(direction, normal);//normalize(mul(sample_cosine_weighted_direction(rand.x,rand.y), GetTangentSpace(normal)));
                // direction *= 3 / float(steps);


                // CenterPos += normal * 0.1f;
                CenterPos += normal * 0.01f;
                    for(int i = 1; i < steps; i++) {
                        float m = exp(pow(float(i) / 4.0, 0.05)) - 2.0;
                        CenterPos += direction * min(m, 1.);
                        float2 uv2 = viewSpaceToScreenSpace(CenterPos);
                        float2 newDepth = LinearEyeDepth( SAMPLE_DEPTH_TEXTURE( _CameraDepthTexture, uv2));
                        float depthdiff = newDepth - length(CenterPos - origin);
                        if(any(uv2 < 0 || uv2 > 1)) break;
                        if(depthdiff >= 0 && depthdiff < 0.2f) {
                            throughput *= tex2D(_AlbedoTex, uv2).xyz;
                            Lum = max(tex2D(_MainTex, uv2).xyz,0);
                            uv = uv2;
                            break;
                        }

                    }
                }

                return float4(lerp(Lum, PrevCol, 0.64f) , 1);
            }
            ENDCG
        }
    }
}