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; }