Skip to main content

shadow_biases/
shadow_biases.rs

1//! Demonstrates how shadow biases affect shadows in a 3d scene.
2
3use bevy::{
4    camera_controller::free_camera::{FreeCamera, FreeCameraPlugin},
5    light::ShadowFilteringMethod,
6    prelude::*,
7};
8
9fn main() {
10    App::new()
11        .add_plugins(DefaultPlugins)
12        .add_plugins(FreeCameraPlugin)
13        .add_systems(Startup, setup)
14        .add_systems(
15            Update,
16            (
17                cycle_filter_methods,
18                adjust_light_position,
19                adjust_point_light_biases,
20                toggle_light,
21                adjust_directional_light_biases,
22            ),
23        )
24        .run();
25}
26
27#[derive(Component)]
28struct Lights;
29
30/// set up a 3D scene to test shadow biases and perspective projections
31fn setup(
32    mut commands: Commands,
33    mut meshes: ResMut<Assets<Mesh>>,
34    mut materials: ResMut<Assets<StandardMaterial>>,
35) {
36    let spawn_plane_depth = 300.0f32;
37    let spawn_height = 2.0;
38    let sphere_radius = 0.25;
39
40    let white_handle = materials.add(StandardMaterial {
41        base_color: Color::WHITE,
42        perceptual_roughness: 1.0,
43        ..default()
44    });
45    let sphere_handle = meshes.add(Sphere::new(sphere_radius));
46
47    let light_transform = Transform::from_xyz(5.0, 5.0, 0.0).looking_at(Vec3::ZERO, Vec3::Y);
48    commands.spawn((
49        light_transform,
50        Visibility::default(),
51        Lights,
52        children![
53            (PointLight {
54                intensity: 0.0,
55                range: spawn_plane_depth,
56                color: Color::WHITE,
57                shadow_maps_enabled: true,
58                ..default()
59            }),
60            (DirectionalLight {
61                shadow_maps_enabled: true,
62                ..default()
63            })
64        ],
65    ));
66
67    // camera
68    commands.spawn((
69        Camera3d::default(),
70        Transform::from_xyz(-1.0, 1.0, 1.0).looking_at(Vec3::new(-1.0, 1.0, 0.0), Vec3::Y),
71        FreeCamera::default(),
72        ShadowFilteringMethod::Hardware2x2,
73    ));
74
75    for z_i32 in (-spawn_plane_depth as i32..=0).step_by(2) {
76        commands.spawn((
77            Mesh3d(sphere_handle.clone()),
78            MeshMaterial3d(white_handle.clone()),
79            Transform::from_xyz(
80                0.0,
81                if z_i32 % 4 == 0 {
82                    spawn_height
83                } else {
84                    sphere_radius
85                },
86                z_i32 as f32,
87            ),
88        ));
89    }
90
91    // ground plane
92    let plane_size = 2.0 * spawn_plane_depth;
93    commands.spawn((
94        Mesh3d(meshes.add(Plane3d::default().mesh().size(plane_size, plane_size))),
95        MeshMaterial3d(white_handle),
96    ));
97
98    commands.spawn((
99        Node {
100            position_type: PositionType::Absolute,
101            padding: UiRect::all(px(5)),
102            ..default()
103        },
104        BackgroundColor(Color::BLACK.with_alpha(0.75)),
105        GlobalZIndex(i32::MAX),
106        children![(
107            Text::default(),
108            children![
109                (TextSpan::new("Controls:\n")),
110                (TextSpan::new("R / Z - reset biases to default / zero\n")),
111                (TextSpan::new("L     - switch between directional and point lights [",)),
112                (TextSpan::new("DirectionalLight")),
113                (TextSpan::new("]\n")),
114                (TextSpan::new("F     - switch directional light filter methods [",)),
115                (TextSpan::new("Hardware2x2")),
116                (TextSpan::new("]\n")),
117                (TextSpan::new("1/2   - change point light depth bias [")),
118                (TextSpan::new("0.00")),
119                (TextSpan::new("]\n")),
120                (TextSpan::new("3/4   - change point light normal bias [")),
121                (TextSpan::new("0.0")),
122                (TextSpan::new("]\n")),
123                (TextSpan::new("5/6   - change direction light depth bias [")),
124                (TextSpan::new("0.00")),
125                (TextSpan::new("]\n")),
126                (TextSpan::new("7/8   - change direction light normal bias [",)),
127                (TextSpan::new("0.0")),
128                (TextSpan::new("]\n")),
129                (TextSpan::new(
130                    "left/right/up/down/pgup/pgdown - adjust light position (looking at 0,0,0) [",
131                )),
132                (TextSpan(format!("{:.1},", light_transform.translation.x))),
133                (TextSpan(format!(" {:.1},", light_transform.translation.y))),
134                (TextSpan(format!(" {:.1}", light_transform.translation.z))),
135                (TextSpan::new("]\n")),
136            ]
137        )],
138    ));
139}
140
141fn toggle_light(
142    input: Res<ButtonInput<KeyCode>>,
143    mut point_lights: Query<&mut PointLight>,
144    mut directional_lights: Query<&mut DirectionalLight>,
145    example_text: Single<Entity, With<Text>>,
146    mut writer: TextUiWriter,
147) {
148    if input.just_pressed(KeyCode::KeyL) {
149        for mut light in &mut point_lights {
150            light.intensity = if light.intensity == 0.0 {
151                *writer.text(*example_text, 4) = "PointLight".to_string();
152                light_consts::lumens::VERY_LARGE_CINEMA_LIGHT
153            } else {
154                0.0
155            };
156        }
157        for mut light in &mut directional_lights {
158            light.illuminance = if light.illuminance == 0.0 {
159                *writer.text(*example_text, 4) = "DirectionalLight".to_string();
160                light_consts::lux::AMBIENT_DAYLIGHT
161            } else {
162                0.0
163            };
164        }
165    }
166}
167
168fn adjust_light_position(
169    input: Res<ButtonInput<KeyCode>>,
170    mut lights: Query<&mut Transform, With<Lights>>,
171    example_text: Single<Entity, With<Text>>,
172    mut writer: TextUiWriter,
173) {
174    let mut offset = Vec3::ZERO;
175    if input.just_pressed(KeyCode::ArrowLeft) {
176        offset.x -= 1.0;
177    }
178    if input.just_pressed(KeyCode::ArrowRight) {
179        offset.x += 1.0;
180    }
181    if input.just_pressed(KeyCode::ArrowUp) {
182        offset.z -= 1.0;
183    }
184    if input.just_pressed(KeyCode::ArrowDown) {
185        offset.z += 1.0;
186    }
187    if input.just_pressed(KeyCode::PageDown) {
188        offset.y -= 1.0;
189    }
190    if input.just_pressed(KeyCode::PageUp) {
191        offset.y += 1.0;
192    }
193    if offset != Vec3::ZERO {
194        let example_text = *example_text;
195        for mut light in &mut lights {
196            light.translation += offset;
197            light.look_at(Vec3::ZERO, Vec3::Y);
198            *writer.text(example_text, 22) = format!("{:.1},", light.translation.x);
199            *writer.text(example_text, 23) = format!(" {:.1},", light.translation.y);
200            *writer.text(example_text, 24) = format!(" {:.1}", light.translation.z);
201        }
202    }
203}
204
205fn cycle_filter_methods(
206    input: Res<ButtonInput<KeyCode>>,
207    mut filter_methods: Query<&mut ShadowFilteringMethod>,
208    example_text: Single<Entity, With<Text>>,
209    mut writer: TextUiWriter,
210) {
211    if input.just_pressed(KeyCode::KeyF) {
212        for mut filter_method in &mut filter_methods {
213            let filter_method_string;
214            *filter_method = match *filter_method {
215                ShadowFilteringMethod::Hardware2x2 => {
216                    filter_method_string = "Gaussian".to_string();
217                    ShadowFilteringMethod::Gaussian
218                }
219                ShadowFilteringMethod::Gaussian => {
220                    filter_method_string = "Temporal".to_string();
221                    ShadowFilteringMethod::Temporal
222                }
223                ShadowFilteringMethod::Temporal => {
224                    filter_method_string = "Hardware2x2".to_string();
225                    ShadowFilteringMethod::Hardware2x2
226                }
227            };
228            *writer.text(*example_text, 7) = filter_method_string;
229        }
230    }
231}
232
233fn adjust_point_light_biases(
234    input: Res<ButtonInput<KeyCode>>,
235    mut query: Query<&mut PointLight>,
236    example_text: Single<Entity, With<Text>>,
237    mut writer: TextUiWriter,
238) {
239    let depth_bias_step_size = 0.01;
240    let normal_bias_step_size = 0.1;
241    for mut light in &mut query {
242        if input.just_pressed(KeyCode::Digit1) {
243            light.shadow_depth_bias -= depth_bias_step_size;
244        }
245        if input.just_pressed(KeyCode::Digit2) {
246            light.shadow_depth_bias += depth_bias_step_size;
247        }
248        if input.just_pressed(KeyCode::Digit3) {
249            light.shadow_normal_bias -= normal_bias_step_size;
250        }
251        if input.just_pressed(KeyCode::Digit4) {
252            light.shadow_normal_bias += normal_bias_step_size;
253        }
254        if input.just_pressed(KeyCode::KeyR) {
255            light.shadow_depth_bias = PointLight::DEFAULT_SHADOW_DEPTH_BIAS;
256            light.shadow_normal_bias = PointLight::DEFAULT_SHADOW_NORMAL_BIAS;
257        }
258        if input.just_pressed(KeyCode::KeyZ) {
259            light.shadow_depth_bias = 0.0;
260            light.shadow_normal_bias = 0.0;
261        }
262
263        *writer.text(*example_text, 10) = format!("{:.2}", light.shadow_depth_bias);
264        *writer.text(*example_text, 13) = format!("{:.1}", light.shadow_normal_bias);
265    }
266}
267
268fn adjust_directional_light_biases(
269    input: Res<ButtonInput<KeyCode>>,
270    mut query: Query<&mut DirectionalLight>,
271    example_text: Single<Entity, With<Text>>,
272    mut writer: TextUiWriter,
273) {
274    let depth_bias_step_size = 0.01;
275    let normal_bias_step_size = 0.1;
276    for mut light in &mut query {
277        if input.just_pressed(KeyCode::Digit5) {
278            light.shadow_depth_bias -= depth_bias_step_size;
279        }
280        if input.just_pressed(KeyCode::Digit6) {
281            light.shadow_depth_bias += depth_bias_step_size;
282        }
283        if input.just_pressed(KeyCode::Digit7) {
284            light.shadow_normal_bias -= normal_bias_step_size;
285        }
286        if input.just_pressed(KeyCode::Digit8) {
287            light.shadow_normal_bias += normal_bias_step_size;
288        }
289        if input.just_pressed(KeyCode::KeyR) {
290            light.shadow_depth_bias = DirectionalLight::DEFAULT_SHADOW_DEPTH_BIAS;
291            light.shadow_normal_bias = DirectionalLight::DEFAULT_SHADOW_NORMAL_BIAS;
292        }
293        if input.just_pressed(KeyCode::KeyZ) {
294            light.shadow_depth_bias = 0.0;
295            light.shadow_normal_bias = 0.0;
296        }
297
298        *writer.text(*example_text, 16) = format!("{:.2}", light.shadow_depth_bias);
299        *writer.text(*example_text, 19) = format!("{:.1}", light.shadow_normal_bias);
300    }
301}