#define_import_path bevy_pbr::shadow_sampling #import bevy_pbr::{ mesh_view_bindings as view_bindings, utils::{PI, interleaved_gradient_noise}, utils, } // Do the lookup, using HW 2x2 PCF and comparison fn sample_shadow_map_hardware(light_local: vec2, depth: f32, array_index: i32) -> f32 { #ifdef NO_ARRAY_TEXTURES_SUPPORT return textureSampleCompare( view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler, light_local, depth, ); #else return textureSampleCompareLevel( view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler, light_local, array_index, depth, ); #endif } // https://web.archive.org/web/20230210095515/http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1 fn sample_shadow_map_castano_thirteen(light_local: vec2, depth: f32, array_index: i32) -> f32 { let shadow_map_size = vec2(textureDimensions(view_bindings::directional_shadow_textures)); let inv_shadow_map_size = 1.0 / shadow_map_size; let uv = light_local * shadow_map_size; var base_uv = floor(uv + 0.5); let s = (uv.x + 0.5 - base_uv.x); let t = (uv.y + 0.5 - base_uv.y); base_uv -= 0.5; base_uv *= inv_shadow_map_size; let uw0 = (4.0 - 3.0 * s); let uw1 = 7.0; let uw2 = (1.0 + 3.0 * s); let u0 = (3.0 - 2.0 * s) / uw0 - 2.0; let u1 = (3.0 + s) / uw1; let u2 = s / uw2 + 2.0; let vw0 = (4.0 - 3.0 * t); let vw1 = 7.0; let vw2 = (1.0 + 3.0 * t); let v0 = (3.0 - 2.0 * t) / vw0 - 2.0; let v1 = (3.0 + t) / vw1; let v2 = t / vw2 + 2.0; var sum = 0.0; sum += uw0 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u0, v0) * inv_shadow_map_size), depth, array_index); sum += uw1 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u1, v0) * inv_shadow_map_size), depth, array_index); sum += uw2 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u2, v0) * inv_shadow_map_size), depth, array_index); sum += uw0 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u0, v1) * inv_shadow_map_size), depth, array_index); sum += uw1 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u1, v1) * inv_shadow_map_size), depth, array_index); sum += uw2 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u2, v1) * inv_shadow_map_size), depth, array_index); sum += uw0 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u0, v2) * inv_shadow_map_size), depth, array_index); sum += uw1 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u1, v2) * inv_shadow_map_size), depth, array_index); sum += uw2 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u2, v2) * inv_shadow_map_size), depth, array_index); return sum * (1.0 / 144.0); } fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 { return min2 + (value - min1) * (max2 - min2) / (max1 - min1); } fn sample_shadow_map_jimenez_fourteen(light_local: vec2, depth: f32, array_index: i32, texel_size: f32) -> f32 { let shadow_map_size = vec2(textureDimensions(view_bindings::directional_shadow_textures)); let random_angle = 2.0 * PI * interleaved_gradient_noise(light_local * shadow_map_size, view_bindings::globals.frame_count); let m = vec2(sin(random_angle), cos(random_angle)); let rotation_matrix = mat2x2( m.y, -m.x, m.x, m.y ); // Empirically chosen fudge factor to make PCF look better across different CSM cascades let f = map(0.00390625, 0.022949219, 0.015, 0.035, texel_size); let uv_offset_scale = f / (texel_size * shadow_map_size); // https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135) let sample_offset1 = (rotation_matrix * utils::SPIRAL_OFFSET_0_) * uv_offset_scale; let sample_offset2 = (rotation_matrix * utils::SPIRAL_OFFSET_1_) * uv_offset_scale; let sample_offset3 = (rotation_matrix * utils::SPIRAL_OFFSET_2_) * uv_offset_scale; let sample_offset4 = (rotation_matrix * utils::SPIRAL_OFFSET_3_) * uv_offset_scale; let sample_offset5 = (rotation_matrix * utils::SPIRAL_OFFSET_4_) * uv_offset_scale; let sample_offset6 = (rotation_matrix * utils::SPIRAL_OFFSET_5_) * uv_offset_scale; let sample_offset7 = (rotation_matrix * utils::SPIRAL_OFFSET_6_) * uv_offset_scale; let sample_offset8 = (rotation_matrix * utils::SPIRAL_OFFSET_7_) * uv_offset_scale; var sum = 0.0; sum += sample_shadow_map_hardware(light_local + sample_offset1, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset2, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset3, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset4, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset5, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset6, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset7, depth, array_index); sum += sample_shadow_map_hardware(light_local + sample_offset8, depth, array_index); return sum / 8.0; } fn sample_shadow_map(light_local: vec2, depth: f32, array_index: i32, texel_size: f32) -> f32 { #ifdef SHADOW_FILTER_METHOD_CASTANO_13 return sample_shadow_map_castano_thirteen(light_local, depth, array_index); #else ifdef SHADOW_FILTER_METHOD_JIMENEZ_14 return sample_shadow_map_jimenez_fourteen(light_local, depth, array_index, texel_size); #else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2 return sample_shadow_map_hardware(light_local, depth, array_index); #else // This needs a default return value to avoid shader compilation errors if it's compiled with no SHADOW_FILTER_METHOD_* defined. // (eg. if the normal prepass is enabled it ends up compiling this due to the normal prepass depending on pbr_functions, which depends on shadows) // This should never actually get used, as anyone using bevy's lighting/shadows should always have a SHADOW_FILTER_METHOD defined. // Set to 0 to make it obvious that something is wrong. return 0.0; #endif }