From c91ee67961a4f4998c32961aa57f9867a8b52521 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Fri, 19 Jul 2024 17:56:27 -0400 Subject: [PATCH] render: improve shadow settings to make it possible to switch between PCF, PCSS, hardware 2x2 PCF, or disable filtering all together --- lyra-game/src/render/graph/passes/shadows.rs | 58 +++++++++++--------- lyra-game/src/render/shaders/base.wgsl | 30 ++++++---- 2 files changed, 51 insertions(+), 37 deletions(-) diff --git a/lyra-game/src/render/graph/passes/shadows.rs b/lyra-game/src/render/graph/passes/shadows.rs index cd217e7..8ab48f4 100644 --- a/lyra-game/src/render/graph/passes/shadows.rs +++ b/lyra-game/src/render/graph/passes/shadows.rs @@ -494,27 +494,8 @@ impl Node for ShadowMapsPass { // use a queue for storing atlas ids to add to entities after the entities are iterated let mut index_components_queue = VecDeque::new(); - /* for (entity, pos, (has_dir, has_point)) in world.view_iter::<(Entities, &Transform, Or, Has>)>() { - if !self.depth_maps.contains_key(&entity) { - // TODO: calculate far plane - let (light_type, far_plane) = if has_dir.is_some() { - (LightType::Directional, 45.0) - } else if has_point.is_some() { - (LightType::Point, 45.0) - } else { - todo!("Spot lights") - }; - - // TODO: dont pack the textures as they're added - let atlas_index = - self.create_depth_map(&context.queue, light_type, entity, *pos, far_plane); - index_components_queue.push_back((entity, atlas_index)); - } - } */ - for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has)>() { if !self.depth_maps.contains_key(&entity) { - // TODO: dont pack the textures as they're added let atlas_index = self.create_depth_map( &context.queue, LightType::Directional, @@ -528,7 +509,6 @@ impl Node for ShadowMapsPass { for (entity, pos, _) in world.view_iter::<(Entities, &Transform, Has)>() { if !self.depth_maps.contains_key(&entity) { - // TODO: dont pack the textures as they're added let atlas_index = self.create_depth_map(&context.queue, LightType::Point, entity, *pos, 30.0); index_components_queue.push_back((entity, atlas_index)); @@ -727,7 +707,6 @@ fn light_shadow_pass_impl<'a>( } let buffers = buffers.unwrap(); - //let uniform_index = light_uniforms_buffer.offset_of(light_depth_map.uniform_index[0]) as u32; pass.set_bind_group(0, &uniforms_bind_group, &[]); // Get the bindgroup for job's transform and bind to it using an offset. @@ -805,8 +784,19 @@ impl LightShadowMapAtlas { } } +#[derive(Default, Debug, Copy, Clone)] +pub enum ShadowFilteringMode { + None, + /// Uses hardware features for 2x2 PCF. + Pcf2x2, + Pcf, + #[default] + Pcss, +} + #[derive(Debug, Copy, Clone)] pub struct ShadowSettings { + pub filtering_mode: ShadowFilteringMode, /// How many PCF filtering samples are used per dimension. /// /// A value of 25 is common, this is maxed to 128. @@ -821,8 +811,9 @@ pub struct ShadowSettings { impl Default for ShadowSettings { fn default() -> Self { Self { - pcf_samples_num: 64, - pcss_blocker_search_samples: 36, + filtering_mode: ShadowFilteringMode::default(), + pcf_samples_num: 25, + pcss_blocker_search_samples: 25, } } } @@ -830,19 +821,34 @@ impl Default for ShadowSettings { const PCF_SAMPLES_NUM_MAX: u32 = 128; const PCSS_SAMPLES_NUM_MAX: u32 = 128; -/// Uniform version of [`ShadowSettings`] +/// Uniform version of [`ShadowSettings`]. +/// +/// If `pcf_samples_num` is set to zero, PCF and PCSS will be disabled. +/// If `pcf_samples_num` is set to 2, ONLY hardware 2x2 PCF will be used. +/// If `pcss_blocker_search_samples` is set to zero, PCSS will be disabled. #[repr(C)] #[derive(Debug, Copy, Clone, bytemuck::Pod, bytemuck::Zeroable)] struct ShadowSettingsUniform { + //use_pcf_hardware_2x2: u32, pcf_samples_num: u32, pcss_blocker_search_samples: u32, } impl From for ShadowSettingsUniform { fn from(value: ShadowSettings) -> Self { + let raw_pcf_samples = value.pcf_samples_num.min(PCF_SAMPLES_NUM_MAX); + let raw_pcss_samples = value.pcss_blocker_search_samples.min(PCSS_SAMPLES_NUM_MAX); + + let (pcf_samples, pcss_samples) = match value.filtering_mode { + ShadowFilteringMode::None => (0, 0), + ShadowFilteringMode::Pcf2x2 => (2, 0), + ShadowFilteringMode::Pcf => (raw_pcf_samples, 0), + ShadowFilteringMode::Pcss => (raw_pcf_samples, raw_pcss_samples), + }; + Self { - pcf_samples_num: value.pcf_samples_num.min(PCF_SAMPLES_NUM_MAX), - pcss_blocker_search_samples: value.pcss_blocker_search_samples.min(PCSS_SAMPLES_NUM_MAX), + pcf_samples_num: pcf_samples, + pcss_blocker_search_samples: pcss_samples, } } } diff --git a/lyra-game/src/render/shaders/base.wgsl b/lyra-game/src/render/shaders/base.wgsl index 3d8a627..3c7b17a 100755 --- a/lyra-game/src/render/shaders/base.wgsl +++ b/lyra-game/src/render/shaders/base.wgsl @@ -256,22 +256,30 @@ fn calc_shadow_dir_light(normal: vec3, light_dir: vec3, frag_pos_light // Remap xy to [0.0, 1.0] let xy_remapped = proj_coords.xy * 0.5 + 0.5; - - // get the atlas frame in [0; 1] in the atlas texture - // z is width, w is height - var region_rect = vec4(f32(shadow_u.atlas_frame.x), f32(shadow_u.atlas_frame.y), f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height)); - region_rect /= f32(atlas_dimensions.x); - let region_coords = vec2( - mix(region_rect.x, region_rect.x + region_rect.z, xy_remapped.x), - mix(region_rect.y, region_rect.y + region_rect.w, xy_remapped.y) - ); // use a bias to avoid shadow acne let bias = 0.005;//max(0.05 * (1.0 - dot(normal, light_dir)), 0.005); let current_depth = proj_coords.z - bias; - //var shadow = pcf_dir_light(region_coords, current_depth, shadow_u, 1.0); - var shadow = pcss_dir_light(xy_remapped, current_depth, shadow_u); + var shadow = 0.0; + if u_shadow_settings.pcf_samples_num > 0u && u_shadow_settings.pcss_blocker_search_samples > 0u { + shadow = pcss_dir_light(xy_remapped, current_depth, shadow_u); + } + // hardware 2x2 PCF via camparison sampler + else if u_shadow_settings.pcf_samples_num == 2u { + let region_coords = to_atlas_frame_coords(shadow_u, xy_remapped); + shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas_compare, region_coords, current_depth); + } else if u_shadow_settings.pcf_samples_num > 0u { + let atlas_dimensions = textureDimensions(t_shadow_maps_atlas); + // TODO: should texel size be using the entire atlas dimensions, or just the frame? + let texel_size = 1.0 / f32(atlas_dimensions.x); // f32(shadow_u.atlas_frame.width) + + shadow = pcf_dir_light(xy_remapped, current_depth, shadow_u, texel_size); + } else { // pcf_samples_num == 0 + let region_coords = to_atlas_frame_coords(shadow_u, xy_remapped); + let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, region_coords, 0.0); + shadow = select(1.0, 0.0, current_depth > closest_depth); + } // dont cast shadows outside the light's far plane if (proj_coords.z > 1.0) {