float2 rotate(float2 p, float a)
{
    float s, c;
    sincos(a, s, c);
    return mul(p, float2x2(c, -s, s, c));
}

float hash11(float x)
{
    return frac(47325.427 * sin((x - 64.0) * 78.5732));
}

float ValueNoise(float p)
{
    float i = floor(p);
    p = frac(p);
    p = p * p * p * (p * (p * 6.0 - 15.0) + 10.0);
    float n0 = hash11(i);
    float n1 = hash11(i + 1.0);
    return lerp(n0, n1, p);
}

float3 nn(float p)
{
    float m = ValueNoise(p);
    float l = ValueNoise(p - 0.1);
    float r = ValueNoise(p + 0.1);
    return normalize(float3(m - l, m - r, 0.1));
}

float4 mFX(v2f i)
{
    float3 vNorm = mul(UNITY_MATRIX_V, float4(i.wNorm.xyz, 0.0)).xyz * 0.5 + 0.5;
    float rLt = saturate(1.0 - dot(normalize(i.wNorm), i.vDir));
    float irLt = saturate(dot(normalize(i.wNorm), i.vDir));
    float3 rDS = nn(-(vNorm.z + rLt * _RadialDistortion * 1.5) * 4.0);

    float3 c = 0.0;
    c += _mColor1 * pow(irLt, 20.0);
    c += _mColor2 * pow((smoothstep(0.37, -0.5, length(vNorm.xy - float2(0.3, 0.43))) + smoothstep(0.45, -0.4, length(vNorm.xy - float2(0.62, 0.65)))), 3.0);
    c += _mColor3 * pow(exp(-length((frac(rotate(reflect(mul(unity_CameraToWorld, float4(i.vDir , 0.0)).xyz, mul(unity_WorldToCamera, float4(normalize(half3(dot(half3(i.wTan.x, i.wBiTan.x, i.wNorm.x), rDS), dot(half3(i.wTan.y, i.wBiTan.y, i.wNorm.y), rDS), dot(half3(i.wTan.z, i.wBiTan.z, i.wNorm.z), rDS))), 0.0)).xyz).xy, _Time.y * _ShineSpeed * 0.3)) - 0.5) * 0.8) * 4.0), 3.0);
    c *= 5.0 * UNITY_SAMPLE_TEX2D_SAMPLER(_MetallicTexture, _MainTex, TRANSFORM_TEX(i.uv, _MetallicTexture));
    c = clamp(pow(c, 1.0 + _Contrast), 0.0, 5.0);

    return float4(c, UNITY_SAMPLE_TEX2D_SAMPLER(_MetallicMask, _MainTex, TRANSFORM_TEX(i.uv, _MetallicMask)).r);
}

void calcNormals(inout v2f i, uint face)
{
    float3 n = UnpackScaleNormal(UNITY_SAMPLE_TEX2D_SAMPLER(_BumpMap, _MainTex, TRANSFORM_TEX(i.uv, _BumpMap)), _BumpScale);
    if (!(face > 0)) { i.wNorm *= -1.0; i.wTan *= -1.0; i.wBiTan *= -1.0; }
    i.wTan = normalize(i.wTan);
    i.wBiTan = normalize(i.wBiTan);
    i.wNorm = normalize(half3(
        dot(half3(i.wTan.x, i.wBiTan.x, i.wNorm.x), n), 
        dot(half3(i.wTan.y, i.wBiTan.y, i.wNorm.y), n), 
        dot(half3(i.wTan.z, i.wBiTan.z, i.wNorm.z), n)));
    i.wTan = cross(i.wBiTan, i.wNorm);
    i.wBiTan = cross(i.wNorm, i.wTan);
}

float pow5(float a)
{
    return a * a * a * a * a;
}

float3 F_Schlick(float u, float3 f0)
{
    return f0 + (1.0 - f0) * pow(1.0 - u, 5.0);
}

float3 F_FresnelLerp (float3 F0, float3 F90, float cosA)
{
    float t = pow5(1 - cosA);
    return lerp(F0, F90, t);
}

