void HemisphereMC_fp
(
    in const float2 uv : TEXCOORD0,
    
    out float4 oColor0 : COLOR0,
    
    uniform sampler sMRT1 : register(s0), // fragment normals
    uniform sampler sMRT2 : register(s1), // view space position, remember that we are looking down the negative Z axis!!!
    uniform sampler sRand : register(s2), // MxN random texture, M sets of N precomputed low-discrepancy samples
    
    uniform const float4 cViewportSize, // (viewport_width, viewport_height, inverse_viewport_width, inverse_viewport_height)
    uniform const float cFov, // vertical field of view in radians
    uniform const float cSampleInScreenspace, // whether to sample in screen or world space
    uniform const float cSampleLengthScreenSpace, // The sample length in screen space [0, 1]
    uniform const float cSampleLengthWorldSpace, // the sample length in world space in units
    uniform const float cSampleLengthExponent // The exponent of the sample length
)
{
    const int interleaved = 4;
    const int m = 8;
    const int n = 4;
    const int numSamples = m * n;
    const float2 interleaveOffset = uv * cViewportSize.xy / interleaved;
    const float3 fragmentPosition = tex2D(sMRT2, uv).xyz; // the current fragment in view space
    const float3 fragmentNormal = 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) * -fragmentPosition.z;
    }
    else
    {
        rUV = atan(cSampleLengthWorldSpace / -fragmentPosition.z) / cFov; // the radius of influence projected into screen space
        r = cSampleLengthWorldSpace;
    }

    if (rUV < (cViewportSize.z)) // abort if the projected radius of influence is smaller than 1 fragment
    {
        oColor0 = 1;
        return;
    }
    

    float accessibility = 0; // accessibility of the fragment

    const float3 viewVector = float3(0, 0, 1); // the constant view vector in view space

    // the reflection vector to align the hemisphere with the fragment normal
    // somehow the x component must be flipped...???
    const float3 reflector = normalize(fragmentNormal + viewVector) * float3(-1, 1, 1); 

    float count = 0;
    float sampleLength;

    for (float i = 0.0f; i < m; i++)
    for (float j = 0.0f; j < n; j++)
    {
        count ++;

        const float2 randomTC = interleaveOffset + float2(i/(interleaved * m), j/(interleaved * n)); 
        float3 randomVector = (tex2D(sRand, randomTC) * 2 - 1); // unpack to [-1, 1]x[-1, 1]x[1, 1]

        sampleLength = pow(count/(float)numSamples, cSampleLengthExponent);

        const float3 sampleVector = reflect(randomVector, reflector) * sampleLength;

        const float2 sampleTC = uv + sampleVector.xy * rUV;

        const float3 samplePosition = tex2D(sMRT2, sampleTC);

        if (samplePosition.z < (fragmentPosition.z - sampleVector.z * r)) // thin air
            accessibility++;
        else // solid geometry
            accessibility += length(fragmentPosition - samplePosition) > r; // out of reach, i.e. false occluder
    }

    accessibility /= numSamples;
    oColor0 = float4(accessibility.xxx, 1);

    float3 direction = 0;
    direction += reflect(float3(0, 0, -1), reflector);
    direction = normalize(direction);
}
