Path: blob/main/crates/bevy_post_process/src/motion_blur/mod.rs
9395 views
//! Per-object, per-pixel motion blur.1//!2//! Add the [`MotionBlur`] component to a camera to enable motion blur.34use crate::{5bloom::bloom,6motion_blur::pipeline::{MotionBlurPipeline, MotionBlurPipelineId},7};8use bevy_app::{App, Plugin};9use bevy_asset::embedded_asset;10use bevy_camera::Camera;11use bevy_core_pipeline::{12prepass::{DepthPrepass, MotionVectorPrepass, ViewPrepassTextures},13schedule::{Core3d, Core3dSystems},14};15use bevy_ecs::{16component::Component,17query::{QueryItem, With},18reflect::ReflectComponent,19schedule::IntoScheduleConfigs,20system::Res,21};22use bevy_reflect::{std_traits::ReflectDefault, Reflect};23use bevy_render::{24diagnostic::RecordDiagnostics,25extract_component::{26ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,27},28globals::GlobalsBuffer,29render_resource::{30BindGroupEntries, Operations, PipelineCache, RenderPassColorAttachment,31RenderPassDescriptor, ShaderType, SpecializedRenderPipelines,32},33renderer::{RenderContext, ViewQuery},34sync_component::SyncComponent,35view::{Msaa, ViewTarget},36Render, RenderApp, RenderStartup, RenderSystems,37};3839pub mod pipeline;4041/// 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 during44/// 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 estimate47/// 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 not49/// 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 more53/// correct results, but are more expensive and complex, and have other kinds of artifacts. This54/// implementation is relatively inexpensive and effective.55///56/// # Usage57///58/// Add the [`MotionBlur`] component to a camera to enable and configure motion blur for that59/// camera.60///61/// ```62/// # use bevy_post_process::motion_blur::MotionBlur;63/// # use bevy_camera::Camera3d;64/// # use bevy_ecs::prelude::*;65/// # fn test(mut commands: Commands) {66/// commands.spawn((67/// Camera3d::default(),68/// MotionBlur::default(),69/// ));70/// # }71/// ````72#[derive(Reflect, Component, Clone)]73#[reflect(Component, Default, Clone)]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 and79/// exposing the film/sensor. For 24fps cinematic film, a shutter angle of 0.5 (180 degrees) is80/// 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 further84/// than it traveled in that frame. This might be a desirable effect for artistic reasons, but85/// consider allowing users to opt out of this.86///87/// This value is intentionally tied to framerate to avoid the aforementioned non-physical88/// 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 that90/// depending on artistic intent or the action of a scene, it is common to set the shutter91/// angle between 0.125 (45 deg) and 0.5 (180 deg). This is the most faithful way to92/// reproduce the look of film.93/// - Set the shutter angle greater than one. For example, to emulate the blur strength of94/// 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 further96/// than they moved between frames; users may find this distracting.97pub shutter_angle: f32,98/// The quality of motion blur, corresponding to the number of per-pixel samples taken in each99/// direction during blur.100///101/// Setting this to `1` results in each pixel being sampled once in the leading direction, once102/// 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` is104/// equivalent to disabling motion blur.105pub samples: u32,106}107108impl Default for MotionBlur {109fn default() -> Self {110Self {111shutter_angle: 0.5,112samples: 1,113}114}115}116117impl SyncComponent for MotionBlur {118type Out = MotionBlurUniform;119}120121impl ExtractComponent for MotionBlur {122type QueryData = &'static Self;123type QueryFilter = With<Camera>;124125fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {126Some(MotionBlurUniform {127shutter_angle: item.shutter_angle,128samples: item.samples,129#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]130_webgl2_padding: Default::default(),131})132}133}134135#[doc(hidden)]136#[derive(Component, ShaderType, Clone)]137pub struct MotionBlurUniform {138shutter_angle: f32,139samples: u32,140#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]141// WebGL2 structs must be 16 byte aligned.142_webgl2_padding: bevy_math::Vec2,143}144145/// Adds support for per-object motion blur to the app. See [`MotionBlur`] for details.146#[derive(Default)]147pub struct MotionBlurPlugin;148impl Plugin for MotionBlurPlugin {149fn build(&self, app: &mut App) {150embedded_asset!(app, "motion_blur.wgsl");151152app.add_plugins((153ExtractComponentPlugin::<MotionBlur>::default(),154UniformComponentPlugin::<MotionBlurUniform>::default(),155));156157let Some(render_app) = app.get_sub_app_mut(RenderApp) else {158return;159};160161render_app162.init_resource::<SpecializedRenderPipelines<MotionBlurPipeline>>()163.add_systems(RenderStartup, pipeline::init_motion_blur_pipeline)164.add_systems(165Render,166pipeline::prepare_motion_blur_pipelines.in_set(RenderSystems::Prepare),167);168169render_app.add_systems(170Core3d,171motion_blur.before(bloom).in_set(Core3dSystems::PostProcess),172);173}174}175176pub fn motion_blur(177view: ViewQuery<(178&ViewTarget,179&MotionBlurPipelineId,180&ViewPrepassTextures,181&MotionBlurUniform,182&Msaa,183)>,184motion_blur_pipeline: Res<MotionBlurPipeline>,185pipeline_cache: Res<PipelineCache>,186settings_uniforms: Res<ComponentUniforms<MotionBlurUniform>>,187globals_buffer: Res<GlobalsBuffer>,188mut ctx: RenderContext,189) {190let (view_target, pipeline_id, prepass_textures, motion_blur_uniform, msaa) = view.into_inner();191192if motion_blur_uniform.samples == 0 || motion_blur_uniform.shutter_angle <= 0.0 {193return; // We can skip running motion blur in these cases.194}195196let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {197return;198};199200let Some(settings_binding) = settings_uniforms.uniforms().binding() else {201return;202};203let (Some(prepass_motion_vectors_texture), Some(prepass_depth_texture)) =204(&prepass_textures.motion_vectors, &prepass_textures.depth)205else {206return;207};208let Some(globals_uniforms) = globals_buffer.buffer.binding() else {209return;210};211212let post_process = view_target.post_process_write();213214let layout = if msaa.samples() == 1 {215&motion_blur_pipeline.layout216} else {217&motion_blur_pipeline.layout_msaa218};219220let bind_group = ctx.render_device().create_bind_group(221Some("motion_blur_bind_group"),222&pipeline_cache.get_bind_group_layout(layout),223&BindGroupEntries::sequential((224post_process.source,225&prepass_motion_vectors_texture.texture.default_view,226&prepass_depth_texture.texture.default_view,227&motion_blur_pipeline.sampler,228settings_binding.clone(),229globals_uniforms.clone(),230)),231);232233let diagnostics = ctx.diagnostic_recorder();234let diagnostics = diagnostics.as_deref();235236let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {237label: Some("motion_blur"),238color_attachments: &[Some(RenderPassColorAttachment {239view: post_process.destination,240depth_slice: None,241resolve_target: None,242ops: Operations::default(),243})],244depth_stencil_attachment: None,245timestamp_writes: None,246occlusion_query_set: None,247multiview_mask: None,248});249let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur");250251render_pass.set_render_pipeline(pipeline);252render_pass.set_bind_group(0, &bind_group, &[]);253render_pass.draw(0..3, 0..1);254255pass_span.end(&mut render_pass);256}257258259