diff --git a/crates/bevy_pbr/src/render/shadow_sampling.wgsl b/crates/bevy_pbr/src/render/shadow_sampling.wgsl index 04d8920307a6c..f0ee07771564d 100644 --- a/crates/bevy_pbr/src/render/shadow_sampling.wgsl +++ b/crates/bevy_pbr/src/render/shadow_sampling.wgsl @@ -24,7 +24,7 @@ fn sample_shadow_map_hardware(light_local: vec2, depth: f32, array_index: i } // 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 { +fn sample_shadow_map_castano_thirteen(light_local: vec2, depth: f32, receiver_plane_depth_bias: vec2, 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; @@ -53,17 +53,26 @@ fn sample_shadow_map_castano_thirteen(light_local: vec2, depth: f32, array_ 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); + let sample_offset_u0_v0 = (vec2(u0, v0) * inv_shadow_map_size); + sum += uw0 * vw0 * sample_shadow_map_hardware(base_uv + sample_offset_u0_v0, depth + dot(sample_offset_u0_v0, receiver_plane_depth_bias), array_index); + let sample_offset_u1_v0 = (vec2(u1, v0) * inv_shadow_map_size); + sum += uw1 * vw0 * sample_shadow_map_hardware(base_uv + sample_offset_u1_v0, depth + dot(sample_offset_u1_v0, receiver_plane_depth_bias), array_index); + let sample_offset_u2_v0 = (vec2(u2, v0) * inv_shadow_map_size); + sum += uw2 * vw0 * sample_shadow_map_hardware(base_uv + sample_offset_u2_v0, depth + dot(sample_offset_u2_v0, receiver_plane_depth_bias), array_index); + + let sample_offset_u0_v1 = (vec2(u0, v1) * inv_shadow_map_size); + sum += uw0 * vw1 * sample_shadow_map_hardware(base_uv + sample_offset_u0_v1, depth + dot(sample_offset_u0_v1, receiver_plane_depth_bias), array_index); + let sample_offset_u1_v1 = (vec2(u1, v1) * inv_shadow_map_size); + sum += uw1 * vw1 * sample_shadow_map_hardware(base_uv + sample_offset_u1_v1, depth + dot(sample_offset_u1_v1, receiver_plane_depth_bias), array_index); + let sample_offset_u2_v1 = (vec2(u2, v1) * inv_shadow_map_size); + sum += uw2 * vw1 * sample_shadow_map_hardware(base_uv + sample_offset_u2_v1, depth + dot(sample_offset_u2_v1, receiver_plane_depth_bias), array_index); + + let sample_offset_u0_v2 = (vec2(u0, v2) * inv_shadow_map_size); + sum += uw0 * vw2 * sample_shadow_map_hardware(base_uv + sample_offset_u0_v2, depth + dot(sample_offset_u0_v2, receiver_plane_depth_bias), array_index); + let sample_offset_u1_v2 = (vec2(u1, v2) * inv_shadow_map_size); + sum += uw1 * vw2 * sample_shadow_map_hardware(base_uv + sample_offset_u1_v2, depth + dot(sample_offset_u1_v2, receiver_plane_depth_bias), array_index); + let sample_offset_u2_v2 = (vec2(u2, v2) * inv_shadow_map_size); + sum += uw2 * vw2 * sample_shadow_map_hardware(base_uv + sample_offset_u2_v2, depth + dot(sample_offset_u2_v2, receiver_plane_depth_bias), array_index); return sum * (1.0 / 144.0); } @@ -79,7 +88,7 @@ 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 { +fn sample_shadow_map_jimenez_fourteen(light_local: vec2, depth: f32, receiver_plane_depth_bias: vec2, 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); @@ -104,22 +113,85 @@ fn sample_shadow_map_jimenez_fourteen(light_local: vec2, depth: f32, array_ let sample_offset8 = (rotation_matrix * vec2( 0.1250, 0.0000)) * 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); + sum += sample_shadow_map_hardware( + light_local + sample_offset1, + depth + dot(sample_offset1, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset2, + depth + dot(sample_offset2, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset3, + depth + dot(sample_offset3, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset4, + depth + dot(sample_offset4, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset5, + depth + dot(sample_offset5, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset6, + depth + dot(sample_offset6, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset7, + depth + dot(sample_offset7, receiver_plane_depth_bias), + array_index, + ); + sum += sample_shadow_map_hardware( + light_local + sample_offset8, + depth + dot(sample_offset8, receiver_plane_depth_bias), + array_index, + ); return sum / 8.0; } +// Receiver Plane Depth Bias +// Isidoro06 - Slides 36-40 https://web.archive.org/web/20230309054654/http://developer.amd.com/wordpress/media/2012/10/Isidoro-ShadowMapping.pdf +// This implementation is from https://github.com/TheRealMJP/Shadows/blob/1a6d90c92ea58ccddb0dcd32b035c58b8f7784f4/Shadows/Mesh.hlsl#L102 +fn compute_receiver_plane_depth_bias(tex_coord_dx: vec3, tex_coord_dy: vec3) -> vec2 { + var bias_uv: vec2 = vec2( + tex_coord_dy.y * tex_coord_dx.z - tex_coord_dx.y * tex_coord_dy.z, + tex_coord_dx.x * tex_coord_dy.z - tex_coord_dy.x * tex_coord_dx.z + ); + bias_uv = bias_uv * 1.0 / ((tex_coord_dx.x * tex_coord_dy.y) - (tex_coord_dx.y * tex_coord_dy.x)); + return bias_uv; +} + fn sample_shadow_map(light_local: vec2, depth: f32, array_index: i32, texel_size: f32) -> f32 { +#ifndef SHADOW_FILTER_METHOD_HARDWARE_2X2 + let shadow_pos = vec3(light_local, depth); + let receiver_plane_depth_bias = compute_receiver_plane_depth_bias( + dpdx(shadow_pos), + dpdy(shadow_pos), + ); +#endif + #ifdef SHADOW_FILTER_METHOD_CASTANO_13 - return sample_shadow_map_castano_thirteen(light_local, depth, array_index); + return sample_shadow_map_castano_thirteen( + light_local, + depth, + receiver_plane_depth_bias, + array_index, + ); #else ifdef SHADOW_FILTER_METHOD_JIMENEZ_14 - return sample_shadow_map_jimenez_fourteen(light_local, depth, array_index, texel_size); + return sample_shadow_map_jimenez_fourteen( + light_local, + depth, + receiver_plane_depth_bias, + array_index, + texel_size, + ); #else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2 return sample_shadow_map_hardware(light_local, depth, array_index); #else diff --git a/examples/3d/shadow_biases.rs b/examples/3d/shadow_biases.rs index cffd6bb91f603..4288294e9c05e 100644 --- a/examples/3d/shadow_biases.rs +++ b/examples/3d/shadow_biases.rs @@ -12,6 +12,7 @@ fn main() { Update, ( cycle_filter_methods, + adjust_light_position, adjust_point_light_biases, toggle_light, adjust_directional_light_biases, @@ -21,6 +22,9 @@ fn main() { .run(); } +#[derive(Component)] +struct Lights; + /// set up a 3D scene to test shadow biases and perspective projections fn setup( mut commands: Commands, @@ -44,36 +48,39 @@ fn setup( .unwrap(), ); - commands.spawn(PointLightBundle { - transform: Transform::from_xyz(5.0, 5.0, 0.0), - point_light: PointLight { - intensity: 0.0, - range: spawn_plane_depth, - color: Color::WHITE, - shadow_depth_bias: 0.0, - shadow_normal_bias: 0.0, - shadows_enabled: true, - ..default() - }, - ..default() - }); - - commands.spawn(DirectionalLightBundle { - directional_light: DirectionalLight { - illuminance: 100000.0, - shadow_depth_bias: 0.0, - shadow_normal_bias: 0.0, - shadows_enabled: true, - ..default() - }, - transform: Transform::from_rotation(Quat::from_euler( - EulerRot::ZYX, - 0.0, - PI / 2., - -PI / 4., - )), - ..default() - }); + let light_transform = Transform::from_xyz(5.0, 5.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y); + commands + .spawn(( + SpatialBundle { + transform: light_transform, + ..default() + }, + Lights, + )) + .with_children(|builder| { + builder.spawn(PointLightBundle { + point_light: PointLight { + intensity: 0.0, + range: spawn_plane_depth, + color: Color::WHITE, + shadow_depth_bias: 0.0, + shadow_normal_bias: 0.0, + shadows_enabled: true, + ..default() + }, + ..default() + }); + builder.spawn(DirectionalLightBundle { + directional_light: DirectionalLight { + illuminance: 100000.0, + shadow_depth_bias: 0.0, + shadow_normal_bias: 0.0, + shadows_enabled: true, + ..default() + }, + ..default() + }); + }); // camera commands.spawn(( @@ -90,7 +97,15 @@ fn setup( commands.spawn(PbrBundle { mesh: sphere_handle.clone(), material: white_handle.clone(), - transform: Transform::from_xyz(0.0, spawn_height, z_i32 as f32), + transform: Transform::from_xyz( + 0.0, + if z_i32 % 4 == 0 { + spawn_height + } else { + sphere_radius + }, + z_i32 as f32, + ), ..default() }); } @@ -106,43 +121,69 @@ fn setup( font_size: 20., ..default() }; - commands.spawn( - TextBundle::from_sections([ - TextSection::new("Controls:\n", style.clone()), - TextSection::new("WSAD - forward/back/strafe left/right\n", style.clone()), - TextSection::new("E / Q - up / down\n", style.clone()), - TextSection::new( - "L - switch between directional and point lights [", - style.clone(), - ), - TextSection::new("DirectionalLight", style.clone()), - TextSection::new("]\n", style.clone()), - TextSection::new("F - switch between filter methods [", style.clone()), - TextSection::new("Hardware2x2", style.clone()), - TextSection::new("]\n", style.clone()), - TextSection::new("1/2 - change point light depth bias [", style.clone()), - TextSection::new("0.00", style.clone()), - TextSection::new("]\n", style.clone()), - TextSection::new("3/4 - change point light normal bias [", style.clone()), - TextSection::new("0.0", style.clone()), - TextSection::new("]\n", style.clone()), - TextSection::new("5/6 - change direction light depth bias [", style.clone()), - TextSection::new("0.00", style.clone()), - TextSection::new("]\n", style.clone()), - TextSection::new( - "7/8 - change direction light normal bias [", - style.clone(), - ), - TextSection::new("0.0", style.clone()), - TextSection::new("]\n", style.clone()), - ]) - .with_style(Style { - position_type: PositionType::Absolute, - top: Val::Px(12.0), - left: Val::Px(12.0), + commands + .spawn(NodeBundle { + style: Style { + position_type: PositionType::Absolute, + padding: UiRect::all(Val::Px(5.0)), + ..default() + }, + z_index: ZIndex::Global(i32::MAX), + background_color: Color::BLACK.with_a(0.75).into(), ..default() - }), - ); + }) + .with_children(|c| { + c.spawn(TextBundle::from_sections([ + TextSection::new("Controls:\n", style.clone()), + TextSection::new("WSAD - forward/back/strafe left/right\n", style.clone()), + TextSection::new("E / Q - up / down\n", style.clone()), + TextSection::new("R / Z - reset biases to default / zero\n", style.clone()), + TextSection::new( + "L - switch between directional and point lights [", + style.clone(), + ), + TextSection::new("DirectionalLight", style.clone()), + TextSection::new("]\n", style.clone()), + TextSection::new( + "F - switch directional light filter methods [", + style.clone(), + ), + TextSection::new("Hardware2x2", style.clone()), + TextSection::new("]\n", style.clone()), + TextSection::new("1/2 - change point light depth bias [", style.clone()), + TextSection::new("0.00", style.clone()), + TextSection::new("]\n", style.clone()), + TextSection::new("3/4 - change point light normal bias [", style.clone()), + TextSection::new("0.0", style.clone()), + TextSection::new("]\n", style.clone()), + TextSection::new("5/6 - change direction light depth bias [", style.clone()), + TextSection::new("0.00", style.clone()), + TextSection::new("]\n", style.clone()), + TextSection::new( + "7/8 - change direction light normal bias [", + style.clone(), + ), + TextSection::new("0.0", style.clone()), + TextSection::new("]\n", style.clone()), + TextSection::new( + "left/right/up/down/pgup/pgdown - adjust light position (looking at 0,0,0) [", + style.clone(), + ), + TextSection::new( + format!("{:.1},", light_transform.translation.x), + style.clone(), + ), + TextSection::new( + format!(" {:.1},", light_transform.translation.y), + style.clone(), + ), + TextSection::new( + format!(" {:.1}", light_transform.translation.z), + style.clone(), + ), + TextSection::new("]\n", style.clone()), + ])); + }); } fn toggle_light( @@ -154,7 +195,7 @@ fn toggle_light( if input.just_pressed(KeyCode::L) { for mut light in &mut point_lights { light.intensity = if light.intensity == 0.0 { - example_text.single_mut().sections[4].value = "PointLight".to_string(); + example_text.single_mut().sections[5].value = "PointLight".to_string(); 100000000.0 } else { 0.0 @@ -162,7 +203,7 @@ fn toggle_light( } for mut light in &mut directional_lights { light.illuminance = if light.illuminance == 0.0 { - example_text.single_mut().sections[4].value = "DirectionalLight".to_string(); + example_text.single_mut().sections[5].value = "DirectionalLight".to_string(); 100000.0 } else { 0.0 @@ -171,6 +212,42 @@ fn toggle_light( } } +fn adjust_light_position( + input: Res>, + mut lights: Query<&mut Transform, With>, + mut example_text: Query<&mut Text>, +) { + let mut offset = Vec3::ZERO; + if input.just_pressed(KeyCode::Left) { + offset.x -= 1.0; + } + if input.just_pressed(KeyCode::Right) { + offset.x += 1.0; + } + if input.just_pressed(KeyCode::Up) { + offset.z -= 1.0; + } + if input.just_pressed(KeyCode::Down) { + offset.z += 1.0; + } + if input.just_pressed(KeyCode::PageDown) { + offset.y -= 1.0; + } + if input.just_pressed(KeyCode::PageUp) { + offset.y += 1.0; + } + if offset != Vec3::ZERO { + let mut example_text = example_text.single_mut(); + for mut light in &mut lights { + light.translation += offset; + light.look_at(Vec3::ZERO, Vec3::Y); + example_text.sections[23].value = format!("{:.1},", light.translation.x); + example_text.sections[24].value = format!(" {:.1},", light.translation.y); + example_text.sections[25].value = format!(" {:.1}", light.translation.z); + } + } +} + fn cycle_filter_methods( input: Res>, mut filter_methods: Query<&mut ShadowFilteringMethod>, @@ -193,7 +270,7 @@ fn cycle_filter_methods( ShadowFilteringMethod::Hardware2x2 } }; - example_text.single_mut().sections[7].value = filter_method_string; + example_text.single_mut().sections[8].value = filter_method_string; } } } @@ -208,24 +285,27 @@ fn adjust_point_light_biases( for mut light in &mut query { if input.just_pressed(KeyCode::Key1) { light.shadow_depth_bias -= depth_bias_step_size; - example_text.single_mut().sections[10].value = - format!("{:.2}", light.shadow_depth_bias); } if input.just_pressed(KeyCode::Key2) { light.shadow_depth_bias += depth_bias_step_size; - example_text.single_mut().sections[10].value = - format!("{:.2}", light.shadow_depth_bias); } if input.just_pressed(KeyCode::Key3) { light.shadow_normal_bias -= normal_bias_step_size; - example_text.single_mut().sections[13].value = - format!("{:.1}", light.shadow_normal_bias); } if input.just_pressed(KeyCode::Key4) { light.shadow_normal_bias += normal_bias_step_size; - example_text.single_mut().sections[13].value = - format!("{:.1}", light.shadow_normal_bias); } + if input.just_pressed(KeyCode::R) { + light.shadow_depth_bias = PointLight::DEFAULT_SHADOW_DEPTH_BIAS; + light.shadow_normal_bias = PointLight::DEFAULT_SHADOW_NORMAL_BIAS; + } + if input.just_pressed(KeyCode::Z) { + light.shadow_depth_bias = 0.0; + light.shadow_normal_bias = 0.0; + } + + example_text.single_mut().sections[11].value = format!("{:.2}", light.shadow_depth_bias); + example_text.single_mut().sections[14].value = format!("{:.1}", light.shadow_normal_bias); } } @@ -239,24 +319,27 @@ fn adjust_directional_light_biases( for mut light in &mut query { if input.just_pressed(KeyCode::Key5) { light.shadow_depth_bias -= depth_bias_step_size; - example_text.single_mut().sections[16].value = - format!("{:.2}", light.shadow_depth_bias); } if input.just_pressed(KeyCode::Key6) { light.shadow_depth_bias += depth_bias_step_size; - example_text.single_mut().sections[16].value = - format!("{:.2}", light.shadow_depth_bias); } if input.just_pressed(KeyCode::Key7) { light.shadow_normal_bias -= normal_bias_step_size; - example_text.single_mut().sections[19].value = - format!("{:.1}", light.shadow_normal_bias); } if input.just_pressed(KeyCode::Key8) { light.shadow_normal_bias += normal_bias_step_size; - example_text.single_mut().sections[19].value = - format!("{:.1}", light.shadow_normal_bias); } + if input.just_pressed(KeyCode::R) { + light.shadow_depth_bias = DirectionalLight::DEFAULT_SHADOW_DEPTH_BIAS; + light.shadow_normal_bias = DirectionalLight::DEFAULT_SHADOW_NORMAL_BIAS; + } + if input.just_pressed(KeyCode::Z) { + light.shadow_depth_bias = 0.0; + light.shadow_normal_bias = 0.0; + } + + example_text.single_mut().sections[17].value = format!("{:.2}", light.shadow_depth_bias); + example_text.single_mut().sections[20].value = format!("{:.1}", light.shadow_normal_bias); } }