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