Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_post_process/src/motion_blur/mod.rs
9395 views
1
//! Per-object, per-pixel motion blur.
2
//!
3
//! Add the [`MotionBlur`] component to a camera to enable motion blur.
4
5
use crate::{
6
bloom::bloom,
7
motion_blur::pipeline::{MotionBlurPipeline, MotionBlurPipelineId},
8
};
9
use bevy_app::{App, Plugin};
10
use bevy_asset::embedded_asset;
11
use bevy_camera::Camera;
12
use bevy_core_pipeline::{
13
prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},
14
schedule::{Core3d, Core3dSystems},
15
};
16
use bevy_ecs::{
17
component::Component,
18
query::{QueryItem, With},
19
reflect::ReflectComponent,
20
schedule::IntoScheduleConfigs,
21
system::Res,
22
};
23
use bevy_reflect::{std_traits::ReflectDefault, Reflect};
24
use bevy_render::{
25
diagnostic::RecordDiagnostics,
26
extract_component::{
27
ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
28
},
29
globals::GlobalsBuffer,
30
render_resource::{
31
BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment,
32
RenderPassDescriptor, ShaderType, SpecializedRenderPipelines,
33
},
34
renderer::{RenderContext, ViewQuery},
35
sync_component::SyncComponent,
36
view::{Msaa, ViewTarget},
37
Render, RenderApp, RenderStartup, RenderSystems,
38
};
39
40
pub mod pipeline;
41
42
/// A component that enables and configures motion blur when added to a camera.
43
///
44
/// Motion blur is an effect that simulates how moving objects blur as they change position during
45
/// the exposure of film, a sensor, or an eyeball.
46
///
47
/// Because rendering simulates discrete steps in time, we use per-pixel motion vectors to estimate
48
/// the path of objects between frames. This kind of implementation has some artifacts:
49
/// - Fast moving objects in front of a stationary object or when in front of empty space, will not
50
/// have their edges blurred.
51
/// - Transparent objects do not write to depth or motion vectors, so they cannot be blurred.
52
///
53
/// Other approaches, such as *A Reconstruction Filter for Plausible Motion Blur* produce more
54
/// correct results, but are more expensive and complex, and have other kinds of artifacts. This
55
/// implementation is relatively inexpensive and effective.
56
///
57
/// # Usage
58
///
59
/// Add the [`MotionBlur`] component to a camera to enable and configure motion blur for that
60
/// camera.
61
///
62
/// ```
63
/// # use bevy_post_process::motion_blur::MotionBlur;
64
/// # use bevy_camera::Camera3d;
65
/// # use bevy_ecs::prelude::*;
66
/// # fn test(mut commands: Commands) {
67
/// commands.spawn((
68
/// Camera3d::default(),
69
/// MotionBlur::default(),
70
/// ));
71
/// # }
72
/// ````
73
#[derive(Reflect, Component, Clone)]
74
#[reflect(Component, Default, Clone)]
75
#[require(DepthPrepass, MotionVectorPrepass)]
76
pub struct MotionBlur {
77
/// The strength of motion blur from `0.0` to `1.0`.
78
///
79
/// The shutter angle describes the fraction of a frame that a camera's shutter is open and
80
/// exposing the film/sensor. For 24fps cinematic film, a shutter angle of 0.5 (180 degrees) is
81
/// common. This means that the shutter was open for half of the frame, or 1/48th of a second.
82
/// The lower the shutter angle, the less exposure time and thus less blur.
83
///
84
/// A value greater than one is non-physical and results in an object's blur stretching further
85
/// than it traveled in that frame. This might be a desirable effect for artistic reasons, but
86
/// consider allowing users to opt out of this.
87
///
88
/// This value is intentionally tied to framerate to avoid the aforementioned non-physical
89
/// over-blurring. If you want to emulate a cinematic look, your options are:
90
/// - Framelimit your app to 24fps, and set the shutter angle to 0.5 (180 deg). Note that
91
/// depending on artistic intent or the action of a scene, it is common to set the shutter
92
/// angle between 0.125 (45 deg) and 0.5 (180 deg). This is the most faithful way to
93
/// reproduce the look of film.
94
/// - Set the shutter angle greater than one. For example, to emulate the blur strength of
95
/// film while rendering at 60fps, you would set the shutter angle to `60/24 * 0.5 = 1.25`.
96
/// Note that this will result in artifacts where the motion of objects will stretch further
97
/// than they moved between frames; users may find this distracting.
98
pub shutter_angle: f32,
99
/// The quality of motion blur, corresponding to the number of per-pixel samples taken in each
100
/// direction during blur.
101
///
102
/// Setting this to `1` results in each pixel being sampled once in the leading direction, once
103
/// in the trailing direction, and once in the middle, for a total of 3 samples (`1 * 2 + 1`).
104
/// Setting this to `3` will result in `3 * 2 + 1 = 7` samples. Setting this to `0` is
105
/// equivalent to disabling motion blur.
106
pub samples: u32,
107
}
108
109
impl Default for MotionBlur {
110
fn default() -> Self {
111
Self {
112
shutter_angle: 0.5,
113
samples: 1,
114
}
115
}
116
}
117
118
impl SyncComponent for MotionBlur {
119
type Out = MotionBlurUniform;
120
}
121
122
impl ExtractComponent for MotionBlur {
123
type QueryData = &'static Self;
124
type QueryFilter = With<Camera>;
125
126
fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
127
Some(MotionBlurUniform {
128
shutter_angle: item.shutter_angle,
129
samples: item.samples,
130
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
131
_webgl2_padding: Default::default(),
132
})
133
}
134
}
135
136
#[doc(hidden)]
137
#[derive(Component, ShaderType, Clone)]
138
pub struct MotionBlurUniform {
139
shutter_angle: f32,
140
samples: u32,
141
#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
142
// WebGL2 structs must be 16 byte aligned.
143
_webgl2_padding: bevy_math::Vec2,
144
}
145
146
/// Adds support for per-object motion blur to the app. See [`MotionBlur`] for details.
147
#[derive(Default)]
148
pub struct MotionBlurPlugin;
149
impl Plugin for MotionBlurPlugin {
150
fn build(&self, app: &mut App) {
151
embedded_asset!(app, "motion_blur.wgsl");
152
153
app.add_plugins((
154
ExtractComponentPlugin::<MotionBlur>::default(),
155
UniformComponentPlugin::<MotionBlurUniform>::default(),
156
));
157
158
let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
159
return;
160
};
161
162
render_app
163
.init_resource::<SpecializedRenderPipelines<MotionBlurPipeline>>()
164
.add_systems(RenderStartup, pipeline::init_motion_blur_pipeline)
165
.add_systems(
166
Render,
167
pipeline::prepare_motion_blur_pipelines.in_set(RenderSystems::Prepare),
168
);
169
170
render_app.add_systems(
171
Core3d,
172
motion_blur.before(bloom).in_set(Core3dSystems::PostProcess),
173
);
174
}
175
}
176
177
pub fn motion_blur(
178
view: ViewQuery<(
179
&ViewTarget,
180
&MotionBlurPipelineId,
181
&ViewPrepassTextures,
182
&MotionBlurUniform,
183
&Msaa,
184
)>,
185
motion_blur_pipeline: Res<MotionBlurPipeline>,
186
pipeline_cache: Res<PipelineCache>,
187
settings_uniforms: Res<ComponentUniforms<MotionBlurUniform>>,
188
globals_buffer: Res<GlobalsBuffer>,
189
mut ctx: RenderContext,
190
) {
191
let (view_target, pipeline_id, prepass_textures, motion_blur_uniform, msaa) = view.into_inner();
192
193
if motion_blur_uniform.samples == 0 || motion_blur_uniform.shutter_angle <= 0.0 {
194
return; // We can skip running motion blur in these cases.
195
}
196
197
let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
198
return;
199
};
200
201
let Some(settings_binding) = settings_uniforms.uniforms().binding() else {
202
return;
203
};
204
let (Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) =
205
(&prepass_textures.motion_vectors, &prepass_textures.depth)
206
else {
207
return;
208
};
209
let Some(globals_uniforms) = globals_buffer.buffer.binding() else {
210
return;
211
};
212
213
let post_process = view_target.post_process_write();
214
215
let layout = if msaa.samples() == 1 {
216
&motion_blur_pipeline.layout
217
} else {
218
&motion_blur_pipeline.layout_msaa
219
};
220
221
let bind_group = ctx.render_device().create_bind_group(
222
Some("motion_blur_bind_group"),
223
&pipeline_cache.get_bind_group_layout(layout),
224
&BindGroupEntries::sequential((
225
post_process.source,
226
&prepass_motion_vectors_texture.texture.default_view,
227
&prepass_depth_texture.texture.default_view,
228
&motion_blur_pipeline.sampler,
229
settings_binding.clone(),
230
globals_uniforms.clone(),
231
)),
232
);
233
234
let diagnostics = ctx.diagnostic_recorder();
235
let diagnostics = diagnostics.as_deref();
236
237
let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
238
label: Some("motion_blur"),
239
color_attachments: &[Some(RenderPassColorAttachment {
240
view: post_process.destination,
241
depth_slice: None,
242
resolve_target: None,
243
ops: Operations::default(),
244
})],
245
depth_stencil_attachment: None,
246
timestamp_writes: None,
247
occlusion_query_set: None,
248
multiview_mask: None,
249
});
250
let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur");
251
252
render_pass.set_render_pipeline(pipeline);
253
render_pass.set_bind_group(0, &bind_group, &[]);
254
render_pass.draw(0..3, 0..1);
255
256
pass_span.end(&mut render_pass);
257
}
258
259