Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/contact_shadows.rs
9308 views
1
//! Demonstrates contact shadows, also known as screen-space shadows.
2
3
use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender};
4
use bevy::anti_alias::taa::TemporalAntiAliasing;
5
use bevy::core_pipeline::tonemapping::Tonemapping;
6
use bevy::light::Skybox;
7
use bevy::pbr::ScreenSpaceAmbientOcclusion;
8
use bevy::post_process::motion_blur::MotionBlur;
9
use bevy::window::{CursorIcon, PrimaryWindow, SystemCursorIcon};
10
use bevy::{
11
camera::Hdr, ecs::message::MessageReader, light::NotShadowReceiver, pbr::ContactShadows,
12
post_process::bloom::Bloom, prelude::*,
13
};
14
15
#[path = "../helpers/widgets.rs"]
16
mod widgets;
17
18
#[derive(Clone, Copy, PartialEq, Default, Debug)]
19
enum ContactShadowState {
20
#[default]
21
Enabled,
22
Disabled,
23
}
24
25
#[derive(Clone, Copy, PartialEq, Default, Debug)]
26
enum ShadowMaps {
27
#[default]
28
Enabled,
29
Disabled,
30
}
31
32
#[derive(Clone, Copy, PartialEq, Default, Debug)]
33
enum LightRotation {
34
Stationary,
35
#[default]
36
Rotating,
37
}
38
39
#[derive(Clone, Copy, PartialEq, Default, Debug)]
40
enum LightType {
41
Directional,
42
#[default]
43
Point,
44
Spot,
45
}
46
47
#[derive(Clone, Copy, PartialEq, Default, Debug)]
48
enum ReceiveShadows {
49
#[default]
50
Enabled,
51
Disabled,
52
}
53
54
/// Each example setting that can be toggled in the UI.
55
#[derive(Clone, Copy, PartialEq)]
56
enum ExampleSetting {
57
ContactShadows(ContactShadowState),
58
ShadowMaps(ShadowMaps),
59
LightRotation(LightRotation),
60
LightType(LightType),
61
ReceiveShadows(ReceiveShadows),
62
}
63
64
const LIGHT_ROTATION_SPEED: f32 = 0.002;
65
66
#[derive(Resource, Default)]
67
struct AppStatus {
68
contact_shadows: ContactShadowState,
69
shadow_maps: ShadowMaps,
70
light_rotation: LightRotation,
71
light_type: LightType,
72
receive_shadows: ReceiveShadows,
73
}
74
75
#[derive(Component)]
76
struct LightContainer;
77
78
#[derive(Component)]
79
struct GroundPlane;
80
81
fn main() {
82
App::new()
83
.add_plugins((
84
DefaultPlugins.set(WindowPlugin {
85
primary_window: Some(Window {
86
title: "Bevy Contact Shadows Example".into(),
87
..default()
88
}),
89
..default()
90
}),
91
MeshPickingPlugin,
92
))
93
.init_resource::<AppStatus>()
94
.insert_resource(GlobalAmbientLight::NONE)
95
.add_message::<WidgetClickEvent<ExampleSetting>>()
96
.add_systems(Startup, setup)
97
.add_systems(Update, rotate_light)
98
.add_systems(
99
Update,
100
(
101
widgets::handle_ui_interactions::<ExampleSetting>,
102
update_radio_buttons.after(widgets::handle_ui_interactions::<ExampleSetting>),
103
handle_setting_change.after(widgets::handle_ui_interactions::<ExampleSetting>),
104
),
105
)
106
.run();
107
}
108
109
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
110
commands.spawn((
111
Camera3d::default(),
112
Transform::from_xyz(-0.8, 0.6, -0.8).looking_at(Vec3::new(0.0, 0.35, 0.0), Vec3::Y),
113
ContactShadows::default(),
114
TemporalAntiAliasing::default(), // Contact shadows and AO benefit from TAA
115
// Everything past this point is extra to look pretty.
116
Bloom::default(),
117
Hdr,
118
Skybox {
119
brightness: 1000.0,
120
image: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
121
..default()
122
},
123
EnvironmentMapLight {
124
diffuse_map: asset_server.load("environment_maps/pisa_diffuse_rgb9e5_zstd.ktx2"),
125
specular_map: asset_server.load("environment_maps/pisa_specular_rgb9e5_zstd.ktx2"),
126
intensity: 1000.0,
127
..default()
128
},
129
ScreenSpaceAmbientOcclusion::default(),
130
Msaa::Off,
131
Tonemapping::AcesFitted,
132
MotionBlur {
133
shutter_angle: 2.0, // This is really just for fun when spinning the model
134
..default()
135
},
136
));
137
138
let directional_light = commands
139
.spawn((
140
DirectionalLight {
141
shadow_maps_enabled: true,
142
contact_shadows_enabled: true,
143
..default()
144
},
145
Visibility::Hidden,
146
))
147
.id();
148
149
let point_light = commands
150
.spawn((
151
PointLight {
152
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT * 0.4,
153
shadow_maps_enabled: true,
154
contact_shadows_enabled: true,
155
..default()
156
},
157
Visibility::Visible,
158
))
159
.id();
160
161
let spot_light = commands
162
.spawn((
163
SpotLight {
164
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT * 0.4,
165
shadow_maps_enabled: true,
166
contact_shadows_enabled: true,
167
..default()
168
},
169
Visibility::Hidden,
170
))
171
.id();
172
173
commands
174
.spawn((
175
Transform::from_xyz(-0.8, 1.5, 1.2).looking_at(Vec3::ZERO, Vec3::Y),
176
Visibility::default(),
177
LightContainer,
178
))
179
.add_child(directional_light)
180
.add_child(point_light)
181
.add_child(spot_light);
182
183
commands
184
.spawn((
185
SceneRoot(asset_server.load(
186
GltfAssetLabel::Scene(0).from_asset("models/FlightHelmet/FlightHelmet.gltf"),
187
)),
188
Transform::from_rotation(Quat::from_rotation_y(std::f32::consts::PI)),
189
))
190
.observe(
191
|event: On<Pointer<Drag>>,
192
mut query: Query<&mut Transform, With<SceneRoot>>,
193
mut commands: Commands,
194
mut window: Query<Entity, With<PrimaryWindow>>| {
195
for mut transform in query.iter_mut() {
196
transform.rotate_y(event.delta.x * 0.01);
197
}
198
commands
199
.entity(window.single_mut().unwrap())
200
.insert(CursorIcon::System(SystemCursorIcon::Grabbing));
201
},
202
)
203
.observe(
204
|_: On<Pointer<Over>>,
205
mut commands: Commands,
206
mut window: Query<Entity, With<PrimaryWindow>>| {
207
commands
208
.entity(window.single_mut().unwrap())
209
.insert(CursorIcon::System(SystemCursorIcon::Grab));
210
},
211
)
212
.observe(
213
|_: On<Pointer<Out>>,
214
mut commands: Commands,
215
mut window: Query<Entity, With<PrimaryWindow>>| {
216
commands
217
.entity(window.single_mut().unwrap())
218
.insert(CursorIcon::System(SystemCursorIcon::Default));
219
},
220
)
221
.observe(
222
|_: On<Pointer<DragEnd>>,
223
mut commands: Commands,
224
mut window: Query<Entity, With<PrimaryWindow>>| {
225
commands
226
.entity(window.single_mut().unwrap())
227
.insert(CursorIcon::System(SystemCursorIcon::Default));
228
},
229
);
230
231
commands.spawn((
232
Mesh3d(asset_server.add(Circle::default().mesh().into())),
233
MeshMaterial3d(asset_server.add(StandardMaterial {
234
base_color: Color::srgb(0.06, 0.06, 0.06),
235
..default()
236
})),
237
Transform::from_rotation(Quat::from_axis_angle(Vec3::X, -std::f32::consts::FRAC_PI_2)),
238
GroundPlane,
239
));
240
241
spawn_buttons(&mut commands);
242
243
commands.spawn((
244
Node {
245
position_type: PositionType::Absolute,
246
top: px(12.0),
247
left: px(0.0),
248
right: px(0.0),
249
justify_content: JustifyContent::Center,
250
..default()
251
},
252
children![(
253
Text::new("Drag model to spin"),
254
TextFont {
255
font_size: FontSize::Px(18.0),
256
..default()
257
},
258
)],
259
));
260
}
261
262
fn rotate_light(
263
mut lights: Query<&mut Transform, With<LightContainer>>,
264
app_status: Res<AppStatus>,
265
) {
266
if app_status.light_rotation != LightRotation::Rotating {
267
return;
268
}
269
270
for mut transform in lights.iter_mut() {
271
transform.rotate_around(Vec3::ZERO, Quat::from_rotation_y(LIGHT_ROTATION_SPEED));
272
}
273
}
274
275
fn spawn_buttons(commands: &mut Commands) {
276
commands.spawn((
277
widgets::main_ui_node(),
278
children![
279
widgets::option_buttons(
280
"Contact Shadows",
281
&[
282
(
283
ExampleSetting::ContactShadows(ContactShadowState::Enabled),
284
"On"
285
),
286
(
287
ExampleSetting::ContactShadows(ContactShadowState::Disabled),
288
"Off"
289
),
290
],
291
),
292
widgets::option_buttons(
293
"Shadow Maps",
294
&[
295
(ExampleSetting::ShadowMaps(ShadowMaps::Enabled), "On"),
296
(ExampleSetting::ShadowMaps(ShadowMaps::Disabled), "Off"),
297
],
298
),
299
widgets::option_buttons(
300
"Light Rotation",
301
&[
302
(ExampleSetting::LightRotation(LightRotation::Rotating), "On"),
303
(
304
ExampleSetting::LightRotation(LightRotation::Stationary),
305
"Off"
306
),
307
],
308
),
309
widgets::option_buttons(
310
"Light Type",
311
&[
312
(
313
ExampleSetting::LightType(LightType::Directional),
314
"Directional"
315
),
316
(ExampleSetting::LightType(LightType::Point), "Point"),
317
(ExampleSetting::LightType(LightType::Spot), "Spot"),
318
],
319
),
320
widgets::option_buttons(
321
"Receive Shadows",
322
&[
323
(
324
ExampleSetting::ReceiveShadows(ReceiveShadows::Enabled),
325
"On"
326
),
327
(
328
ExampleSetting::ReceiveShadows(ReceiveShadows::Disabled),
329
"Off"
330
),
331
],
332
),
333
],
334
));
335
}
336
337
fn update_radio_buttons(
338
mut widgets: Query<
339
(
340
Entity,
341
Option<&mut BackgroundColor>,
342
Has<Text>,
343
&WidgetClickSender<ExampleSetting>,
344
),
345
Or<(With<RadioButton>, With<RadioButtonText>)>,
346
>,
347
app_status: Res<AppStatus>,
348
mut writer: TextUiWriter,
349
) {
350
for (entity, background_color, has_text, sender) in widgets.iter_mut() {
351
let selected = match **sender {
352
ExampleSetting::ContactShadows(value) => value == app_status.contact_shadows,
353
ExampleSetting::ShadowMaps(value) => value == app_status.shadow_maps,
354
ExampleSetting::LightRotation(value) => value == app_status.light_rotation,
355
ExampleSetting::LightType(value) => value == app_status.light_type,
356
ExampleSetting::ReceiveShadows(value) => value == app_status.receive_shadows,
357
};
358
359
if let Some(mut background_color) = background_color {
360
widgets::update_ui_radio_button(&mut background_color, selected);
361
}
362
if has_text {
363
widgets::update_ui_radio_button_text(entity, &mut writer, selected);
364
}
365
}
366
}
367
368
fn handle_setting_change(
369
mut lights: Query<
370
(
371
&mut Visibility,
372
Option<&mut DirectionalLight>,
373
Option<&mut PointLight>,
374
Option<&mut SpotLight>,
375
),
376
Or<(With<DirectionalLight>, With<PointLight>, With<SpotLight>)>,
377
>,
378
mut ground_plane: Query<Entity, With<GroundPlane>>,
379
mut events: MessageReader<WidgetClickEvent<ExampleSetting>>,
380
mut app_status: ResMut<AppStatus>,
381
mut commands: Commands,
382
) {
383
for event in events.read() {
384
match **event {
385
ExampleSetting::ContactShadows(value) => {
386
app_status.contact_shadows = value;
387
for (_, maybe_directional_light, maybe_point_light, maybe_spot_light) in
388
lights.iter_mut()
389
{
390
if let Some(mut directional_light) = maybe_directional_light {
391
directional_light.contact_shadows_enabled =
392
value == ContactShadowState::Enabled;
393
}
394
if let Some(mut point_light) = maybe_point_light {
395
point_light.contact_shadows_enabled = value == ContactShadowState::Enabled;
396
}
397
if let Some(mut spot_light) = maybe_spot_light {
398
spot_light.contact_shadows_enabled = value == ContactShadowState::Enabled;
399
}
400
}
401
}
402
ExampleSetting::ShadowMaps(value) => {
403
app_status.shadow_maps = value;
404
for (_, maybe_directional_light, maybe_point_light, maybe_spot_light) in
405
lights.iter_mut()
406
{
407
if let Some(mut directional_light) = maybe_directional_light {
408
directional_light.shadow_maps_enabled = value == ShadowMaps::Enabled;
409
}
410
if let Some(mut point_light) = maybe_point_light {
411
point_light.shadow_maps_enabled = value == ShadowMaps::Enabled;
412
}
413
if let Some(mut spot_light) = maybe_spot_light {
414
spot_light.shadow_maps_enabled = value == ShadowMaps::Enabled;
415
}
416
}
417
}
418
ExampleSetting::LightRotation(value) => {
419
app_status.light_rotation = value;
420
}
421
ExampleSetting::LightType(value) => {
422
app_status.light_type = value;
423
for (
424
mut visibility,
425
maybe_directional_light,
426
maybe_point_light,
427
maybe_spot_light,
428
) in lights.iter_mut()
429
{
430
let is_visible = match value {
431
LightType::Directional => maybe_directional_light.is_some(),
432
LightType::Point => maybe_point_light.is_some(),
433
LightType::Spot => maybe_spot_light.is_some(),
434
};
435
*visibility = if is_visible {
436
Visibility::Visible
437
} else {
438
Visibility::Hidden
439
};
440
}
441
}
442
ExampleSetting::ReceiveShadows(value) => {
443
app_status.receive_shadows = value;
444
for entity in ground_plane.iter_mut() {
445
match value {
446
ReceiveShadows::Enabled => {
447
commands.entity(entity).remove::<NotShadowReceiver>();
448
}
449
ReceiveShadows::Disabled => {
450
commands.entity(entity).insert(NotShadowReceiver);
451
}
452
}
453
}
454
}
455
}
456
}
457
}
458
459