﻿Shader "Mochie/Underwater Visuals" {
    Properties {
        
        // Base
        [Enum(Screen Space,0, World Space,1)]_RenderMode("Render Mode", Int) = 0
        _Color("Screen Tint", Color) = (1,1,1,1)
        [IntRange]_StencilRef("Stencil Reference", Range(1,255)) = 65

        // Blur
        [Toggle(DOF_ENABLED)]_DoFToggle("Enable", Int) = 1
        [Toggle(DEPTH_ENABLED)]_DepthToggle("Depth of Field", Int) = 1
        [Toggle(HIGH_QUALITY_BLUR)]_HQBlur("High Quality", Int) = 0
        _BlurStr("Strength", Float) = 1.3
        _Radius("Vision Radius", Float) = 1
        _Fade("Fade", Float) = 1.25

        // Caustics
        [Toggle(CAUSTICS_ENABLED)]_CausticsToggle("Enable", Int) = 1
        [HideInInspector]_NormalMap("Normal Map", 2D) = "bump" {}
        [Enum(Voronoi,0, Texture,1, Flipbook,2)]_CausticsMode("Caustics Style", Int) = 0
        _CausticsTex("Caustics Texture", 2D) = "black" {}
        _CausticsTexArray("Texture Array", 2DArray) = "black" {}
        _CausticsDisp("Dispersion", Float) = 0.25
        _CausticsFlipbookDisp("Dispersion", Float) = 0.6
        _CausticsDistortion("Distortion", Float) = 0.1
        _CausticsDistortionTex("Distortion Texture", 2D) = "bump" {}
        _CausticsDistortionScale("Distortion Scale", Float) = 1
        _CausticsDistortionSpeed("Distortion Speed", Vector) = (-0.1, -0.1,0,0)
        _CausticsColor("Color", Color) = (1,1,1,1)
        _CausticsOpacity("Opacity", Float) = .5
        _CausticsPower("Power", Float) = 1
        _CausticsThreshold("Threshold", Float) = 0
        _CausticsScale("Scale", Float) = 10
        _CausticsSpeed("Speed", Float) = 3
        _CausticsFade("Depth Fade", Float) = 5
        _CausticsRotation("Rotation", Vector) = (-20,0,20,0)
        _CausticsSurfaceFade("Surface Fade", Float) = 100
        _CausticsFlipbookSpeed("Flipbook Speed", Float) = 16

        // Fog
        [Toggle(FOG_ENABLED)]_FogToggle("Enable", Int) = 1
        [HDR]_FogTint("Tint", Color) = (0.11,0.26,0.26,1)
        _FogOpacity("Opacity", Range(0,1)) = 0.8
        _FogRadius("Radius", Float) = 1.7
        _FogFade("Fade", Float) = 3

        [HideInInspector]_NaNLmao("", float) = 0
        [HideInInspector]_MaterialResetCheck("Reset", Int) = 0
        
    }
    SubShader {
        Tags {
            "RenderType"="Transparent" 
            "Queue"="Transparent+1" 
            "ForceNoShadowCasting"="True" 
            "IgnoreProjector"="True"
        }
        Cull Front
        ZTest Always
        ZWrite Off
        Pass {
            Tags {"LightMode"="ForwardBase"}
            Blend One One
            Stencil {
                Ref [_StencilRef]
                Comp NotEqual
                Pass Keep
                Fail Keep
                ZFail Keep
            }
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma shader_feature_local CAUSTICS_ENABLED
            #pragma shader_feature_local _ _CAUSTICS_VORONOI_ON _CAUSTICS_TEXTURE_ON _CAUSTICS_FLIPBOOK_ON
            #include "UnityCG.cginc"
            #include "../Common/Sampling.cginc"

            MOCHIE_DECLARE_TEX2D_SCREENSPACE(_CameraDepthTexture);
            float4 _CameraDepthTexture_TexelSize;
            #define HAS_DEPTH_TEXTURE
            #include "../Common/Utilities.cginc"
            #include "../Common/Noise.cginc"

            MOCHIE_DECLARE_TEX2D(_CausticsTex);
            MOCHIE_DECLARE_TEX2D(_CausticsDistortionTex);
            MOCHIE_DECLARE_TEX2DARRAY(_CausticsTexArray);
            float4 _CausticsTex_TexelSize;
            float _CausticsFlipbookSpeed;
            float _CausticsDisp;
            float _CausticsDistortion;
            float _CausticsDistortionScale;
            float2 _CausticsDistortionSpeed;
            float3 _CausticsRotation;
            float _CausticsSurfaceFade;
            float3 _CausticsColor;
            float _CausticsScale;
            float _CausticsSpeed;
            float _CausticsPower;
            float _CausticsOpacity;
            float _CausticsFade;
            float _CausticsFlipbookDisp;

            sampler2D _NormalMap;
            float _RenderMode;
            float _NaNLmao;

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

            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 raycast : TEXCOORD1;
                float4 localPos : TEXCOORD2;
                UNITY_VERTEX_INPUT_INSTANCE_ID 
                UNITY_VERTEX_OUTPUT_STEREO
            };
            
            v2f vert (appdata v){
                v2f o = (v2f)0;

                #if defined(SHADER_API_MOBILE)
                    v.vertex = 0/_NaNLmao;
                #endif

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                // if (_RenderMode == 0){
                    // v.vertex.xyz = Rotate3D(v.vertex.xyz, float3(0,180,0));
                    // v.vertex.z += 0.1;
                    v.vertex.x *= 1.4;
                    float4 wPos = mul(unity_CameraToWorld, v.vertex);
                    float4 oPos = mul(unity_WorldToObject, wPos);
                    o.localPos = oPos;
                    o.raycast = UnityObjectToViewPos(oPos).xyz * float3(-1,-1,1);
                    o.pos = UnityObjectToClipPos(oPos);
                // }
                // else {
                // 	o.pos = UnityObjectToClipPos(v.vertex);
                // 	o.localPos = v.vertex;
                // }
                o.uv = ComputeGrabScreenPos(o.pos);

                return o;
            }

            float GetDepth(v2f i, float2 screenUV){
                float backgroundDepth = LinearEyeDepth(MOCHIE_SAMPLE_TEX2D_SCREENSPACE(_CameraDepthTexture, screenUV));
                float surfaceDepth = UNITY_Z_0_FAR_FROM_CLIPSPACE(i.uv.z);
                float depthDifference = backgroundDepth - surfaceDepth;
                return depthDifference / 20;
            }
            
            float2 GetScreenUV(v2f i){
                float2 screenUV = i.uv.xy / i.uv.w; 
                #if UNITY_UV_STARTS_AT_TOP
                    if (_CameraDepthTexture_TexelSize.y < 0) {
                        screenUV.y = 1 - screenUV.y;
                    }
                #endif
                screenUV.y = _ProjectionParams.x * .5 + .5 - screenUV.y * _ProjectionParams.x;
                return screenUV;
            }

            float4 frag (v2f i) : SV_Target {

                #if defined(SHADER_API_MOBILE)
                    discard;
                #endif

                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

                float4 col = 0;
                #ifdef CAUSTICS_ENABLED
                    MirrorCheck();
                    float2 screenUV = GetScreenUV(i);
                    float caustDepth = saturate(1-GetDepth(i, screenUV));
                    float caustFade = saturate(pow(caustDepth, _CausticsFade));
                    #if defined(_CAUSTICS_VORONOI_ON)
                        if (caustFade > 0){
                            float3 wPos = GetWorldSpacePixelPosSP(i.localPos, screenUV);
                            float2 depthUV = Rotate3D(wPos, _CausticsRotation).xz;
                            float3 causticsOffset = UnpackNormal(tex2D(_NormalMap, (depthUV*_CausticsDistortionScale*0.1)+_Time.y*_CausticsDistortionSpeed*0.05));
                            float2 causticsUV = (depthUV + (causticsOffset.xy * _CausticsDistortion)) * _CausticsScale;
                            float voronoi0 = Voronoi2D(causticsUV, _Time.y*_CausticsSpeed);
                            float voronoi1 = Voronoi2D(causticsUV, (_Time.y*_CausticsSpeed)+_CausticsDisp);
                            float voronoi2 = Voronoi2D(causticsUV, (_Time.y*_CausticsSpeed)+_CausticsDisp*2.0);
                            float3 voronoi = float3(voronoi0, voronoi1, voronoi2);
                            voronoi = pow(voronoi, _CausticsPower);
                            float3 caustics = smootherstep(0, 1, voronoi) * _CausticsOpacity * caustFade * _CausticsColor;
                            col.rgb += caustics;
                        }
                    #elif defined(_CAUSTICS_TEXTURE_ON)
                        if (caustFade > 0){
                            float3 wPos = GetWorldSpacePixelPosSP(i.localPos, screenUV);
                            float2 depthUV = Rotate3D(wPos, _CausticsRotation).xz;
                            float3 causticsOffset = UnpackNormal(tex2D(_NormalMap, (depthUV*_CausticsDistortionScale*0.1)+_Time.y*_CausticsDistortionSpeed*0.05));
                            float2 causticsUV = (depthUV + (causticsOffset.xy * _CausticsDistortion)) * _CausticsScale / 5.0;
                            causticsUV *= 0.2;
                            _CausticsSpeed *= 0.05;
                            _CausticsOpacity *= 10;
                            float2 uvTex0 = causticsUV +_Time.y * _CausticsSpeed * 0.4;
                            float2 uvTex1 = causticsUV * -1 + _Time.y * _CausticsSpeed * 0.2;
                            float3 tex0 = MOCHIE_SAMPLE_TEX2D(_CausticsTex, uvTex0);
                            float3 tex1 = MOCHIE_SAMPLE_TEX2D(_CausticsTex, uvTex1);
                            float3 caustics = min(tex0, tex1);
                            caustics = clamp(caustics, 0, 0.1);
                            col.rgb += caustics * _CausticsOpacity;
                        }
                    #elif defined(_CAUSTICS_FLIPBOOK_ON)
                        if (caustFade > 0){
                            float3 wPos = GetWorldSpacePixelPosSP(i.localPos, screenUV);
                            float2 depthUV = Rotate3D(wPos, _CausticsRotation).xz;
                            float2 causticsUV = depthUV * _CausticsScale / 7.0;
                            _CausticsFlipbookSpeed *= 0.8;
                            float causticsR = tex2DflipbookSmooth(_CausticsTexArray, sampler_CausticsTexArray, causticsUV * 0.35, _CausticsFlipbookSpeed).r;
                            float causticsG = tex2DflipbookSmooth(_CausticsTexArray, sampler_CausticsTexArray, causticsUV * 0.35, _CausticsFlipbookSpeed + (0.0005 * _CausticsFlipbookDisp)).g;
                            float causticsB = tex2DflipbookSmooth(_CausticsTexArray, sampler_CausticsTexArray, causticsUV * 0.35, _CausticsFlipbookSpeed + (0.0005 * _CausticsFlipbookDisp * 1.5)).b;
                            float3 caustics = float3(causticsR, causticsG, causticsB);
                            caustics = smootherstep(0.15, 1, caustics);
                            col.rgb += caustics;
                        }
                    #endif
                #else
                    discard;
                #endif

                return float4(col.rgb, 1);
            }

            ENDCG
        }

        Pass {
            Tags {"LightMode"="ForwardBase"}
            Blend SrcAlpha OneMinusSrcAlpha
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma shader_feature_local FOG_ENABLED
            #include "UnityCG.cginc"
            #include "../Common/Sampling.cginc"

            MOCHIE_DECLARE_TEX2D_SCREENSPACE(_CameraDepthTexture);
            float4 _CameraDepthTexture_TexelSize;
            float4 _FogTint;
            float _FogRadius;
            float _FogFade;
            float _FogOpacity;
            float _RenderMode;
            float _NaNLmao;

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

            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 cameraPos : TEXCOORD1;
                float3 raycast : TEXCOORD2;
                UNITY_VERTEX_INPUT_INSTANCE_ID 
                UNITY_VERTEX_OUTPUT_STEREO
            };
            
            float2x2 GetRotationMatrix(float axis){
                float c, s, ang;
                ang = (axis+90) * (UNITY_PI/180.0);
                sincos(ang, c, s);
                float2x2 mat = float2x2(c,-s,s,c);
                mat = ((mat*0.5)+0.5)*2-1;
                return mat;
            }
            float3 Rotate3D(float3 coords, float3 axis){
                coords.xy = mul(GetRotationMatrix(axis.x), coords.xy);
                coords.xz = mul(GetRotationMatrix(axis.y), coords.xz);
                coords.yz = mul(GetRotationMatrix(axis.z), coords.yz);
                return coords;
            }

            v2f vert (appdata v){
                v2f o = (v2f)0;

                #if defined(SHADER_API_MOBILE)
                    v.vertex = 0/_NaNLmao;
                #endif

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.cameraPos = _WorldSpaceCameraPos;
                #if UNITY_SINGLE_PASS_STEREO
                    o.cameraPos = (unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5;
                #endif

                // if (_RenderMode == 0){
                    // v.vertex.xyz = Rotate3D(v.vertex.xyz, float3(0,180,0));
                    // v.vertex.z += 0.1;
                    v.vertex.x *= 1.4;
                    float4 wPos = mul(unity_CameraToWorld, v.vertex);
                    float4 oPos = mul(unity_WorldToObject, wPos);
                    o.raycast = UnityObjectToViewPos(oPos).xyz * float3(-1,-1,1);
                    o.pos = UnityObjectToClipPos(oPos);
                // }
                // else {
                // 	o.pos = UnityObjectToClipPos(v.vertex);
                // }
                o.uv = ComputeGrabScreenPos(o.pos);
                return o;
            }

            float GetRadius(v2f i, float2 screenUV){
                float depth = Linear01Depth(DecodeFloatRG(MOCHIE_SAMPLE_TEX2D_SCREENSPACE(_CameraDepthTexture, screenUV)));
                i.raycast *= (_ProjectionParams.z / i.raycast.z);
                float4 vPos = float4(i.raycast * depth, 1);
                float3 wPos = mul(unity_CameraToWorld, vPos).xyz;
                float dist = distance(wPos, i.cameraPos);
                return 1-smoothstep(_FogRadius, _FogRadius-_FogFade, dist);
            }
            
            float2 GetScreenUV(v2f i){
                float2 screenUV = i.uv.xy / i.uv.w; 
                #if UNITY_UV_STARTS_AT_TOP
                    if (_CameraDepthTexture_TexelSize.y < 0) {
                        screenUV.y = 1 - screenUV.y;
                    }
                #endif
                screenUV.y = _ProjectionParams.x * .5 + .5 - screenUV.y * _ProjectionParams.x;
                return screenUV;
            }
            
            void MirrorCheck(){
                if (unity_CameraProjection[2][0] != 0.0f || unity_CameraProjection[2][1] != 0.0f) discard;
            }

            float4 frag (v2f i) : SV_Target {

                #if defined(SHADER_API_MOBILE)
                    discard;
                #endif

                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

                float4 fogCol = 0;
                #ifdef FOG_ENABLED
                    MirrorCheck();
                    float2 screenUV = GetScreenUV(i);
                    float radius = GetRadius(i, screenUV); 
                    fogCol = float4(_FogTint.rgb * radius, _FogOpacity * radius);
                #else
                    discard;
                #endif

                return fogCol;
            }
            ENDCG
        }

        GrabPass{
            "_DoFGrab"
            Tags {"LightMode"="Always"}
        }

        Pass {
            Tags {"LightMode"="Always"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #pragma multi_compile_instancing
            #pragma shader_feature_local DOF_ENABLED
            #pragma shader_feature_local HIGH_QUALITY_BLUR
            #pragma shader_feature_local DEPTH_ENABLED
            #include "UnityCG.cginc"
            #include "../Common/Sampling.cginc"

            #ifdef DEPTH_ENABLED
                MOCHIE_DECLARE_TEX2D_SCREENSPACE(_CameraDepthTexture);
                float4 _CameraDepthTexture_TexelSize;
            #endif
            MOCHIE_DECLARE_TEX2D_SCREENSPACE(_DoFGrab);
            float _Radius, _Fade, _BlurStr;
            
            float4 _Color;
            float _RenderMode;
            float _NaNLmao;
            
            #include "WaterBlurKernels.cginc"

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

            struct v2f {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 cameraPos : TEXCOORD1;
                float3 raycast : TEXCOORD2;
                UNITY_VERTEX_INPUT_INSTANCE_ID 
                UNITY_VERTEX_OUTPUT_STEREO
            };

            float2x2 GetRotationMatrix(float axis){
                float c, s, ang;
                ang = (axis+90) * (UNITY_PI/180.0);
                sincos(ang, c, s);
                float2x2 mat = float2x2(c,-s,s,c);
                mat = ((mat*0.5)+0.5)*2-1;
                return mat;
            }
            float3 Rotate3D(float3 coords, float3 axis){
                coords.xy = mul(GetRotationMatrix(axis.x), coords.xy);
                coords.xz = mul(GetRotationMatrix(axis.y), coords.xz);
                coords.yz = mul(GetRotationMatrix(axis.z), coords.yz);
                return coords;
            }

            v2f vert (appdata v){
                v2f o = (v2f)0;

                #if defined(SHADER_API_MOBILE)
                    v.vertex = 0/_NaNLmao;
                #endif

                UNITY_SETUP_INSTANCE_ID(v);
                UNITY_TRANSFER_INSTANCE_ID(v, o);
                UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o);

                o.cameraPos = _WorldSpaceCameraPos;
                #if UNITY_SINGLE_PASS_STEREO
                    o.cameraPos = (unity_StereoWorldSpaceCameraPos[0] + unity_StereoWorldSpaceCameraPos[1]) * 0.5;
                #endif

                // if (_RenderMode == 0){
                    // v.vertex.xyz = Rotate3D(v.vertex.xyz, float3(0,180,0));
                    // v.vertex.z += 0.1;
                    v.vertex.x *= 1.4;
                    float4 wPos = mul(unity_CameraToWorld, v.vertex);
                    float4 oPos = mul(unity_WorldToObject, wPos);
                    #ifdef DEPTH_ENABLED
                        o.raycast = UnityObjectToViewPos(oPos).xyz * float3(-1,-1,1);
                    #endif
                    o.pos = UnityObjectToClipPos(oPos);
                // }
                // else {
                // 	o.pos = UnityObjectToClipPos(v.vertex);
                // }
                o.uv = ComputeGrabScreenPos(o.pos);
                return o;
            }
            
            void MirrorCheck(){
                if (unity_CameraProjection[2][0] != 0.0f || unity_CameraProjection[2][1] != 0.0f) discard;
            }

            #ifdef DEPTH_ENABLED
                float GetRadius(v2f i){
                    float2 screenUV = i.uv.xy/i.uv.w;
                    #if UNITY_UV_STARTS_AT_TOP
                        if (_CameraDepthTexture_TexelSize.y < 0) {
                            screenUV.y = 1 - screenUV.y;
                        }
                    #endif
                    screenUV.y = _ProjectionParams.x * .5 + .5 - screenUV.y * _ProjectionParams.x;
                    float depth = Linear01Depth(DecodeFloatRG(MOCHIE_SAMPLE_TEX2D_SCREENSPACE(_CameraDepthTexture, screenUV)));
                    i.raycast *= (_ProjectionParams.z / i.raycast.z);
                    float4 vPos = float4(i.raycast * depth, 1);
                    float3 wPos = mul(unity_CameraToWorld, vPos).xyz;
                    float dist = distance(wPos, i.cameraPos);
                    return 1-smoothstep(_Radius, _Radius-_Fade, dist);
                }
            #endif

            float4 frag (v2f i) : SV_Target {
                
                #if defined(SHADER_API_MOBILE)
                    discard;
                #endif

                UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(i);

                float4 blurCol = 0;
                #ifdef DOF_ENABLED
                    MirrorCheck();
                    float2 blurStr = _BlurStr * 0.01;
                    #ifdef DEPTH_ENABLED
                        blurStr *= GetRadius(i);
                    #endif
                    blurStr.x *= 0.5625;
                    #if UNITY_SINGLE_PASS_STEREO || defined(UNITY_STEREO_INSTANCING_ENABLED) || defined(UNITY_STEREO_MULTIVIEW_ENABLED)
                        blurStr *= 0.5;
                    #endif
                    float2 uv = i.uv.xy / i.uv.w;
                    float2 uvb = uv;
                    #ifdef HIGH_QUALITY_BLUR
                        blurStr *= 1.25;
                        [unroll(136)]
                        for (uint k = 0; k < 137; ++k){
                            uvb.xy = uv.xy + (blurKernel[k] * blurStr);
                            blurCol += MOCHIE_SAMPLE_TEX2D_SCREENSPACE(_DoFGrab, uvb);
                        }
                        blurCol /= 137;
                    #else
                        [unroll(43)]
                        for (uint k = 0; k < 44; ++k){
                            uvb.xy = uv.xy + (blurKernel[k] * blurStr);
                            blurCol += MOCHIE_SAMPLE_TEX2D_SCREENSPACE(_DoFGrab, uvb);
                        }
                        blurCol /= 43;
                    #endif
                #else
                    discard;
                #endif

                return blurCol * _Color;
            }
            ENDCG
        }
    }
    CustomEditor "Mochie.UnderwaterEditor"
}