bevy_post_process/motion_blur/
mod.rs1use crate::{
6 bloom::bloom,
7 motion_blur::pipeline::{MotionBlurPipeline, MotionBlurPipelineId},
8};
9use bevy_app::{App, Plugin};
10use bevy_asset::embedded_asset;
11use bevy_camera::{Camera, Camera3d};
12use bevy_core_pipeline::{
13 prepass::{MotionVectorPrepass, ViewPrepassTextures},
14 schedule::{Core3d, Core3dSystems},
15};
16use bevy_ecs::{
17 component::Component,
18 query::{QueryItem, With},
19 reflect::ReflectComponent,
20 schedule::IntoScheduleConfigs,
21 system::{Query, Res},
22};
23use bevy_reflect::{std_traits::ReflectDefault, Reflect};
24use bevy_render::{
25 diagnostic::RecordDiagnostics,
26 extract_component::{
27 ComponentUniforms, ExtractComponent, ExtractComponentPlugin, UniformComponentPlugin,
28 },
29 globals::GlobalsBuffer,
30 render_resource::{
31 BindGroupEntries, LoadOp, Operations, PipelineCache, RenderPassColorAttachment,
32 RenderPassDescriptor, ShaderType, SpecializedRenderPipelines, StoreOp, TextureUsages,
33 },
34 renderer::{RenderContext, ViewQuery},
35 sync_component::SyncComponent,
36 view::{prepare_view_targets, Msaa, ViewDepthTexture, ViewTarget},
37 GpuResourceAppExt, Render, RenderApp, RenderStartup, RenderSystems,
38};
39
40pub mod pipeline;
41
42#[derive(Reflect, Component, Clone)]
74#[reflect(Component, Default, Clone)]
75#[require(MotionVectorPrepass)]
76pub struct MotionBlur {
77 pub shutter_angle: f32,
99 pub samples: u32,
107}
108
109impl Default for MotionBlur {
110 fn default() -> Self {
111 Self {
112 shutter_angle: 0.5,
113 samples: 1,
114 }
115 }
116}
117
118impl SyncComponent for MotionBlur {
119 type Target = MotionBlurUniform;
120}
121
122impl ExtractComponent for MotionBlur {
123 type QueryData = &'static Self;
124 type QueryFilter = With<Camera>;
125 type Out = MotionBlurUniform;
126
127 fn extract_component(item: QueryItem<Self::QueryData>) -> Option<Self::Out> {
128 Some(MotionBlurUniform {
129 shutter_angle: item.shutter_angle,
130 samples: item.samples,
131 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
132 _webgl2_padding: Default::default(),
133 })
134 }
135}
136
137#[doc(hidden)]
138#[derive(Component, ShaderType, Clone)]
139pub struct MotionBlurUniform {
140 shutter_angle: f32,
141 samples: u32,
142 #[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
143 _webgl2_padding: bevy_math::Vec2,
145}
146
147#[derive(Default)]
149pub struct MotionBlurPlugin;
150impl Plugin for MotionBlurPlugin {
151 fn build(&self, app: &mut App) {
152 embedded_asset!(app, "motion_blur.wgsl");
153
154 app.add_plugins((
155 ExtractComponentPlugin::<MotionBlur>::default(),
156 UniformComponentPlugin::<MotionBlurUniform>::default(),
157 ));
158
159 let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
160 return;
161 };
162
163 render_app
164 .init_gpu_resource::<SpecializedRenderPipelines<MotionBlurPipeline>>()
165 .add_systems(RenderStartup, pipeline::init_motion_blur_pipeline)
166 .add_systems(
167 Render,
168 (
169 pipeline::prepare_motion_blur_pipelines.in_set(RenderSystems::Prepare),
170 prepare_view_depth_texture_usages_for_motion_blur
171 .in_set(RenderSystems::PrepareViews)
172 .after(prepare_view_targets)
173 .ambiguous_with_all(),
174 ),
175 );
176
177 render_app.add_systems(
178 Core3d,
179 motion_blur.before(bloom).in_set(Core3dSystems::PostProcess),
180 );
181 }
182}
183
184pub fn motion_blur(
185 view: ViewQuery<(
186 &ViewTarget,
187 &MotionBlurPipelineId,
188 &ViewPrepassTextures,
189 &ViewDepthTexture,
190 &MotionBlurUniform,
191 &Msaa,
192 )>,
193 motion_blur_pipeline: Res<MotionBlurPipeline>,
194 pipeline_cache: Res<PipelineCache>,
195 settings_uniforms: Res<ComponentUniforms<MotionBlurUniform>>,
196 globals_buffer: Res<GlobalsBuffer>,
197 mut ctx: RenderContext,
198) {
199 let (view_target, pipeline_id, prepass_textures, depth, motion_blur_uniform, msaa) =
200 view.into_inner();
201
202 if motion_blur_uniform.samples == 0 || motion_blur_uniform.shutter_angle <= 0.0 {
203 return; }
205
206 let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
207 return;
208 };
209
210 let Some(settings_binding) = settings_uniforms.uniforms().binding() else {
211 return;
212 };
213 let Some(prepass_motion_vectors_texture) = &prepass_textures.motion_vectors else {
214 return;
215 };
216 let Some(globals_uniforms) = globals_buffer.buffer.binding() else {
217 return;
218 };
219
220 let post_process = view_target.post_process_write();
221
222 let layout = if msaa.samples() == 1 {
223 &motion_blur_pipeline.layout
224 } else {
225 &motion_blur_pipeline.layout_msaa
226 };
227
228 let bind_group = ctx.render_device().create_bind_group(
229 Some("motion_blur_bind_group"),
230 &pipeline_cache.get_bind_group_layout(layout),
231 &BindGroupEntries::sequential((
232 post_process.source,
233 &prepass_motion_vectors_texture.texture.default_view,
234 depth.view(),
235 &motion_blur_pipeline.sampler,
236 settings_binding.clone(),
237 globals_uniforms.clone(),
238 )),
239 );
240
241 let diagnostics = ctx.diagnostic_recorder();
242 let diagnostics = diagnostics.as_deref();
243
244 let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
245 label: Some("motion_blur"),
246 color_attachments: &[Some(RenderPassColorAttachment {
247 view: post_process.destination,
248 depth_slice: None,
249 resolve_target: None,
250 ops: Operations {
251 load: LoadOp::Clear(Default::default()),
252 store: StoreOp::Store,
253 },
254 })],
255 depth_stencil_attachment: None,
256 timestamp_writes: None,
257 occlusion_query_set: None,
258 multiview_mask: None,
259 });
260 let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur");
261
262 render_pass.set_render_pipeline(pipeline);
263 render_pass.set_bind_group(0, &bind_group, &[]);
264 render_pass.draw(0..3, 0..1);
265
266 pass_span.end(&mut render_pass);
267}
268
269fn prepare_view_depth_texture_usages_for_motion_blur(
270 mut view_targets: Query<&mut Camera3d, With<MotionBlurUniform>>,
271) {
272 for mut camera in view_targets.iter_mut() {
273 camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
274 }
275}