<@if not SHADOW_SLH@> <@def SHADOW_SLH@> <@include ShadowCore.slh@> #define SHADOW_NOISE_ENABLED 0 #define SHADOW_SCREEN_SPACE_DITHER 1 // the shadow texture uniform sampler2DShadow shadowMaps[SHADOW_CASCADE_MAX_COUNT]; // Sample the shadowMap with PCF (built-in) float fetchShadow(int cascadeIndex, vec3 shadowTexcoord) { return texture(shadowMaps[cascadeIndex], shadowTexcoord); } 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) ); 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); } 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 http://http.developer.nvidia.com/GPUGems/gpugems_ch11.html ivec2 offset = coords & ivec2(1,1); offset.y = (offset.x+offset.y) & 1; offsets.points[0] = shadowScale * vec3(offset + PCFkernel[0], 0.0); offsets.points[1] = shadowScale * vec3(offset + PCFkernel[1], 0.0); offsets.points[2] = shadowScale * vec3(offset + PCFkernel[2], 0.0); offsets.points[3] = shadowScale * vec3(offset + PCFkernel[3], 0.0); return offsets; } float evalShadowAttenuationPCF(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float bias) { shadowTexcoord.z -= bias; 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]) ); return shadowAttenuation; } float evalShadowCascadeAttenuation(int cascadeIndex, ShadowSampleOffsets offsets, vec4 shadowTexcoord, float oneMinusNdotL) { float bias = getShadowFixedBias(cascadeIndex) + getShadowSlopeBias(cascadeIndex) * oneMinusNdotL; return evalShadowAttenuationPCF(cascadeIndex, offsets, shadowTexcoord, bias); } float evalShadowAttenuation(vec3 worldLightDir, vec4 worldPosition, float viewDepth, vec3 worldNormal) { ShadowSampleOffsets offsets = evalShadowFilterOffsets(worldPosition); vec4 cascadeShadowCoords[2]; cascadeShadowCoords[0] = vec4(0); cascadeShadowCoords[1] = vec4(0); ivec2 cascadeIndices; float cascadeMix = determineShadowCascadesOnPixel(worldPosition, viewDepth, cascadeShadowCoords, cascadeIndices); // Adjust bias if we are at a grazing angle with light float oneMinusNdotL = 1.0 - clamp(dot(worldLightDir, worldNormal), 0, 1); vec2 cascadeAttenuations = vec2(1.0, 1.0); cascadeAttenuations.x = evalShadowCascadeAttenuation(cascadeIndices.x, offsets, cascadeShadowCoords[0], oneMinusNdotL); if (cascadeMix > 0.0 && cascadeIndices.y < getShadowCascadeCount()) { cascadeAttenuations.y = evalShadowCascadeAttenuation(cascadeIndices.y, offsets, cascadeShadowCoords[1], oneMinusNdotL); } float attenuation = mix(cascadeAttenuations.x, cascadeAttenuations.y, cascadeMix); // Falloff to max distance return mix(1.0, attenuation, evalShadowFalloff(viewDepth)); } <@endif@>