Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/pcss.rs
9308 views
1
//! Demonstrates percentage-closer soft shadows (PCSS).
2
3
use std::f32::consts::PI;
4
5
#[cfg(feature = "free_camera")]
6
use bevy::camera_controller::free_camera::{FreeCamera, FreeCameraPlugin};
7
use bevy::{
8
anti_alias::taa::TemporalAntiAliasing,
9
camera::{
10
primitives::{CubemapFrusta, Frustum},
11
visibility::{CubemapVisibleEntities, VisibleMeshEntities},
12
},
13
core_pipeline::prepass::{DepthPrepass, MotionVectorPrepass},
14
light::{ShadowFilteringMethod, Skybox},
15
math::vec3,
16
prelude::*,
17
render::camera::TemporalJitter,
18
};
19
20
use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender};
21
22
#[path = "../helpers/widgets.rs"]
23
mod widgets;
24
25
/// The size of the light, which affects the size of the penumbras.
26
const LIGHT_RADIUS: f32 = 10.0;
27
28
/// The intensity of the point and spot lights.
29
const POINT_LIGHT_INTENSITY: f32 = 1_000_000_000.0;
30
31
/// The range in meters of the point and spot lights.
32
const POINT_LIGHT_RANGE: f32 = 110.0;
33
34
/// The depth bias for directional and spot lights. This value is set higher
35
/// than the default to avoid shadow acne.
36
const DIRECTIONAL_SHADOW_DEPTH_BIAS: f32 = 0.20;
37
38
/// The depth bias for point lights. This value is set higher than the default to
39
/// avoid shadow acne.
40
///
41
/// Unfortunately, there is a bit of Peter Panning with this value, because of
42
/// the distance and angle of the light. This can't be helped in this scene
43
/// without increasing the shadow map size beyond reasonable limits.
44
const POINT_SHADOW_DEPTH_BIAS: f32 = 0.35;
45
46
/// The near Z value for the shadow map, in meters. This is set higher than the
47
/// default in order to achieve greater resolution in the shadow map for point
48
/// and spot lights.
49
const SHADOW_MAP_NEAR_Z: f32 = 50.0;
50
51
/// The current application settings (light type, shadow filter, and the status
52
/// of PCSS).
53
#[derive(Resource)]
54
struct AppStatus {
55
/// The type of light presently in the scene: either directional or point.
56
light_type: LightType,
57
/// The type of shadow filter: Gaussian or temporal.
58
shadow_filter: ShadowFilter,
59
/// Whether soft shadows are enabled.
60
soft_shadows: bool,
61
}
62
63
impl Default for AppStatus {
64
fn default() -> Self {
65
Self {
66
light_type: default(),
67
shadow_filter: default(),
68
soft_shadows: true,
69
}
70
}
71
}
72
73
/// The type of light presently in the scene: directional, point, or spot.
74
#[derive(Clone, Copy, Default, PartialEq)]
75
enum LightType {
76
/// A directional light, with a cascaded shadow map.
77
#[default]
78
Directional,
79
/// A point light, with a cube shadow map.
80
Point,
81
/// A spot light, with a cube shadow map.
82
Spot,
83
}
84
85
/// The type of shadow filter.
86
///
87
/// Generally, `Gaussian` is preferred when temporal antialiasing isn't in use,
88
/// while `Temporal` is preferred when TAA is in use. In this example, this
89
/// setting also turns TAA on and off.
90
#[derive(Clone, Copy, Default, PartialEq)]
91
enum ShadowFilter {
92
/// The non-temporal Gaussian filter (Castano '13 for directional lights, an
93
/// analogous alternative for point and spot lights).
94
#[default]
95
NonTemporal,
96
/// The temporal Gaussian filter (Jimenez '14 for directional lights, an
97
/// analogous alternative for point and spot lights).
98
Temporal,
99
}
100
101
/// Each example setting that can be toggled in the UI.
102
#[derive(Clone, Copy, PartialEq)]
103
enum AppSetting {
104
/// The type of light presently in the scene: directional, point, or spot.
105
LightType(LightType),
106
/// The type of shadow filter.
107
ShadowFilter(ShadowFilter),
108
/// Whether PCSS is enabled or disabled.
109
SoftShadows(bool),
110
}
111
112
/// The example application entry point.
113
fn main() {
114
#[cfg(not(feature = "free_camera"))]
115
println!("Enable feature free_camera to add a free camera to this example");
116
117
App::new()
118
.init_resource::<AppStatus>()
119
.add_plugins((
120
DefaultPlugins.set(WindowPlugin {
121
primary_window: Some(Window {
122
title: "Bevy Percentage Closer Soft Shadows Example".into(),
123
..default()
124
}),
125
..default()
126
}),
127
#[cfg(feature = "free_camera")]
128
FreeCameraPlugin,
129
))
130
.add_message::<WidgetClickEvent<AppSetting>>()
131
.add_systems(Startup, setup)
132
.add_systems(Update, widgets::handle_ui_interactions::<AppSetting>)
133
.add_systems(
134
Update,
135
update_radio_buttons.after(widgets::handle_ui_interactions::<AppSetting>),
136
)
137
.add_systems(
138
Update,
139
(
140
handle_light_type_change,
141
handle_shadow_filter_change,
142
handle_pcss_toggle,
143
)
144
.after(widgets::handle_ui_interactions::<AppSetting>),
145
)
146
.run();
147
}
148
149
/// Creates all the objects in the scene.
150
fn setup(mut commands: Commands, asset_server: Res<AssetServer>, app_status: Res<AppStatus>) {
151
spawn_camera(&mut commands, &asset_server);
152
spawn_light(&mut commands, &app_status);
153
spawn_gltf_scene(&mut commands, &asset_server);
154
spawn_buttons(&mut commands);
155
}
156
157
/// Spawns the camera, with the initial shadow filtering method.
158
fn spawn_camera(commands: &mut Commands, asset_server: &AssetServer) {
159
commands
160
.spawn((
161
Camera3d::default(),
162
Transform::from_xyz(-12.912 * 0.7, 4.466 * 0.7, -10.624 * 0.7).with_rotation(
163
Quat::from_euler(EulerRot::YXZ, -134.76 / 180.0 * PI, -0.175, 0.0),
164
),
165
#[cfg(feature = "free_camera")]
166
FreeCamera::default(),
167
))
168
.insert(ShadowFilteringMethod::Gaussian)
169
// `TemporalJitter` is needed for TAA. Note that it does nothing without
170
// `TemporalAntiAliasSettings`.
171
.insert(TemporalJitter::default())
172
// We want MSAA off for TAA to work properly.
173
.insert(Msaa::Off)
174
// The depth prepass is needed for TAA.
175
.insert(DepthPrepass)
176
// The motion vector prepass is needed for TAA.
177
.insert(MotionVectorPrepass)
178
// Add a nice skybox.
179
.insert(Skybox {
180
image: asset_server.load("environment_maps/sky_skybox.ktx2"),
181
brightness: 500.0,
182
rotation: Quat::IDENTITY,
183
});
184
}
185
186
/// Spawns the initial light.
187
fn spawn_light(commands: &mut Commands, app_status: &AppStatus) {
188
// Because this light can become a directional light, point light, or spot
189
// light depending on the settings, we add the union of the components
190
// necessary for this light to behave as all three of those.
191
commands
192
.spawn((
193
create_directional_light(app_status),
194
Transform::from_rotation(Quat::from_array([
195
0.6539259,
196
-0.34646285,
197
0.36505926,
198
-0.5648683,
199
]))
200
.with_translation(vec3(57.693, 34.334, -6.422)),
201
))
202
// These two are needed for point lights.
203
.insert(CubemapVisibleEntities::default())
204
.insert(CubemapFrusta::default())
205
// These two are needed for spot lights.
206
.insert(VisibleMeshEntities::default())
207
.insert(Frustum::default());
208
}
209
210
/// Loads and spawns the glTF palm tree scene.
211
fn spawn_gltf_scene(commands: &mut Commands, asset_server: &AssetServer) {
212
commands.spawn(SceneRoot(
213
asset_server.load("models/PalmTree/PalmTree.gltf#Scene0"),
214
));
215
}
216
217
/// Spawns all the buttons at the bottom of the screen.
218
fn spawn_buttons(commands: &mut Commands) {
219
commands.spawn((
220
widgets::main_ui_node(),
221
children![
222
widgets::option_buttons(
223
"Light Type",
224
&[
225
(AppSetting::LightType(LightType::Directional), "Directional"),
226
(AppSetting::LightType(LightType::Point), "Point"),
227
(AppSetting::LightType(LightType::Spot), "Spot"),
228
],
229
),
230
widgets::option_buttons(
231
"Shadow Filter",
232
&[
233
(AppSetting::ShadowFilter(ShadowFilter::Temporal), "Temporal"),
234
(
235
AppSetting::ShadowFilter(ShadowFilter::NonTemporal),
236
"Non-Temporal",
237
),
238
],
239
),
240
widgets::option_buttons(
241
"Soft Shadows",
242
&[
243
(AppSetting::SoftShadows(true), "On"),
244
(AppSetting::SoftShadows(false), "Off"),
245
],
246
),
247
],
248
));
249
}
250
251
/// Updates the style of the radio buttons that enable and disable soft shadows
252
/// to reflect whether PCSS is enabled.
253
fn update_radio_buttons(
254
mut widgets: Query<
255
(
256
Entity,
257
Option<&mut BackgroundColor>,
258
Has<Text>,
259
&WidgetClickSender<AppSetting>,
260
),
261
Or<(With<RadioButton>, With<RadioButtonText>)>,
262
>,
263
app_status: Res<AppStatus>,
264
mut writer: TextUiWriter,
265
) {
266
for (entity, image, has_text, sender) in widgets.iter_mut() {
267
let selected = match **sender {
268
AppSetting::LightType(light_type) => light_type == app_status.light_type,
269
AppSetting::ShadowFilter(shadow_filter) => shadow_filter == app_status.shadow_filter,
270
AppSetting::SoftShadows(soft_shadows) => soft_shadows == app_status.soft_shadows,
271
};
272
273
if let Some(mut bg_color) = image {
274
widgets::update_ui_radio_button(&mut bg_color, selected);
275
}
276
if has_text {
277
widgets::update_ui_radio_button_text(entity, &mut writer, selected);
278
}
279
}
280
}
281
282
/// Handles requests from the user to change the type of light.
283
fn handle_light_type_change(
284
mut commands: Commands,
285
mut lights: Query<Entity, Or<(With<DirectionalLight>, With<PointLight>, With<SpotLight>)>>,
286
mut events: MessageReader<WidgetClickEvent<AppSetting>>,
287
mut app_status: ResMut<AppStatus>,
288
) {
289
for event in events.read() {
290
let AppSetting::LightType(light_type) = **event else {
291
continue;
292
};
293
app_status.light_type = light_type;
294
295
for light in lights.iter_mut() {
296
let mut light_commands = commands.entity(light);
297
light_commands
298
.remove::<DirectionalLight>()
299
.remove::<PointLight>()
300
.remove::<SpotLight>();
301
match light_type {
302
LightType::Point => {
303
light_commands.insert(create_point_light(&app_status));
304
}
305
LightType::Spot => {
306
light_commands.insert(create_spot_light(&app_status));
307
}
308
LightType::Directional => {
309
light_commands.insert(create_directional_light(&app_status));
310
}
311
}
312
}
313
}
314
}
315
316
/// Handles requests from the user to change the shadow filter method.
317
///
318
/// This system is also responsible for enabling and disabling TAA as
319
/// appropriate.
320
fn handle_shadow_filter_change(
321
mut commands: Commands,
322
mut cameras: Query<(Entity, &mut ShadowFilteringMethod)>,
323
mut events: MessageReader<WidgetClickEvent<AppSetting>>,
324
mut app_status: ResMut<AppStatus>,
325
) {
326
for event in events.read() {
327
let AppSetting::ShadowFilter(shadow_filter) = **event else {
328
continue;
329
};
330
app_status.shadow_filter = shadow_filter;
331
332
for (camera, mut shadow_filtering_method) in cameras.iter_mut() {
333
match shadow_filter {
334
ShadowFilter::NonTemporal => {
335
*shadow_filtering_method = ShadowFilteringMethod::Gaussian;
336
commands.entity(camera).remove::<TemporalAntiAliasing>();
337
}
338
ShadowFilter::Temporal => {
339
*shadow_filtering_method = ShadowFilteringMethod::Temporal;
340
commands
341
.entity(camera)
342
.insert(TemporalAntiAliasing::default());
343
}
344
}
345
}
346
}
347
}
348
349
/// Handles requests from the user to toggle soft shadows on and off.
350
fn handle_pcss_toggle(
351
mut lights: Query<AnyOf<(&mut DirectionalLight, &mut PointLight, &mut SpotLight)>>,
352
mut events: MessageReader<WidgetClickEvent<AppSetting>>,
353
mut app_status: ResMut<AppStatus>,
354
) {
355
for event in events.read() {
356
let AppSetting::SoftShadows(value) = **event else {
357
continue;
358
};
359
app_status.soft_shadows = value;
360
361
// Recreating the lights is the simplest way to toggle soft shadows.
362
for (directional_light, point_light, spot_light) in lights.iter_mut() {
363
if let Some(mut directional_light) = directional_light {
364
*directional_light = create_directional_light(&app_status);
365
}
366
if let Some(mut point_light) = point_light {
367
*point_light = create_point_light(&app_status);
368
}
369
if let Some(mut spot_light) = spot_light {
370
*spot_light = create_spot_light(&app_status);
371
}
372
}
373
}
374
}
375
376
/// Creates the [`DirectionalLight`] component with the appropriate settings.
377
fn create_directional_light(app_status: &AppStatus) -> DirectionalLight {
378
DirectionalLight {
379
shadow_maps_enabled: true,
380
soft_shadow_size: if app_status.soft_shadows {
381
Some(LIGHT_RADIUS)
382
} else {
383
None
384
},
385
shadow_depth_bias: DIRECTIONAL_SHADOW_DEPTH_BIAS,
386
..default()
387
}
388
}
389
390
/// Creates the [`PointLight`] component with the appropriate settings.
391
fn create_point_light(app_status: &AppStatus) -> PointLight {
392
PointLight {
393
intensity: POINT_LIGHT_INTENSITY,
394
range: POINT_LIGHT_RANGE,
395
shadow_maps_enabled: true,
396
radius: LIGHT_RADIUS,
397
soft_shadows_enabled: app_status.soft_shadows,
398
shadow_depth_bias: POINT_SHADOW_DEPTH_BIAS,
399
shadow_map_near_z: SHADOW_MAP_NEAR_Z,
400
..default()
401
}
402
}
403
404
/// Creates the [`SpotLight`] component with the appropriate settings.
405
fn create_spot_light(app_status: &AppStatus) -> SpotLight {
406
SpotLight {
407
intensity: POINT_LIGHT_INTENSITY,
408
range: POINT_LIGHT_RANGE,
409
radius: LIGHT_RADIUS,
410
shadow_maps_enabled: true,
411
soft_shadows_enabled: app_status.soft_shadows,
412
shadow_depth_bias: DIRECTIONAL_SHADOW_DEPTH_BIAS,
413
shadow_map_near_z: SHADOW_MAP_NEAR_Z,
414
..default()
415
}
416
}
417
418