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, ViewDepthStencilTexture, 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<RenderApp> for MotionBlur {
119 type Target = MotionBlurUniform;
120}
121
122impl ExtractComponent<RenderApp> 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.wesl");
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 &ViewDepthStencilTexture,
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 let Some(depth_view) = depth.attachment.depth_stencil_views().depth_only_view() else {
202 return;
203 };
204 if motion_blur_uniform.samples == 0 || motion_blur_uniform.shutter_angle <= 0.0 {
205 return; }
207
208 let Some(pipeline) = pipeline_cache.get_render_pipeline(pipeline_id.0) else {
209 return;
210 };
211
212 let Some(settings_binding) = settings_uniforms.uniforms().binding() else {
213 return;
214 };
215 let Some(prepass_motion_vectors_texture) = &prepass_textures.motion_vectors else {
216 return;
217 };
218 let Some(globals_uniforms) = globals_buffer.buffer.binding() else {
219 return;
220 };
221
222 let post_process = view_target.post_process_write();
223
224 let layout = if msaa.samples() == 1 {
225 &motion_blur_pipeline.layout
226 } else {
227 &motion_blur_pipeline.layout_msaa
228 };
229
230 let bind_group = ctx.render_device().create_bind_group(
231 Some("motion_blur_bind_group"),
232 &pipeline_cache.get_bind_group_layout(layout),
233 &BindGroupEntries::sequential((
234 post_process.source,
235 &prepass_motion_vectors_texture.texture.default_view,
236 depth_view,
237 &motion_blur_pipeline.sampler,
238 settings_binding.clone(),
239 globals_uniforms.clone(),
240 )),
241 );
242
243 let diagnostics = ctx.diagnostic_recorder();
244 let diagnostics = diagnostics.as_deref();
245
246 let mut render_pass = ctx.begin_tracked_render_pass(RenderPassDescriptor {
247 label: Some("motion_blur"),
248 color_attachments: &[Some(RenderPassColorAttachment {
249 view: post_process.destination,
250 depth_slice: None,
251 resolve_target: None,
252 ops: Operations {
253 load: LoadOp::Clear(Default::default()),
254 store: StoreOp::Store,
255 },
256 })],
257 depth_stencil_attachment: None,
258 timestamp_writes: None,
259 occlusion_query_set: None,
260 multiview_mask: None,
261 });
262 let pass_span = diagnostics.pass_span(&mut render_pass, "motion_blur");
263
264 render_pass.set_render_pipeline(pipeline);
265 render_pass.set_bind_group(0, &bind_group, &[]);
266 render_pass.draw(0..3, 0..1);
267
268 pass_span.end(&mut render_pass);
269}
270
271fn prepare_view_depth_texture_usages_for_motion_blur(
272 mut view_targets: Query<&mut Camera3d, With<MotionBlurUniform>>,
273) {
274 for mut camera in view_targets.iter_mut() {
275 camera.depth_texture_usages.0 |= TextureUsages::TEXTURE_BINDING.bits();
276 }
277}