void HorizonBased_fp
(
    in float2 uv : TEXCOORD0,
    
    out float4 oColor0 : COLOR0,
    
    uniform sampler sMRT1 : register(s0), // fragment normal
    uniform sampler sMRT2 : register(s1), // view space position
    uniform sampler sRand : register(s2),  // (cos(a), sin(a), jitter
    
    uniform float4 cViewportSize, // (viewport_width, viewport_height, inverse_viewport_width, inverse_viewport_height)
    uniform float cFov, // vertical field of view in radians
    uniform float cSampleInScreenspace, // whether to sample in screen or world space
    uniform float cSampleLengthScreenSpace, // The sample length in screen space [0, 1]
    uniform float cSampleLengthWorldSpace, // the sample length in world space in units
    uniform float cAngleBias // angle bias to avoid shadows in low tessellated curvatures [0, pi/2]
)
{
    const float pi = 3.1415926535897932384626433832795028841971693993751;

    const float numSteps = 7; // number of samples/steps along a direction
    const float numDirections = 4; // number of sampling directions in uv space
    
    float3 p = tex2D(sMRT2, uv).xyz; // the current fragment in view space
    float3 pointNormal = tex2D(sMRT1, uv).xyz; // the fragment normal

    float Ruv = 0; // radius of influence in screen space
    float R = 0; // radius of influence in world space
    if (cSampleInScreenspace == 1)
    {
        Ruv = cSampleLengthScreenSpace;
        R = tan(Ruv * cFov) * -p.z;
    }
    else
    {
        Ruv = atan(cSampleLengthWorldSpace / -p.z) / cFov; // the radius of influence projected into screen space
        R = cSampleLengthWorldSpace;
    }

    // if the radius of influence is smaller than one fragment we exit early,
    // since all samples would hit the current fragment.
    if (Ruv < (1 / cViewportSize.x))
    {
        oColor0 = float4(1, 1, 1, 1);
        return;
    }

    float occlusion = 0; // occlusion of the fragment
    float2x2 directionMatrix;    // the matrix to create the sample directions
                                 // the compiler should evaluate the directions at compile time
    directionMatrix._m00 = cos((2 * pi) / numDirections);
    directionMatrix._m01 = sin((2 * pi) / numDirections);
    directionMatrix._m10 = - sin((2 * pi) / numDirections);
    directionMatrix._m11 = cos((2 * pi) / numDirections);
    float2 deltaUV = float2(1, 0) * (Ruv / (numSteps + 1)); // The step vector in view space. scale it to the step size
            // we don't want to sample to the perimeter of R since those samples would be 
            // omitted by the distance attenuation (W(R) = 0 by definition)
            // Therefore we add a extra step and don't use the last sample.

    float3 randomValues = tex2D(sRand, (uv * cViewportSize.xy) / 4); //4px tiles
    float2x2 rotationMatrix;
    rotationMatrix._m00 = (randomValues.x - 0.5) * 2;
    rotationMatrix._m01 = (randomValues.y - 0.5) * 2;
    rotationMatrix._m10 = - rotationMatrix._m01;
    rotationMatrix._m11 = rotationMatrix._m00;
    float jitter = randomValues.z;

    for (int i = 0; i < numDirections; i++)
    {
        deltaUV = mul(deltaUV, directionMatrix); // rotate the deltaUV vector by 1/numDirections
        float2 sampleDirection = mul(deltaUV, rotationMatrix); // now rotate this vector with the random rotation

        float oldAngle = cAngleBias;

        for (int j = 1; j <= numSteps; j++) // sample along a direction, needs to start at one, for the sake of the next line
        {
            float2 sampleUV = uv + ((jitter + j) * sampleDirection); // jitter the step a little bit
            
            float3 sample = tex2D(sMRT2, sampleUV); // the sample in view space
            float3 sampleVector = (sample - p);
            float gamma = (pi / 2) - acos(dot(pointNormal, normalize(sampleVector))); //the angle between the fragment tangent and the sample

            if (gamma > oldAngle) 
            {
                float attenuation = saturate(1 - (pow((length(sampleVector) / R), 2)));
                occlusion += attenuation * (sin(gamma) - sin(oldAngle));
                oldAngle = gamma;
            }
        }
    }

    // ??? should step samples that fall under the horizontal be considered in the following line??? 
    occlusion /= (numDirections * numSteps);
    oColor0 = 1 - float4(occlusion.xxx, 1) * 2 * pi;
}
