#ifndef SHADOWS_GLSL #define SHADOWS_GLSL // expects bindless.glsl to be included before this file (it has no include guard) // Returns 1.0 when fully lit, 0.0 when fully in shadow float calculateDirectionalShadow(vec3 fragPos, vec3 n, mat4 lightViewProj, uint shadowMapID, float normalOffset) { // pushing the sample point along the normal hides most of the shadow acne vec4 lightClip = lightViewProj * vec4(fragPos + n * normalOffset, 1.0); vec3 lightNDC = lightClip.xyz / lightClip.w; vec2 uv = lightNDC.xy * 0.5 + 0.5; // outside the shadow map counts as lit if (any(lessThan(uv, vec2(0.0))) || any(greaterThan(uv, vec2(1.0))) || lightNDC.z > 1.0) { return 1.0; } vec2 texelSize = 1.0 / vec2(textureSize(sampler2DShadow(textures[shadowMapID], samplers[SHADOW_SAMPLER_ID]), 0)); // 3x3 PCF, every tap is already bilinearly filtered by the compare sampler float shadowed = 0.0; for (int x = -1; x <= 1; x++) { for (int y = -1; y <= 1; y++) { shadowed += sampleTexture2DShadow(shadowMapID, vec3(uv + vec2(x, y) * texelSize, lightNDC.z)); } } // the shadow sampler compares with GREATER_OR_EQUAL (it was set up for reversed Z), // with regular depth that returns 1 when the fragment is behind the occluder return 1.0 - shadowed / 9.0; } #endif // SHADOWS_GLSL