bevy_core_pipeline/motion_blur/
mod.rs

1//! Per-object, per-pixel motion blur.
2//!
3//! Add the [`MotionBlur`] component to a camera to enable motion blur.
4
5#![expect(deprecated)]
6
7use crate::{
8    core_3d::graph::{Core3d, Node3d},
9    prepass::{DepthPrepass, MotionVectorPrepass},
10};
11use bevy_app::{App, Plugin};
12use bevy_asset::{load_internal_asset, Handle};
13use bevy_ecs::{
14    bundle::Bundle, component::Component, query::With, reflect::ReflectComponent,
15    schedule::IntoSystemConfigs,
16};
17use bevy_reflect::{std_traits::ReflectDefault, Reflect};
18use bevy_render::{
19    camera::Camera,
20    extract_component::{ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin},
21    render_graph::{RenderGraphApp, ViewNodeRunner},
22    render_resource::{Shader, ShaderType, SpecializedRenderPipelines},
23    Render, RenderApp, RenderSet,
24};
25
26pub mod node;
27pub mod pipeline;
28
29/// Adds [`MotionBlur`] and the required depth and motion vector prepasses to a camera entity.
30#[derive(Bundle, Default)]
31#[deprecated(
32    since = "0.15.0",
33    note = "Use the `MotionBlur` component instead. Inserting it will now also insert the other components required by it automatically."
34)]
35pub struct MotionBlurBundle {
36    pub motion_blur: MotionBlur,
37    pub depth_prepass: DepthPrepass,
38    pub motion_vector_prepass: MotionVectorPrepass,
39}
40
41/// A component that enables and configures motion blur when added to a camera.
42///
43/// Motion blur is an effect that simulates how moving objects blur as they change position during
44/// the exposure of film, a sensor, or an eyeball.
45///
46/// Because rendering simulates discrete steps in time, we use per-pixel motion vectors to estimate
47/// the path of objects between frames. This kind of implementation has some artifacts:
48/// - Fast moving objects in front of a stationary object or when in front of empty space, will not
49///   have their edges blurred.
50/// - Transparent objects do not write to depth or motion vectors, so they cannot be blurred.
51///
52/// Other approaches, such as *A Reconstruction Filter for Plausible Motion Blur* produce more
53/// correct results, but are more expensive and complex, and have other kinds of artifacts. This
54/// implementation is relatively inexpensive and effective.
55///
56/// # Usage
57///
58/// Add the [`MotionBlur`] component to a camera to enable and configure motion blur for that
59/// camera.
60///
61/// ```
62/// # use bevy_core_pipeline::{core_3d::Camera3d, motion_blur::MotionBlur};
63/// # use bevy_ecs::prelude::*;
64/// # fn test(mut commands: Commands) {
65/// commands.spawn((
66///     Camera3d::default(),
67///     MotionBlur::default(),
68/// ));
69/// # }
70/// ````
71#[derive(Reflect, Component, Clone, ExtractComponent, ShaderType)]
72#[reflect(Component, Default)]
73#[extract_component_filter(With<Camera>)]
74#[require(DepthPrepass, MotionVectorPrepass)]
75pub struct MotionBlur {
76    /// The strength of motion blur from `0.0` to `1.0`.
77    ///
78    /// The shutter angle describes the fraction of a frame that a camera's shutter is open and
79    /// exposing the film/sensor. For 24fps cinematic film, a shutter angle of 0.5 (180 degrees) is
80    /// common. This means that the shutter was open for half of the frame, or 1/48th of a second.
81    /// The lower the shutter angle, the less exposure time and thus less blur.
82    ///
83    /// A value greater than one is non-physical and results in an object's blur stretching further
84    /// than it traveled in that frame. This might be a desirable effect for artistic reasons, but
85    /// consider allowing users to opt out of this.
86    ///
87    /// This value is intentionally tied to framerate to avoid the aforementioned non-physical
88    /// over-blurring. If you want to emulate a cinematic look, your options are:
89    ///   - Framelimit your app to 24fps, and set the shutter angle to 0.5 (180 deg). Note that
90    ///     depending on artistic intent or the action of a scene, it is common to set the shutter
91    ///     angle between 0.125 (45 deg) and 0.5 (180 deg). This is the most faithful way to
92    ///     reproduce the look of film.
93    ///   - Set the shutter angle greater than one. For example, to emulate the blur strength of
94    ///     film while rendering at 60fps, you would set the shutter angle to `60/24 * 0.5 = 1.25`.
95    ///     Note that this will result in artifacts where the motion of objects will stretch further
96    ///     than they moved between frames; users may find this distracting.
97    pub shutter_angle: f32,
98    /// The quality of motion blur, corresponding to the number of per-pixel samples taken in each
99    /// direction during blur.
100    ///
101    /// Setting this to `1` results in each pixel being sampled once in the leading direction, once
102    /// in the trailing direction, and once in the middle, for a total of 3 samples (`1 * 2 + 1`).
103    /// Setting this to `3` will result in `3 * 2 + 1 = 7` samples. Setting this to `0` is
104    /// equivalent to disabling motion blur.
105    pub samples: u32,
106    #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
107    // WebGL2 structs must be 16 byte aligned.
108    pub _webgl2_padding: bevy_math::Vec2,
109}
110
111impl Default for MotionBlur {
112    fn default() -> Self {
113        Self {
114            shutter_angle: 0.5,
115            samples: 1,
116            #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
117            _webgl2_padding: Default::default(),
118        }
119    }
120}
121
122pub const MOTION_BLUR_SHADER_HANDLE: Handle<Shader> =
123    Handle::weak_from_u128(987457899187986082347921);
124
125/// Adds support for per-object motion blur to the app. See [`MotionBlur`] for details.
126pub struct MotionBlurPlugin;
127impl Plugin for MotionBlurPlugin {
128    fn build(&self, app: &mut App) {
129        load_internal_asset!(
130            app,
131            MOTION_BLUR_SHADER_HANDLE,
132            "motion_blur.wgsl",
133            Shader::from_wgsl
134        );
135        app.add_plugins((
136            ExtractComponentPlugin::<MotionBlur>::default(),
137            UniformComponentPlugin::<MotionBlur>::default(),
138        ));
139
140        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
141            return;
142        };
143
144        render_app
145            .init_resource::<SpecializedRenderPipelines<pipeline::MotionBlurPipeline>>()
146            .add_systems(
147                Render,
148                pipeline::prepare_motion_blur_pipelines.in_set(RenderSet::Prepare),
149            );
150
151        render_app
152            .add_render_graph_node::<ViewNodeRunner<node::MotionBlurNode>>(
153                Core3d,
154                Node3d::MotionBlur,
155            )
156            .add_render_graph_edges(
157                Core3d,
158                (
159                    Node3d::EndMainPass,
160                    Node3d::MotionBlur,
161                    Node3d::Bloom, // we want blurred areas to bloom and tonemap properly.
162                ),
163            );
164    }
165
166    fn finish(&self, app: &mut App) {
167        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
168            return;
169        };
170
171        render_app.init_resource::<pipeline::MotionBlurPipeline>();
172    }
173}