<@if not SHADOW_SLH@> <@def SHADOW_SLH@> <@include render-utils/ShaderConstants.h@> <@include ShadowCore.slh@> #define SHADOW_DITHER 1 #define SHADOW_NOISE_ENABLED 0 #define SHADOW_SCREEN_SPACE_DITHER 1 // the shadow texture LAYOUT(binding=RENDER_UTILS_TEXTURE_SHADOW) uniform sampler2DArrayShadow shadowMaps; // Sample the shadowMap with PCF (built-in) float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) { return texture(shadowMaps, vec4(shadowTexcoord.xy, cascadeIndex, shadowTexcoord.z)); } vec2 PCFkernel[4] = vec2[4]( vec2(-1.5, 0.5), vec2(0.5, 0.5), vec2(-1.5, -1.5), vec2(0.5, -1.5) ); #if SHADOW_NOISE_ENABLED float evalShadowNoise(vec4 seed) { float dot_product = dot(seed, vec4(12.9898,78.233,45.164,94.673)); return fract(sin(dot_product) * 43758.5453); } #endif struct ShadowSampleOffsets { vec3 points[4]; }; ShadowSampleOffsets evalShadowFilterOffsets(vec4 position) { float shadowScale = getShadowScale(); ShadowSampleOffsets offsets; #if SHADOW_SCREEN_SPACE_DITHER // Pattern dithering in screen space ivec2 coords = ivec2(gl_FragCoord.xy); #else // Pattern dithering in world space (mm resolution) ivec2 coords = ivec2(position.x, position.y+position.z); #endif #if SHADOW_NOISE_ENABLED // Add some noise to break dithering int index = int(4.0*evalShadowNoise(gl_FragCoord.xyyx))%4; coords.x += index & 1; coords.y += (index & 2) >> 1; #endif // Offset for efficient PCF, see https://developer.nvidia.com/gpugems/GPUGems/gpugems_ch11.html ivec2 offset = coords & ivec2(1,1); offset.y = (offset.x+offset.y) & 1; offsets.points[0] = shadowScale * vec3(vec2(offset) + PCFkernel[0], 0.0); offsets.points[1] = shadowScale * vec3(vec2(offset) + PCFkernel[1], 0.0); offsets.points[2] = shadowScale * vec3(vec2(offset) + PCFkernel[2], 0.0); offsets.points[3] = shadowScale * vec3(vec2(offset) + PCFkernel[3], 0.0); return offsets; } float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { shadowTexcoord.z -= bias; #if SHADOW_DITHER float shadowAttenuation = 0.25 * ( fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[0]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[1]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[2]) + fetchShadow(cascadeIndex, shadowTexcoord.xyz + offsets.points[3]) ); #else float shadowAttenuation = fetchShadow(cascadeIndex, shadowTexcoord.xyz); #endif return shadowAttenuation; } float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float slopeNdotL) { float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * slopeNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); vec4 cascadeShadowCoords[4]; vec4 cascadeWeights; vec4 cascadeAttenuations = vec4(1.0); vec3 cascadeMix; bvec4 isPixelOnCascade; int cascadeIndex; float NdotL = clamp(dot(worldLightDir, worldNormal), 0.0, 1.0); float slopeNdotL = min(2.0, sqrt(1.0-NdotL*NdotL) / NdotL); for (cascadeIndex=0 ; cascadeIndex