Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/3d/motion_blur.rs
6592 views
1
//! Demonstrates how to enable per-object motion blur. This rendering feature can be configured per
2
//! camera using the [`MotionBlur`] component.z
3
4
use bevy::{
5
image::{ImageAddressMode, ImageFilterMode, ImageSampler, ImageSamplerDescriptor},
6
math::ops,
7
post_process::motion_blur::MotionBlur,
8
prelude::*,
9
};
10
11
fn main() {
12
let mut app = App::new();
13
14
app.add_plugins(DefaultPlugins)
15
.add_systems(Startup, (setup_camera, setup_scene, setup_ui))
16
.add_systems(Update, (keyboard_inputs, move_cars, move_camera).chain())
17
.run();
18
}
19
20
fn setup_camera(mut commands: Commands) {
21
commands.spawn((
22
Camera3d::default(),
23
// Add the `MotionBlur` component to a camera to enable motion blur.
24
// Motion blur requires the depth and motion vector prepass, which this bundle adds.
25
// Configure the amount and quality of motion blur per-camera using this component.
26
MotionBlur {
27
shutter_angle: 1.0,
28
samples: 2,
29
},
30
// MSAA and Motion Blur together are not compatible on WebGL
31
#[cfg(all(feature = "webgl2", target_arch = "wasm32", not(feature = "webgpu")))]
32
Msaa::Off,
33
));
34
}
35
36
// Everything past this point is used to build the example, but isn't required to use motion blur.
37
38
#[derive(Resource)]
39
enum CameraMode {
40
Track,
41
Chase,
42
}
43
44
#[derive(Component)]
45
struct Moves(f32);
46
47
#[derive(Component)]
48
struct CameraTracked;
49
50
#[derive(Component)]
51
struct Rotates;
52
53
fn setup_scene(
54
asset_server: Res<AssetServer>,
55
mut images: ResMut<Assets<Image>>,
56
mut commands: Commands,
57
mut meshes: ResMut<Assets<Mesh>>,
58
mut materials: ResMut<Assets<StandardMaterial>>,
59
) {
60
commands.insert_resource(AmbientLight {
61
color: Color::WHITE,
62
brightness: 300.0,
63
..default()
64
});
65
commands.insert_resource(CameraMode::Chase);
66
commands.spawn((
67
DirectionalLight {
68
illuminance: 3_000.0,
69
shadows_enabled: true,
70
..default()
71
},
72
Transform::default().looking_to(Vec3::new(-1.0, -0.7, -1.0), Vec3::X),
73
));
74
// Sky
75
commands.spawn((
76
Mesh3d(meshes.add(Sphere::default())),
77
MeshMaterial3d(materials.add(StandardMaterial {
78
unlit: true,
79
base_color: Color::linear_rgb(0.1, 0.6, 1.0),
80
..default()
81
})),
82
Transform::default().with_scale(Vec3::splat(-4000.0)),
83
));
84
// Ground
85
let mut plane: Mesh = Plane3d::default().into();
86
let uv_size = 4000.0;
87
let uvs = vec![[uv_size, 0.0], [0.0, 0.0], [0.0, uv_size], [uv_size; 2]];
88
plane.insert_attribute(Mesh::ATTRIBUTE_UV_0, uvs);
89
commands.spawn((
90
Mesh3d(meshes.add(plane)),
91
MeshMaterial3d(materials.add(StandardMaterial {
92
base_color: Color::WHITE,
93
perceptual_roughness: 1.0,
94
base_color_texture: Some(images.add(uv_debug_texture())),
95
..default()
96
})),
97
Transform::from_xyz(0.0, -0.65, 0.0).with_scale(Vec3::splat(80.)),
98
));
99
100
spawn_cars(&asset_server, &mut meshes, &mut materials, &mut commands);
101
spawn_trees(&mut meshes, &mut materials, &mut commands);
102
spawn_barriers(&mut meshes, &mut materials, &mut commands);
103
}
104
105
fn spawn_cars(
106
asset_server: &AssetServer,
107
meshes: &mut Assets<Mesh>,
108
materials: &mut Assets<StandardMaterial>,
109
commands: &mut Commands,
110
) {
111
const N_CARS: usize = 20;
112
let box_mesh = meshes.add(Cuboid::new(0.3, 0.15, 0.55));
113
let cylinder = meshes.add(Cylinder::default());
114
let logo = asset_server.load("branding/icon.png");
115
let wheel_matl = materials.add(StandardMaterial {
116
base_color: Color::WHITE,
117
base_color_texture: Some(logo.clone()),
118
..default()
119
});
120
121
let mut matl = |color| {
122
materials.add(StandardMaterial {
123
base_color: color,
124
..default()
125
})
126
};
127
128
let colors = [
129
matl(Color::linear_rgb(1.0, 0.0, 0.0)),
130
matl(Color::linear_rgb(1.0, 1.0, 0.0)),
131
matl(Color::BLACK),
132
matl(Color::linear_rgb(0.0, 0.0, 1.0)),
133
matl(Color::linear_rgb(0.0, 1.0, 0.0)),
134
matl(Color::linear_rgb(1.0, 0.0, 1.0)),
135
matl(Color::linear_rgb(0.5, 0.5, 0.0)),
136
matl(Color::linear_rgb(1.0, 0.5, 0.0)),
137
];
138
139
let make_wheel = |x: f32, z: f32| {
140
(
141
Mesh3d(cylinder.clone()),
142
MeshMaterial3d(wheel_matl.clone()),
143
Transform::from_xyz(0.14 * x, -0.045, 0.15 * z)
144
.with_scale(Vec3::new(0.15, 0.04, 0.15))
145
.with_rotation(Quat::from_rotation_z(std::f32::consts::FRAC_PI_2)),
146
Rotates,
147
)
148
};
149
150
for i in 0..N_CARS {
151
let color = colors[i % colors.len()].clone();
152
commands
153
.spawn((
154
Mesh3d(box_mesh.clone()),
155
MeshMaterial3d(color.clone()),
156
Transform::from_scale(Vec3::splat(0.5)),
157
Moves(i as f32 * 2.0),
158
children![
159
(
160
Mesh3d(box_mesh.clone()),
161
MeshMaterial3d(color),
162
Transform::from_xyz(0.0, 0.08, 0.03).with_scale(Vec3::new(1.0, 1.0, 0.5)),
163
),
164
make_wheel(1.0, 1.0),
165
make_wheel(1.0, -1.0),
166
make_wheel(-1.0, 1.0),
167
make_wheel(-1.0, -1.0)
168
],
169
))
170
.insert_if(CameraTracked, || i == 0);
171
}
172
}
173
174
fn spawn_barriers(
175
meshes: &mut Assets<Mesh>,
176
materials: &mut Assets<StandardMaterial>,
177
commands: &mut Commands,
178
) {
179
const N_CONES: usize = 100;
180
let capsule = meshes.add(Capsule3d::default());
181
let matl = materials.add(StandardMaterial {
182
base_color: Color::srgb_u8(255, 87, 51),
183
reflectance: 1.0,
184
..default()
185
});
186
let mut spawn_with_offset = |offset: f32| {
187
for i in 0..N_CONES {
188
let pos = race_track_pos(
189
offset,
190
(i as f32) / (N_CONES as f32) * std::f32::consts::PI * 2.0,
191
);
192
commands.spawn((
193
Mesh3d(capsule.clone()),
194
MeshMaterial3d(matl.clone()),
195
Transform::from_xyz(pos.x, -0.65, pos.y).with_scale(Vec3::splat(0.07)),
196
));
197
}
198
};
199
spawn_with_offset(0.04);
200
spawn_with_offset(-0.04);
201
}
202
203
fn spawn_trees(
204
meshes: &mut Assets<Mesh>,
205
materials: &mut Assets<StandardMaterial>,
206
commands: &mut Commands,
207
) {
208
const N_TREES: usize = 30;
209
let capsule = meshes.add(Capsule3d::default());
210
let sphere = meshes.add(Sphere::default());
211
let leaves = materials.add(Color::linear_rgb(0.0, 1.0, 0.0));
212
let trunk = materials.add(Color::linear_rgb(0.4, 0.2, 0.2));
213
214
let mut spawn_with_offset = |offset: f32| {
215
for i in 0..N_TREES {
216
let pos = race_track_pos(
217
offset,
218
(i as f32) / (N_TREES as f32) * std::f32::consts::PI * 2.0,
219
);
220
let [x, z] = pos.into();
221
commands.spawn((
222
Mesh3d(sphere.clone()),
223
MeshMaterial3d(leaves.clone()),
224
Transform::from_xyz(x, -0.3, z).with_scale(Vec3::splat(0.3)),
225
));
226
commands.spawn((
227
Mesh3d(capsule.clone()),
228
MeshMaterial3d(trunk.clone()),
229
Transform::from_xyz(x, -0.5, z).with_scale(Vec3::new(0.05, 0.3, 0.05)),
230
));
231
}
232
};
233
spawn_with_offset(0.07);
234
spawn_with_offset(-0.07);
235
}
236
237
fn setup_ui(mut commands: Commands) {
238
commands.spawn((
239
Text::default(),
240
Node {
241
position_type: PositionType::Absolute,
242
top: px(12),
243
left: px(12),
244
..default()
245
},
246
children![
247
TextSpan::default(),
248
TextSpan::default(),
249
TextSpan::new("1/2: -/+ shutter angle (blur amount)\n"),
250
TextSpan::new("3/4: -/+ sample count (blur quality)\n"),
251
TextSpan::new("Spacebar: cycle camera\n"),
252
],
253
));
254
}
255
256
fn keyboard_inputs(
257
mut motion_blur: Single<&mut MotionBlur>,
258
presses: Res<ButtonInput<KeyCode>>,
259
text: Single<Entity, With<Text>>,
260
mut writer: TextUiWriter,
261
mut camera: ResMut<CameraMode>,
262
) {
263
if presses.just_pressed(KeyCode::Digit1) {
264
motion_blur.shutter_angle -= 0.25;
265
} else if presses.just_pressed(KeyCode::Digit2) {
266
motion_blur.shutter_angle += 0.25;
267
} else if presses.just_pressed(KeyCode::Digit3) {
268
motion_blur.samples = motion_blur.samples.saturating_sub(1);
269
} else if presses.just_pressed(KeyCode::Digit4) {
270
motion_blur.samples += 1;
271
} else if presses.just_pressed(KeyCode::Space) {
272
*camera = match *camera {
273
CameraMode::Track => CameraMode::Chase,
274
CameraMode::Chase => CameraMode::Track,
275
};
276
}
277
motion_blur.shutter_angle = motion_blur.shutter_angle.clamp(0.0, 1.0);
278
motion_blur.samples = motion_blur.samples.clamp(0, 64);
279
let entity = *text;
280
*writer.text(entity, 1) = format!("Shutter angle: {:.2}\n", motion_blur.shutter_angle);
281
*writer.text(entity, 2) = format!("Samples: {:.5}\n", motion_blur.samples);
282
}
283
284
/// Parametric function for a looping race track. `offset` will return the point offset
285
/// perpendicular to the track at the given point.
286
fn race_track_pos(offset: f32, t: f32) -> Vec2 {
287
let x_tweak = 2.0;
288
let y_tweak = 3.0;
289
let scale = 8.0;
290
let x0 = ops::sin(x_tweak * t);
291
let y0 = ops::cos(y_tweak * t);
292
let dx = x_tweak * ops::cos(x_tweak * t);
293
let dy = y_tweak * -ops::sin(y_tweak * t);
294
let dl = ops::hypot(dx, dy);
295
let x = x0 + offset * dy / dl;
296
let y = y0 - offset * dx / dl;
297
Vec2::new(x, y) * scale
298
}
299
300
fn move_cars(
301
time: Res<Time>,
302
mut movables: Query<(&mut Transform, &Moves, &Children)>,
303
mut spins: Query<&mut Transform, (Without<Moves>, With<Rotates>)>,
304
) {
305
for (mut transform, moves, children) in &mut movables {
306
let time = time.elapsed_secs() * 0.25;
307
let t = time + 0.5 * moves.0;
308
let dx = ops::cos(t);
309
let dz = -ops::sin(3.0 * t);
310
let speed_variation = (dx * dx + dz * dz).sqrt() * 0.15;
311
let t = t + speed_variation;
312
let prev = transform.translation;
313
transform.translation.x = race_track_pos(0.0, t).x;
314
transform.translation.z = race_track_pos(0.0, t).y;
315
transform.translation.y = -0.59;
316
let delta = transform.translation - prev;
317
transform.look_to(delta, Vec3::Y);
318
for child in children.iter() {
319
let Ok(mut wheel) = spins.get_mut(child) else {
320
continue;
321
};
322
let radius = wheel.scale.x;
323
let circumference = 2.0 * std::f32::consts::PI * radius;
324
let angle = delta.length() / circumference * std::f32::consts::PI * 2.0;
325
wheel.rotate_local_y(angle);
326
}
327
}
328
}
329
330
fn move_camera(
331
camera: Single<(&mut Transform, &mut Projection), Without<CameraTracked>>,
332
tracked: Single<&Transform, With<CameraTracked>>,
333
mode: Res<CameraMode>,
334
) {
335
let (mut transform, mut projection) = camera.into_inner();
336
match *mode {
337
CameraMode::Track => {
338
transform.look_at(tracked.translation, Vec3::Y);
339
transform.translation = Vec3::new(15.0, -0.5, 0.0);
340
if let Projection::Perspective(perspective) = &mut *projection {
341
perspective.fov = 0.05;
342
}
343
}
344
CameraMode::Chase => {
345
transform.translation =
346
tracked.translation + Vec3::new(0.0, 0.15, 0.0) + tracked.back() * 0.6;
347
transform.look_to(tracked.forward(), Vec3::Y);
348
if let Projection::Perspective(perspective) = &mut *projection {
349
perspective.fov = 1.0;
350
}
351
}
352
}
353
}
354
355
fn uv_debug_texture() -> Image {
356
use bevy::{asset::RenderAssetUsages, render::render_resource::*};
357
const TEXTURE_SIZE: usize = 7;
358
359
let mut palette = [
360
164, 164, 164, 255, 168, 168, 168, 255, 153, 153, 153, 255, 139, 139, 139, 255, 153, 153,
361
153, 255, 177, 177, 177, 255, 159, 159, 159, 255,
362
];
363
364
let mut texture_data = [0; TEXTURE_SIZE * TEXTURE_SIZE * 4];
365
for y in 0..TEXTURE_SIZE {
366
let offset = TEXTURE_SIZE * y * 4;
367
texture_data[offset..(offset + TEXTURE_SIZE * 4)].copy_from_slice(&palette);
368
palette.rotate_right(12);
369
}
370
371
let mut img = Image::new_fill(
372
Extent3d {
373
width: TEXTURE_SIZE as u32,
374
height: TEXTURE_SIZE as u32,
375
depth_or_array_layers: 1,
376
},
377
TextureDimension::D2,
378
&texture_data,
379
TextureFormat::Rgba8UnormSrgb,
380
RenderAssetUsages::RENDER_WORLD,
381
);
382
img.sampler = ImageSampler::Descriptor(ImageSamplerDescriptor {
383
address_mode_u: ImageAddressMode::Repeat,
384
address_mode_v: ImageAddressMode::MirrorRepeat,
385
mag_filter: ImageFilterMode::Nearest,
386
..ImageSamplerDescriptor::linear()
387
});
388
img
389
}
390
391