Shader "LuckyShaders/MainGlass"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}

        _BumpMap ("Bump Map", 2D) = "bump" {}
        _BumpIntensity ("Bump Intensity", float) = 1

        _RoughnessMap ("Roughness Map", 2D) = "white" {}
        _Roughness ("Roughness", Range(0,1)) = 0.5
        [MaterialToggle] _RoughnessMapInverted ("Is inverted", Float) = 0

        _Metallic ("Metallic", Range(0,1)) = 0.0

        _SpecularMap ("Specular Map", 2D) = "white" {}
        _SpecularIntensity ("Specular Intensity", float) = 1

        _DispMap ("Displacement Map", 2D) = "gray" {}
        _DispIntensity ("Displacement", float) = 0
        _Tesselation ("Tesselation", Range(1,32)) = 4

        _WetMap ("Wetness Map", 2D) = "gray" {}
        _Wetness ("Wetness", Range(0,1)) = 0
        _WaterColor ("Water Color", Color) = (0.87, 0.92, 1, 0.15)

        _AOMap ("AO Map", 2D) = "white" {}

        _IOR ("Index of Refraction", float) = 1
    }
    SubShader
    {
        Tags { "RenderType"="Transparent" "Queue"="Transparent" }
        LOD 200

        ZWrite Off
        Blend SrcAlpha OneMinusSrcAlpha

        CGPROGRAM

        // Physically based Standard lighting model, and enable shadows on all light types
        //#pragma surface surf Standard fullforwardshadows
        #pragma surface surf Burley addshadow fullforwardshadows vertex:disp tessellate:tessFixed
        #pragma target 4.6

        struct appdata {
            float4 vertex : POSITION;
            float4 tangent : TANGENT;
            float3 normal : NORMAL;
            float2 texcoord : TEXCOORD0;

            //These are needed for lightmaps and GI
            float2 texcoord1 : TEXCOORD1;
            float2 texcoord2 : TEXCOORD2;
        };

        //Define certain default variables like PI
        static const float PI = 3.14159265;
        static const float INV_PI = 0.3183098865475127719056765682209; //TODO: see if reducing accuraccy leads to better performance without negative results

        //Custom lighting model
        struct SurfaceOutputBurley {
            fixed3 Albedo;
            fixed3 Normal;
            fixed3 Emission;
            fixed Metallic;
            fixed Roughness;
            half Specular;
            fixed Alpha;
            fixed Height;
            fixed IOR;
        };

        float pow5 (float x) {
            float x2 = x * x;
            return x2 * x2 * x;
        }

        float D_GGX (float linearRoughness, float NoH) {
            //Walter et al. 2007, "Microfacet Models for Refraction through Rough Surfaces"
            float oneMinusNoHSquared = 1.0 - NoH * NoH;
            float a = NoH * linearRoughness;
            float k = linearRoughness / (oneMinusNoHSquared + a * a);
            return k * k * INV_PI; //d
        }

        float V_SmithGGXCorrelated (float linearRoughness, float NoV, float NoL) {
            //Heitz 2014, "Understanding the Masking-Shadowing Function in Microfacet-Based BRDFs"
            float a2 = linearRoughness * linearRoughness;
            float GGXV = NoL * sqrt((NoV - a2 * NoV) * NoV + a2);
            float GGXL = NoV * sqrt((NoL - a2 * NoL) * NoL + a2);
            return 0.5 / (GGXV + GGXL);
        }

        float3 F_Schlick (float3 f0, float VoH) {
            //Schlick 1994, "An Inexpensive BRDF Model for Physically-Based Rendering"
            return f0 + (float3(1.0,1.0,1.0) - f0) * pow5(1.0 - VoH);
        }

        float F_Schlick (float f0, float f90, float VoH) {
            return f0 + (f90 - f0) * pow5(1.0 - VoH);
        }

        float Fd_Burley (float linearRoughness, float NoV, float NoL, float LoH) {
            //Burley 2012, "Physically-Based Shading at Disney"
            float f90 = 0.5 + 2.0 * linearRoughness * LoH * LoH;
            float lightScatter = F_Schlick(1.0, f90, NoL);
            float viewScatter = F_Schlick(1.0, f90, NoV);
            return lightScatter * viewScatter * INV_PI;
        }

        //In original code Fd_Lambert was defined as 1.0 / PI, but we can just replace this with using INV_PI

        float3 Irradiance_SphericalHarmonics (float3 n) {
            //Irradiance from "Ditch River" IBL (http://www.hdrlabs.com/sibl/archive.html)
            return max(
                  float3( 0.754554516862612,  0.748542953903366,  0.790921515418539)
                + float3(-0.083856548007422,  0.092533500963210,  0.322764661032516) * (n.y)
                + float3( 0.308152705331738,  0.366796330467391,  0.466698181299906) * (n.z)
                + float3(-0.188884931542396, -0.277402551592231, -0.377844212327557) * (n.x)
                , float3(0.0, 0.0, 0.0)
                );
        }

        float2 PrefilteredDFG_Karis (float roughness, float NoV) {
            //Karis 2014, "Physically Based Material on Mobile"
            const float4 c0 = float4(-1.0, -0.0275, -0.572,  0.022);
            const float4 c1 = float4( 1.0,  0.0425,  1.040, -0.040);

            float4 r = roughness * c0 + c1;
            float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;

            return float2(-1.04, 1.04) * a004 + r.zw;
        }

        //inline fixed4 LightingBurleyLight (SurfaceOutputBurley s, half3 viewDir, half attenuation, UnityLight light, UnityIndirect indirect)
        //inline fixed4 LightingBurleyLight (SurfaceOutputBurley s, half3 viewDir, half attenuation, half3 lightDir, fixed4 ref)
        inline fixed4 LightingBurleyLight (SurfaceOutputBurley s, half3 viewDir, half attenuation, half3 lightDir, fixed4 ref, UnityIndirect indirect)
        {
            //Variables from original code:
            //vec3 pos (position)
            //vec3 n (normal)
            //vec3 rd (view direction)
            //vec3 l (incoming light direction)
            //vec3 lp (light position)
            //float range (light range)
            //vec3 baseColor (surface color)
            //float roughness (self-explanatory)
            //float metallic (self-explanatory)

            //Seeing as unity handles a lot of light calculations itself, all I need to do is use these :D

            float3 v = normalize(-viewDir);
            float3 h = normalize(v + lightDir);
            float3 r = normalize(reflect(viewDir, s.Normal));

            float NoV = abs(dot(s.Normal, v)) + 1e-5;
            float NoL = saturate(dot(s.Normal, lightDir));
            float NoH = saturate(dot(s.Normal, h));
            float LoH = saturate(dot(lightDir, h));

            //Upon further inspection, it appears unity already multiplies light color with intensity.
            //EDIT: I have worked through a bunch of fucking issues, like not being able to access attenuation when using _GI functions and shit like that
            //so now with attenuation, we actually need these variables again to get more accurate colors.
            float intensity = 2.0; //Original code specified 2.0
            float indirectIntensity = 0.64; //Original code specified 0.64

            //Because of what I said in the previous comment, we don't actually need to do this hacky math for point lights that I used to do in GLSL lol

            float linearRoughness = s.Roughness * s.Roughness;
            float3 diffuseColor = (1.0 - s.Metallic) * s.Albedo + ref.rgb * s.Metallic * linearRoughness;
            float3 f0 = 0.04 * (1.0 - s.Metallic) + s.Albedo * s.Metallic;

            //Original code specified attenuation here, meaning the intensity of the shadow.
            //No need though, because Unity already multiplies light color by intensity beforehand.
            //Pretty nice, but I still think Unity sucks because it limits you so much.
            //Custom Deferred lighting models are basically not doable, because fuck you that's why lol.
            //Anyway

            float fLinearRoughness = (0.5 + s.Roughness*0.5) * (0.5 + s.Roughness*0.5);

            //Specular BRDF
            float D = D_GGX(linearRoughness, NoH);
            float V = V_SmithGGXCorrelated(linearRoughness, NoV, NoL);
            float3 F = F_Schlick(f0, LoH);
            float3 Fr = (D * V) * F; //for real dude :p

            //Diffuse BRDF
            float3 Fd = diffuseColor * Fd_Burley(linearRoughness, NoV, NoL, LoH);

            float3 color = Fd + Fr;
            color *= (intensity * attenuation * NoL);

            float3 ibl; //This was defined beforehand because of the code below.

            //TODO: Fix this fucking mess

            #ifdef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
                float3 indirectDiffuse = Irradiance_SphericalHarmonics(s.Normal) * INV_PI; //Normally it uses Fd_Lambert() here, but we replaced that with INV_PI
                float3 indirectSpecular = indirect.diffuse; //Original code actually gets this value from a raycast, because it is taken from a sphere tracer :D

                //Indirect contribution
                float2 dfg = PrefilteredDFG_Karis(s.Roughness, NoV);
                float3 specularColor = f0 * dfg.x + dfg.y;
                ibl = diffuseColor * indirect.diffuse + indirect.specular * specularColor * intensity * attenuation * (float3(0.7, 0.9, 1.0) + viewDir.y * 0.8) * 6.0 * s.Specular;
            #endif
            #ifndef UNITY_LIGHT_FUNCTION_APPLY_INDIRECT
                float2 dfg = PrefilteredDFG_Karis(s.Roughness, NoV);
                float3 specularColor = f0 * dfg.x + dfg.y;
                ibl = diffuseColor + specularColor * intensity * attenuation * (float3(0.7, 0.9, 1.0) + viewDir.y * 0.8) * 6.0 * s.Specular;
            #endif

            //float2 dfg = PrefilteredDFG_Karis(s.Roughness, NoV);
            //float3 specularColor = f0 * dfg.x + dfg.y;
            //ibl = diffuseColor + specularColor * intensity * attenuation * (float3(0.7, 0.9, 1.0) + viewDir.y * 0.8) * 6.0 * s.Specular;

            return float4(color * ibl * (_LightColor0.rgb * intensity), 1.0);
        }

        inline fixed4 LightingBurleyReflect (SurfaceOutputBurley s, half3 lightDir, half3 viewDir, half atten)
        {
            float4 hdrReflection = 1.0;
            float3 reflectedDir = reflect(viewDir, s.Normal);
            float4 reflection = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, reflectedDir);
            hdrReflection.rgb = DecodeHDR(reflection, unity_SpecCube0_HDR);
            hdrReflection.a = 1.0;

            float4 c;
            c.rgb = hdrReflection;
            return c;
        }

        inline fixed4 LightingBurley (SurfaceOutputBurley s, half3 lightDir, half3 viewDir, half atten, UnityIndirect indirect)
        {
            fixed4 ref = LightingBurleyReflect (s, lightDir, viewDir, atten);
            fixed4 c = LightingBurleyLight (s, viewDir, atten, lightDir, ref, indirect);

            float3 refractedDir = refract(viewDir, s.Normal, 1.0 / s.IOR);
            float4 refraction = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, refractedDir);

            c.rgb = c.rgb * s.Alpha + (1.0 - s.Alpha) * refraction.rgb;
            c.a = s.Alpha;

            return c;
        }

        sampler2D _MainTex;
        sampler2D _RoughnessMap;
        sampler2D _SpecularMap;
        sampler2D _BumpMap;
        sampler2D _DispMap;
        sampler2D _WetMap;
        sampler2D _AOMap;

        struct Input
        {
            float2 uv_MainTex;
        };

        fixed _Roughness;
        fixed _RoughnessMapInverted;
        fixed _Metallic;
        fixed _SpecularIntensity;
        fixed _BumpIntensity;
        fixed _DispIntensity;
        fixed _Tesselation;
        fixed _Wetness;
        fixed _IOR;
        fixed4 _WaterColor;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        float4 tessFixed()
        {
            return _Tesselation;
        }

        void disp (inout appdata v)
        {
            float d = tex2Dlod(_DispMap, float4(v.texcoord.xy, 0,0)).r * _DispIntensity;
            v.vertex.xyz += v.normal * d;
        }

        void surf (Input IN, inout SurfaceOutputBurley o)
        {
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color.a;
            c.rgb *= tex2D (_AOMap, IN.uv_MainTex).r;
            fixed4 w = tex2D (_WetMap, IN.uv_MainTex);
            fixed3 ww = w.r * _WaterColor * _Wetness;
            o.Albedo = (1.0 - _Wetness * w.r) * c.rgb + (ww * c.rgb * w.a);
            //o.Albedo = float3(_Color.a, _Color.a, _Color.a);

            o.Metallic = _Metallic;

            fixed roughness = _Roughness;
            if (_RoughnessMapInverted > 0) {
                roughness *= (1.0 - tex2D (_RoughnessMap, IN.uv_MainTex).r);
            } else {
                roughness *= tex2D (_RoughnessMap, IN.uv_MainTex).r;
            }
            o.Roughness = (1.0 - _Wetness * w.r) * roughness + _Wetness * w.r;

            o.Specular = (1.0 - _Wetness * w.r) * (tex2D (_SpecularMap, IN.uv_MainTex) * _SpecularIntensity) + _Wetness * w.r;

            o.Normal = (1.0 - _Wetness * w.r) * (UnpackNormal(tex2D (_BumpMap, IN.uv_MainTex) * _BumpIntensity)) + _Wetness * w.r * float3(1.0, 1.0, 1.0);

            o.IOR = _IOR;

            o.Alpha = _Color.a;
        }
        ENDCG
    }
    //FallBack "Diffuse"
}