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