Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/lighting.rs
6592 views
1
//! Illustrates different lights of various types and colors, some static, some moving over
2
//! a simple scene.
3
4
use std::f32::consts::PI;
5
6
use bevy::{
7
camera::{Exposure, PhysicalCameraParameters},
8
color::palettes::css::*,
9
light::CascadeShadowConfigBuilder,
10
prelude::*,
11
};
12
13
fn main() {
14
App::new()
15
.add_plugins(DefaultPlugins)
16
.insert_resource(Parameters(PhysicalCameraParameters {
17
aperture_f_stops: 1.0,
18
shutter_speed_s: 1.0 / 125.0,
19
sensitivity_iso: 100.0,
20
sensor_height: 0.01866,
21
}))
22
.add_systems(Startup, setup)
23
.add_systems(
24
Update,
25
(
26
update_exposure,
27
toggle_ambient_light,
28
movement,
29
animate_light_direction,
30
),
31
)
32
.run();
33
}
34
35
#[derive(Resource, Default, Deref, DerefMut)]
36
struct Parameters(PhysicalCameraParameters);
37
38
#[derive(Component)]
39
struct Movable;
40
41
/// set up a simple 3D scene
42
fn setup(
43
parameters: Res<Parameters>,
44
mut commands: Commands,
45
mut meshes: ResMut<Assets<Mesh>>,
46
mut materials: ResMut<Assets<StandardMaterial>>,
47
asset_server: Res<AssetServer>,
48
) {
49
// ground plane
50
commands.spawn((
51
Mesh3d(meshes.add(Plane3d::default().mesh().size(10.0, 10.0))),
52
MeshMaterial3d(materials.add(StandardMaterial {
53
base_color: Color::WHITE,
54
perceptual_roughness: 1.0,
55
..default()
56
})),
57
));
58
59
// left wall
60
let mut transform = Transform::from_xyz(2.5, 2.5, 0.0);
61
transform.rotate_z(PI / 2.);
62
commands.spawn((
63
Mesh3d(meshes.add(Cuboid::new(5.0, 0.15, 5.0))),
64
MeshMaterial3d(materials.add(StandardMaterial {
65
base_color: INDIGO.into(),
66
perceptual_roughness: 1.0,
67
..default()
68
})),
69
transform,
70
));
71
// back (right) wall
72
let mut transform = Transform::from_xyz(0.0, 2.5, -2.5);
73
transform.rotate_x(PI / 2.);
74
commands.spawn((
75
Mesh3d(meshes.add(Cuboid::new(5.0, 0.15, 5.0))),
76
MeshMaterial3d(materials.add(StandardMaterial {
77
base_color: INDIGO.into(),
78
perceptual_roughness: 1.0,
79
..default()
80
})),
81
transform,
82
));
83
84
// Bevy logo to demonstrate alpha mask shadows
85
let mut transform = Transform::from_xyz(-2.2, 0.5, 1.0);
86
transform.rotate_y(PI / 8.);
87
commands.spawn((
88
Mesh3d(meshes.add(Rectangle::new(2.0, 0.5))),
89
MeshMaterial3d(materials.add(StandardMaterial {
90
base_color_texture: Some(asset_server.load("branding/bevy_logo_light.png")),
91
perceptual_roughness: 1.0,
92
alpha_mode: AlphaMode::Mask(0.5),
93
cull_mode: None,
94
..default()
95
})),
96
transform,
97
Movable,
98
));
99
100
// cube
101
commands.spawn((
102
Mesh3d(meshes.add(Cuboid::default())),
103
MeshMaterial3d(materials.add(StandardMaterial {
104
base_color: DEEP_PINK.into(),
105
..default()
106
})),
107
Transform::from_xyz(0.0, 0.5, 0.0),
108
Movable,
109
));
110
// sphere
111
commands.spawn((
112
Mesh3d(meshes.add(Sphere::new(0.5).mesh().uv(32, 18))),
113
MeshMaterial3d(materials.add(StandardMaterial {
114
base_color: LIMEGREEN.into(),
115
..default()
116
})),
117
Transform::from_xyz(1.5, 1.0, 1.5),
118
Movable,
119
));
120
121
// ambient light
122
// ambient lights' brightnesses are measured in candela per meter square, calculable as (color * brightness)
123
commands.insert_resource(AmbientLight {
124
color: ORANGE_RED.into(),
125
brightness: 200.0,
126
..default()
127
});
128
129
// red point light
130
commands.spawn((
131
PointLight {
132
intensity: 100_000.0,
133
color: RED.into(),
134
shadows_enabled: true,
135
..default()
136
},
137
Transform::from_xyz(1.0, 2.0, 0.0),
138
children![(
139
Mesh3d(meshes.add(Sphere::new(0.1).mesh().uv(32, 18))),
140
MeshMaterial3d(materials.add(StandardMaterial {
141
base_color: RED.into(),
142
emissive: LinearRgba::new(4.0, 0.0, 0.0, 0.0),
143
..default()
144
})),
145
)],
146
));
147
148
// green spot light
149
commands.spawn((
150
SpotLight {
151
intensity: 100_000.0,
152
color: LIME.into(),
153
shadows_enabled: true,
154
inner_angle: 0.6,
155
outer_angle: 0.8,
156
..default()
157
},
158
Transform::from_xyz(-1.0, 2.0, 0.0).looking_at(Vec3::new(-1.0, 0.0, 0.0), Vec3::Z),
159
children![(
160
Mesh3d(meshes.add(Capsule3d::new(0.1, 0.125))),
161
MeshMaterial3d(materials.add(StandardMaterial {
162
base_color: LIME.into(),
163
emissive: LinearRgba::new(0.0, 4.0, 0.0, 0.0),
164
..default()
165
})),
166
Transform::from_rotation(Quat::from_rotation_x(PI / 2.0)),
167
)],
168
));
169
170
// blue point light
171
commands.spawn((
172
PointLight {
173
intensity: 100_000.0,
174
color: BLUE.into(),
175
shadows_enabled: true,
176
..default()
177
},
178
Transform::from_xyz(0.0, 4.0, 0.0),
179
children![(
180
Mesh3d(meshes.add(Sphere::new(0.1).mesh().uv(32, 18))),
181
MeshMaterial3d(materials.add(StandardMaterial {
182
base_color: BLUE.into(),
183
emissive: LinearRgba::new(0.0, 0.0, 713.0, 0.0),
184
..default()
185
})),
186
)],
187
));
188
189
// directional 'sun' light
190
commands.spawn((
191
DirectionalLight {
192
illuminance: light_consts::lux::OVERCAST_DAY,
193
shadows_enabled: true,
194
..default()
195
},
196
Transform {
197
translation: Vec3::new(0.0, 2.0, 0.0),
198
rotation: Quat::from_rotation_x(-PI / 4.),
199
..default()
200
},
201
// The default cascade config is designed to handle large scenes.
202
// As this example has a much smaller world, we can tighten the shadow
203
// bounds for better visual quality.
204
CascadeShadowConfigBuilder {
205
first_cascade_far_bound: 4.0,
206
maximum_distance: 10.0,
207
..default()
208
}
209
.build(),
210
));
211
212
// example instructions
213
214
commands.spawn((
215
Text::default(),
216
Node {
217
position_type: PositionType::Absolute,
218
top: px(12),
219
left: px(12),
220
..default()
221
},
222
children![
223
TextSpan::new("Ambient light is on\n"),
224
TextSpan(format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops,)),
225
TextSpan(format!(
226
"Shutter speed: 1/{:.0}s\n",
227
1.0 / parameters.shutter_speed_s
228
)),
229
TextSpan(format!(
230
"Sensitivity: ISO {:.0}\n",
231
parameters.sensitivity_iso
232
)),
233
TextSpan::new("\n\n"),
234
TextSpan::new("Controls\n"),
235
TextSpan::new("---------------\n"),
236
TextSpan::new("Arrow keys - Move objects\n"),
237
TextSpan::new("Space - Toggle ambient light\n"),
238
TextSpan::new("1/2 - Decrease/Increase aperture\n"),
239
TextSpan::new("3/4 - Decrease/Increase shutter speed\n"),
240
TextSpan::new("5/6 - Decrease/Increase sensitivity\n"),
241
TextSpan::new("R - Reset exposure"),
242
],
243
));
244
245
// camera
246
commands.spawn((
247
Camera3d::default(),
248
Transform::from_xyz(-2.0, 2.5, 5.0).looking_at(Vec3::ZERO, Vec3::Y),
249
Exposure::from_physical_camera(**parameters),
250
));
251
}
252
253
fn update_exposure(
254
key_input: Res<ButtonInput<KeyCode>>,
255
mut parameters: ResMut<Parameters>,
256
mut exposure: Single<&mut Exposure>,
257
text: Single<Entity, With<Text>>,
258
mut writer: TextUiWriter,
259
) {
260
// TODO: Clamp values to a reasonable range
261
let entity = *text;
262
if key_input.just_pressed(KeyCode::Digit2) {
263
parameters.aperture_f_stops *= 2.0;
264
} else if key_input.just_pressed(KeyCode::Digit1) {
265
parameters.aperture_f_stops *= 0.5;
266
}
267
if key_input.just_pressed(KeyCode::Digit4) {
268
parameters.shutter_speed_s *= 2.0;
269
} else if key_input.just_pressed(KeyCode::Digit3) {
270
parameters.shutter_speed_s *= 0.5;
271
}
272
if key_input.just_pressed(KeyCode::Digit6) {
273
parameters.sensitivity_iso += 100.0;
274
} else if key_input.just_pressed(KeyCode::Digit5) {
275
parameters.sensitivity_iso -= 100.0;
276
}
277
if key_input.just_pressed(KeyCode::KeyR) {
278
*parameters = Parameters::default();
279
}
280
281
*writer.text(entity, 2) = format!("Aperture: f/{:.0}\n", parameters.aperture_f_stops);
282
*writer.text(entity, 3) = format!(
283
"Shutter speed: 1/{:.0}s\n",
284
1.0 / parameters.shutter_speed_s
285
);
286
*writer.text(entity, 4) = format!("Sensitivity: ISO {:.0}\n", parameters.sensitivity_iso);
287
288
**exposure = Exposure::from_physical_camera(**parameters);
289
}
290
291
fn toggle_ambient_light(
292
key_input: Res<ButtonInput<KeyCode>>,
293
mut ambient_light: ResMut<AmbientLight>,
294
text: Single<Entity, With<Text>>,
295
mut writer: TextUiWriter,
296
) {
297
if key_input.just_pressed(KeyCode::Space) {
298
if ambient_light.brightness > 1. {
299
ambient_light.brightness = 0.;
300
} else {
301
ambient_light.brightness = 200.;
302
}
303
304
let entity = *text;
305
let ambient_light_state_text: &str = match ambient_light.brightness {
306
0. => "off",
307
_ => "on",
308
};
309
*writer.text(entity, 1) = format!("Ambient light is {ambient_light_state_text}\n");
310
}
311
}
312
313
fn animate_light_direction(
314
time: Res<Time>,
315
mut query: Query<&mut Transform, With<DirectionalLight>>,
316
) {
317
for mut transform in &mut query {
318
transform.rotate_y(time.delta_secs() * 0.5);
319
}
320
}
321
322
fn movement(
323
input: Res<ButtonInput<KeyCode>>,
324
time: Res<Time>,
325
mut query: Query<&mut Transform, With<Movable>>,
326
) {
327
for mut transform in &mut query {
328
let mut direction = Vec3::ZERO;
329
if input.pressed(KeyCode::ArrowUp) {
330
direction.y += 1.0;
331
}
332
if input.pressed(KeyCode::ArrowDown) {
333
direction.y -= 1.0;
334
}
335
if input.pressed(KeyCode::ArrowLeft) {
336
direction.x -= 1.0;
337
}
338
if input.pressed(KeyCode::ArrowRight) {
339
direction.x += 1.0;
340
}
341
342
transform.translation += time.delta_secs() * 2.0 * direction;
343
}
344
}
345
346