SamplerState g_samLinear { Filter = MIN_MAG_MIP_LINEAR; AddressU = Wrap; AddressV = Wrap; AddressW = Wrap; }; Texture2D sMRT1 : register(s0); // fragment normals Texture2D sMRT2 : register(s1); // view space position, remember that we are looking down the negative Z axis!!! Texture2D sRand : register(s2); // MxN random texture, M sets of N precomputed low-discrepancy samples struct v2p { float4 position : SV_POSITION; float2 uv : TEXCOORD0; }; float4 HemisphereMC_fp ( v2p input, 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 ) : SV_Target { const int interleaved = 4; const int m = 8; const int n = 4; const int numSamples = m * n; const float2 interleaveOffset = input.uv * cViewportSize.xy / interleaved; const float3 fragmentPosition = sMRT2.Sample(g_samLinear, input.uv).xyz; // the current fragment in view space const float3 fragmentNormal = sMRT1.Sample(g_samLinear, input.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 { return float4(1.0,1.0,1.0,1.0); } 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; float2 randomTC; float3 randomVector; float3 sampleVector; float2 sampleTC; float3 samplePosition; for (float i = 0.0f; i < m; i++) for (float j = 0.0f; j < n; j++) { count ++; randomTC = interleaveOffset + float2(i/(interleaved * m), j/(interleaved * n)); randomVector = (sRand.SampleLevel(g_samLinear, randomTC, 0) * 2 - 1).xyz; // unpack to [-1, 1]x[-1, 1]x[1, 1] sampleLength = pow(count/(float)numSamples, cSampleLengthExponent); sampleVector = reflect(randomVector, reflector) * sampleLength; sampleTC = input.uv + sampleVector.xy * rUV; samplePosition = sMRT2.SampleLevel(g_samLinear, sampleTC, 0).xyz; 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; float3 direction = 0; direction += reflect(float3(0, 0, -1), reflector); direction = normalize(direction); return float4(accessibility.xxx, 1); }