Implement Shadows #24

Merged
SeanOMik merged 28 commits from feat/shadow-maps into main 2024-08-10 03:10:30 +00:00
2 changed files with 51 additions and 37 deletions
Showing only changes of commit c91ee67961 - Show all commits

View File

@ -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<DirectionalLight>, Has<PointLight>>)>() {
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<DirectionalLight>)>() {
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<PointLight>)>() {
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<ShadowSettings> 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,
}
}
}

View File

@ -256,22 +256,30 @@ fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, 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>(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<f32>(
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) {