float D_GGX(float NoH, float roughness)
{
    float a2 = roughness * roughness;
    float f = (NoH * a2 - NoH) * NoH + 1.0;
    return a2 / (UNITY_PI * f * f);
}

float V_SmithGGXCorrelated(float NoV, float NoL, float a)
{
    float a2 = a * a;
    float GGXL = NoV * sqrt((-NoL * a2 + NoL) * NoL + a2);
    float GGXV = NoL * sqrt((-NoV * a2 + NoV) * NoV + a2);
    return 0.5 / (GGXV + GGXL);
}

//MIT License
//Copyright (c) 2019 Xiexe
half3 calcDirectSpecular(v2f i, float Specular, half3 lightDir, half4 lightCol)
{
    float ndl = dot(i.wNorm, lightDir);
    float ndh = DotClamped(i.wNorm, normalize(lightDir + i.vDir));
    float vdn = abs(dot(i.vDir, i.wNorm));
    float ldh = DotClamped(lightDir, normalize(lightDir + i.vDir));
    half smoothness = 1.0 - Specular * 0.9;
    smoothness *= 1.7 - 0.7 * smoothness;

    float rough = max(smoothness * smoothness, 0.0045);
    float Dn = D_GGX(ndh, rough);
    float3 F = 1 - F_Schlick(ldh, 0);
    float V = V_SmithGGXCorrelated(vdn, ndl, rough);
    float3 directSpecularNonAniso = max(0, (Dn * V) * F);
    half3 specular = max(0, (Dn * V) * F) * 3.0 * lightCol * DotClamped(i.wNorm, lightDir);

    return specular;
}

//MIT License
//Copyright (c) 2019 Xiexe
half3 calcLightDir(float3 worldPos)
{
    half3 lightDir = UnityWorldSpaceLightDir(worldPos);
    half3 probeLightDir = unity_SHAr.xyz + unity_SHAg.xyz + unity_SHAb.xyz;
    lightDir = (lightDir + probeLightDir); //Make light dir the average of the probe direction and the light source direction.
    #if !defined(POINT) && !defined(SPOT)// if the average length of the light probes is null, and we don't have a directional light in the scene, fall back to our fallback lightDir
        if(length(unity_SHAr.xyz*unity_SHAr.w + unity_SHAg.xyz*unity_SHAg.w + unity_SHAb.xyz*unity_SHAb.w) == 0 && length(lightDir) < 0.1)
        {
            lightDir = half4(1, 1, 1, 0);
        }
    #endif
    return normalize(lightDir);
}

//MIT License
//Copyright (c) 2019 Xiexe
//Returns the average direction of all lights and writes to a struct contraining individual directions
float3 getVertexLightsDir(inout VertexLightInformation vLights, float3 worldPos, float4 vertexLightAtten)
{
    float3 dir = float3(0,0,0);
    float3 toLightX = float3(unity_4LightPosX0.x, unity_4LightPosY0.x, unity_4LightPosZ0.x);
    float3 toLightY = float3(unity_4LightPosX0.y, unity_4LightPosY0.y, unity_4LightPosZ0.y);
    float3 toLightZ = float3(unity_4LightPosX0.z, unity_4LightPosY0.z, unity_4LightPosZ0.z);
    float3 toLightW = float3(unity_4LightPosX0.w, unity_4LightPosY0.w, unity_4LightPosZ0.w);

    float3 dirX = toLightX - worldPos;
    float3 dirY = toLightY - worldPos;
    float3 dirZ = toLightZ - worldPos;
    float3 dirW = toLightW - worldPos;

    dirX *= length(toLightX) * vertexLightAtten.x;
    dirY *= length(toLightY) * vertexLightAtten.y;
    dirZ *= length(toLightZ) * vertexLightAtten.z;
    dirW *= length(toLightW) * vertexLightAtten.w;

    vLights.Direction[0] = dirX;
    vLights.Direction[1] = dirY;
    vLights.Direction[2] = dirZ;
    vLights.Direction[3] = dirW;

    dir = (dirX + dirY + dirZ + dirW) / 4;
    return dir;
}

