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