//MIT License
//Copyright (c) 2019 Xiexe
float3 get4VertexLightsColFalloff(inout VertexLightInformation vLight, float3 worldPos, inout float4 vertexLightAtten)
{
    float3 lightColor = 0;
    #if defined(VERTEXLIGHT_ON)
        float4 toLightX = unity_4LightPosX0 - worldPos.x;
        float4 toLightY = unity_4LightPosY0 - worldPos.y;
        float4 toLightZ = unity_4LightPosZ0 - worldPos.z;

        float4 lengthSq = 0;
        lengthSq += toLightX * toLightX;
        lengthSq += toLightY * toLightY;
        lengthSq += toLightZ * toLightZ;

        float4 atten = 1.0 / (1.0 + lengthSq * unity_4LightAtten0);
        float4 atten2 = saturate(1 - (lengthSq * unity_4LightAtten0 / 25));
        atten = min(atten, atten2 * atten2);
        // Cleaner, nicer looking falloff. Also prevents the "Snapping in" effect that Unity's normal integration of vertex lights has.
        vertexLightAtten = atten;

        lightColor.rgb += unity_LightColor[0] * atten.x;
        lightColor.rgb += unity_LightColor[1] * atten.y;
        lightColor.rgb += unity_LightColor[2] * atten.z;
        lightColor.rgb += unity_LightColor[3] * atten.w;

        vLight.ColorFalloff[0] = unity_LightColor[0] * atten.x;
        vLight.ColorFalloff[1] = unity_LightColor[1] * atten.y;
        vLight.ColorFalloff[2] = unity_LightColor[2] * atten.z;
        vLight.ColorFalloff[3] = unity_LightColor[3] * atten.w;

        vLight.Attenuation[0] = atten.x;
        vLight.Attenuation[1] = atten.y;
        vLight.Attenuation[2] = atten.z;
        vLight.Attenuation[3] = atten.w;
    #endif
    return lightColor;
}

//MIT License
//Copyright (c) 2019 Xiexe
float3 getVertexLightsDiffuse(VertexLightInformation vLight, float3 normal)
{
    float3 vertexLightsDiffuse = 0;
    #if defined(VERTEXLIGHT_ON)
        for(int light = 0; light < 4; light++)
        {
            float vLightNdl = dot(vLight.Direction[light], normal);
            vertexLightsDiffuse += vLight.ColorFalloff[light];
        }
    #endif
    return vertexLightsDiffuse;
}

void getLightning(v2f i, inout float3 lightning, inout float3 DirectSpecular, float ma)
{
    UNITY_LIGHT_ATTENUATION(attenuation, i, i.wPos);
    
    #ifdef UNITY_PASS_FORWARDBASE
        if(all(_LightColor0.rgb == 0.0))
        {
            attenuation = 1.0;
        }
    #endif

    half3 indirectDiffuse = ShadeSH9(float4(0.0, 0.5, 0.0, 1.0));
    half4 lightCol = half4(0,0,0,0);

    if(any(_WorldSpaceLightPos0.xyz))
    {
        lightCol = _LightColor0;
        indirectDiffuse = indirectDiffuse;
    }
    else
    {
        lightCol = indirectDiffuse.xyzz * 0.1;
        indirectDiffuse = indirectDiffuse * 0.9;
    }

    #if defined(VERTEXLIGHT_ON)
        VertexLightInformation vLight = (VertexLightInformation)0;
        float4 vertexLightAtten = float4(0,0,0,0);
        float3 vertexLightColor = get4VertexLightsColFalloff(vLight, i.wPos, vertexLightAtten);
        float3 vertexLightDir = getVertexLightsDir(vLight, i.wPos, vertexLightAtten);
        indirectDiffuse += getVertexLightsDiffuse(vLight, i.wNorm);
    #endif

    float3 lightDir = calcLightDir(i.wPos);

    DirectSpecular = attenuation * calcDirectSpecular(i, ma, lightDir, lightCol);
    lightning = lightCol * attenuation + indirectDiffuse;
}