bevy_post_process/dof/
mod.rs

1//! Depth of field, a postprocessing effect that simulates camera focus.
2//!
3//! By default, Bevy renders all objects in full focus: regardless of depth, all
4//! objects are rendered perfectly sharp (up to output resolution). Real lenses,
5//! however, can only focus on objects at a specific distance. The distance
6//! between the nearest and furthest objects that are in focus is known as
7//! [depth of field], and this term is used more generally in computer graphics
8//! to refer to the effect that simulates focus of lenses.
9//!
10//! Attaching [`DepthOfField`] to a camera causes Bevy to simulate the
11//! focus of a camera lens. Generally, Bevy's implementation of depth of field
12//! is optimized for speed instead of physical accuracy. Nevertheless, the depth
13//! of field effect in Bevy is based on physical parameters.
14//!
15//! [Depth of field]: https://en.wikipedia.org/wiki/Depth_of_field
16
17use bevy_app::{App, Plugin};
18use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};
19use bevy_camera::{Camera3d, PhysicalCameraParameters, Projection};
20use bevy_derive::{Deref, DerefMut};
21use bevy_ecs::{
22    component::Component,
23    entity::Entity,
24    query::{QueryItem, With},
25    reflect::ReflectComponent,
26    resource::Resource,
27    schedule::IntoScheduleConfigs as _,
28    system::{lifetimeless::Read, Commands, Query, Res, ResMut},
29    world::World,
30};
31use bevy_image::BevyDefault as _;
32use bevy_math::ops;
33use bevy_reflect::{prelude::ReflectDefault, Reflect};
34use bevy_render::{
35    diagnostic::RecordDiagnostics,
36    extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},
37    render_graph::{
38        NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner,
39    },
40    render_resource::{
41        binding_types::{
42            sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,
43        },
44        BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,
45        CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, LoadOp,
46        Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,
47        RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,
48        ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp,
49        TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,
50    },
51    renderer::{RenderContext, RenderDevice},
52    sync_component::SyncComponentPlugin,
53    sync_world::RenderEntity,
54    texture::{CachedTexture, TextureCache},
55    view::{
56        prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,
57        ViewUniformOffset, ViewUniforms,
58    },
59    Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,
60};
61use bevy_shader::Shader;
62use bevy_utils::{default, once};
63use smallvec::SmallVec;
64use tracing::{info, warn};
65
66use bevy_core_pipeline::{
67    core_3d::{
68        graph::{Core3d, Node3d},
69        DEPTH_TEXTURE_SAMPLING_SUPPORTED,
70    },
71    FullscreenShader,
72};
73
74/// A plugin that adds support for the depth of field effect to Bevy.
75#[derive(Default)]
76pub struct DepthOfFieldPlugin;
77
78/// A component that enables a [depth of field] postprocessing effect when attached to a [`Camera3d`],
79/// simulating the focus of a camera lens.
80///
81/// [depth of field]: https://en.wikipedia.org/wiki/Depth_of_field
82#[derive(Component, Clone, Copy, Reflect)]
83#[reflect(Component, Clone, Default)]
84pub struct DepthOfField {
85    /// The appearance of the effect.
86    pub mode: DepthOfFieldMode,
87
88    /// The distance in meters to the location in focus.
89    pub focal_distance: f32,
90
91    /// The height of the [image sensor format] in meters.
92    ///
93    /// Focal length is derived from the FOV and this value. The default is
94    /// 18.66mm, matching the [Super 35] format, which is popular in cinema.
95    ///
96    /// [image sensor format]: https://en.wikipedia.org/wiki/Image_sensor_format
97    ///
98    /// [Super 35]: https://en.wikipedia.org/wiki/Super_35
99    pub sensor_height: f32,
100
101    /// Along with the focal length, controls how much objects not in focus are
102    /// blurred.
103    pub aperture_f_stops: f32,
104
105    /// The maximum diameter, in pixels, that we allow a circle of confusion to be.
106    ///
107    /// A circle of confusion essentially describes the size of a blur.
108    ///
109    /// This value is nonphysical but is useful for avoiding pathologically-slow
110    /// behavior.
111    pub max_circle_of_confusion_diameter: f32,
112
113    /// Objects are never considered to be farther away than this distance as
114    /// far as depth of field is concerned, even if they actually are.
115    ///
116    /// This is primarily useful for skyboxes and background colors. The Bevy
117    /// renderer considers them to be infinitely far away. Without this value,
118    /// that would cause the circle of confusion to be infinitely large, capped
119    /// only by the `max_circle_of_confusion_diameter`. As that's unsightly,
120    /// this value can be used to essentially adjust how "far away" the skybox
121    /// or background are.
122    pub max_depth: f32,
123}
124
125/// Controls the appearance of the effect.
126#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]
127#[reflect(Default, Clone, PartialEq)]
128pub enum DepthOfFieldMode {
129    /// A more accurate simulation, in which circles of confusion generate
130    /// "spots" of light.
131    ///
132    /// For more information, see [Wikipedia's article on *bokeh*].
133    ///
134    /// This doesn't work on WebGPU.
135    ///
136    /// [Wikipedia's article on *bokeh*]: https://en.wikipedia.org/wiki/Bokeh
137    Bokeh,
138
139    /// A faster simulation, in which out-of-focus areas are simply blurred.
140    ///
141    /// This is less accurate to actual lens behavior and is generally less
142    /// aesthetically pleasing but requires less video memory bandwidth.
143    ///
144    /// This is the default.
145    ///
146    /// This works on native and WebGPU.
147    /// If targeting native platforms, consider using [`DepthOfFieldMode::Bokeh`] instead.
148    #[default]
149    Gaussian,
150}
151
152/// Data about the depth of field effect that's uploaded to the GPU.
153#[derive(Clone, Copy, Component, ShaderType)]
154pub struct DepthOfFieldUniform {
155    /// The distance in meters to the location in focus.
156    focal_distance: f32,
157
158    /// The focal length. See the comment in `DepthOfFieldParams` in `dof.wgsl`
159    /// for more information.
160    focal_length: f32,
161
162    /// The premultiplied factor that we scale the circle of confusion by.
163    ///
164    /// This is calculated as `focal_length² / (sensor_height *
165    /// aperture_f_stops)`.
166    coc_scale_factor: f32,
167
168    /// The maximum circle of confusion diameter in pixels. See the comment in
169    /// [`DepthOfField`] for more information.
170    max_circle_of_confusion_diameter: f32,
171
172    /// The depth value that we clamp distant objects to. See the comment in
173    /// [`DepthOfField`] for more information.
174    max_depth: f32,
175
176    /// Padding.
177    pad_a: u32,
178    /// Padding.
179    pad_b: u32,
180    /// Padding.
181    pad_c: u32,
182}
183
184/// A key that uniquely identifies depth of field pipelines.
185#[derive(Clone, Copy, PartialEq, Eq, Hash)]
186pub struct DepthOfFieldPipelineKey {
187    /// Whether we're doing Gaussian or bokeh blur.
188    pass: DofPass,
189    /// Whether we're using HDR.
190    hdr: bool,
191    /// Whether the render target is multisampled.
192    multisample: bool,
193}
194
195/// Identifies a specific depth of field render pass.
196#[derive(Clone, Copy, PartialEq, Eq, Hash)]
197enum DofPass {
198    /// The first, horizontal, Gaussian blur pass.
199    GaussianHorizontal,
200    /// The second, vertical, Gaussian blur pass.
201    GaussianVertical,
202    /// The first bokeh pass: vertical and diagonal.
203    BokehPass0,
204    /// The second bokeh pass: two diagonals.
205    BokehPass1,
206}
207
208impl Plugin for DepthOfFieldPlugin {
209    fn build(&self, app: &mut App) {
210        embedded_asset!(app, "dof.wgsl");
211
212        app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());
213
214        app.add_plugins(SyncComponentPlugin::<DepthOfField>::default());
215
216        let Some(render_app) = app.get_sub_app_mut(RenderApp) else {
217            return;
218        };
219
220        render_app
221            .init_resource::<SpecializedRenderPipelines<DepthOfFieldPipeline>>()
222            .init_resource::<DepthOfFieldGlobalBindGroup>()
223            .add_systems(RenderStartup, init_dof_global_bind_group_layout)
224            .add_systems(ExtractSchedule, extract_depth_of_field_settings)
225            .add_systems(
226                Render,
227                (
228                    configure_depth_of_field_view_targets,
229                    prepare_auxiliary_depth_of_field_textures,
230                )
231                    .after(prepare_view_targets)
232                    .in_set(RenderSystems::ManageViews),
233            )
234            .add_systems(
235                Render,
236                (
237                    prepare_depth_of_field_view_bind_group_layouts,
238                    prepare_depth_of_field_pipelines,
239                )
240                    .chain()
241                    .in_set(RenderSystems::Prepare),
242            )
243            .add_systems(
244                Render,
245                prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups),
246            )
247            .add_render_graph_node::<ViewNodeRunner<DepthOfFieldNode>>(Core3d, Node3d::DepthOfField)
248            .add_render_graph_edges(
249                Core3d,
250                (Node3d::Bloom, Node3d::DepthOfField, Node3d::Tonemapping),
251            );
252    }
253}
254
255/// The node in the render graph for depth of field.
256#[derive(Default)]
257pub struct DepthOfFieldNode;
258
259/// The layout for the bind group shared among all invocations of the depth of
260/// field shader.
261#[derive(Resource, Clone)]
262pub struct DepthOfFieldGlobalBindGroupLayout {
263    /// The layout.
264    layout: BindGroupLayout,
265    /// The sampler used to sample from the color buffer or buffers.
266    color_texture_sampler: Sampler,
267}
268
269/// The bind group shared among all invocations of the depth of field shader,
270/// regardless of view.
271#[derive(Resource, Default, Deref, DerefMut)]
272pub struct DepthOfFieldGlobalBindGroup(Option<BindGroup>);
273
274#[derive(Component)]
275pub enum DepthOfFieldPipelines {
276    Gaussian {
277        horizontal: CachedRenderPipelineId,
278        vertical: CachedRenderPipelineId,
279    },
280    Bokeh {
281        pass_0: CachedRenderPipelineId,
282        pass_1: CachedRenderPipelineId,
283    },
284}
285
286struct DepthOfFieldPipelineRenderInfo {
287    pass_label: &'static str,
288    view_bind_group_label: &'static str,
289    pipeline: CachedRenderPipelineId,
290    is_dual_input: bool,
291    is_dual_output: bool,
292}
293
294/// The extra texture used as the second render target for the hexagonal bokeh
295/// blur.
296///
297/// This is the same size and format as the main view target texture. It'll only
298/// be present if bokeh is being used.
299#[derive(Component, Deref, DerefMut)]
300pub struct AuxiliaryDepthOfFieldTexture(CachedTexture);
301
302/// Bind group layouts for depth of field specific to a single view.
303#[derive(Component, Clone)]
304pub struct ViewDepthOfFieldBindGroupLayouts {
305    /// The bind group layout for passes that take only one input.
306    single_input: BindGroupLayout,
307
308    /// The bind group layout for the second bokeh pass, which takes two inputs.
309    ///
310    /// This will only be present if bokeh is in use.
311    dual_input: Option<BindGroupLayout>,
312}
313
314/// Information needed to specialize the pipeline corresponding to a pass of the
315/// depth of field shader.
316pub struct DepthOfFieldPipeline {
317    /// The bind group layouts specific to each view.
318    view_bind_group_layouts: ViewDepthOfFieldBindGroupLayouts,
319    /// The bind group layout shared among all invocations of the depth of field
320    /// shader.
321    global_bind_group_layout: BindGroupLayout,
322    /// The asset handle for the fullscreen vertex shader.
323    fullscreen_shader: FullscreenShader,
324    /// The fragment shader asset handle.
325    fragment_shader: Handle<Shader>,
326}
327
328impl ViewNode for DepthOfFieldNode {
329    type ViewQuery = (
330        Read<ViewUniformOffset>,
331        Read<ViewTarget>,
332        Read<ViewDepthTexture>,
333        Read<DepthOfFieldPipelines>,
334        Read<ViewDepthOfFieldBindGroupLayouts>,
335        Read<DynamicUniformIndex<DepthOfFieldUniform>>,
336        Option<Read<AuxiliaryDepthOfFieldTexture>>,
337    );
338
339    fn run<'w>(
340        &self,
341        _: &mut RenderGraphContext,
342        render_context: &mut RenderContext<'w>,
343        (
344            view_uniform_offset,
345            view_target,
346            view_depth_texture,
347            view_pipelines,
348            view_bind_group_layouts,
349            depth_of_field_uniform_index,
350            auxiliary_dof_texture,
351        ): QueryItem<'w, '_, Self::ViewQuery>,
352        world: &'w World,
353    ) -> Result<(), NodeRunError> {
354        let pipeline_cache = world.resource::<PipelineCache>();
355        let view_uniforms = world.resource::<ViewUniforms>();
356        let global_bind_group = world.resource::<DepthOfFieldGlobalBindGroup>();
357
358        let diagnostics = render_context.diagnostic_recorder();
359
360        // We can be in either Gaussian blur or bokeh mode here. Both modes are
361        // similar, consisting of two passes each. We factor out the information
362        // specific to each pass into
363        // [`DepthOfFieldPipelines::pipeline_render_info`].
364        for pipeline_render_info in view_pipelines.pipeline_render_info().iter() {
365            let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = (
366                pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline),
367                view_uniforms.uniforms.binding(),
368                &**global_bind_group,
369            ) else {
370                return Ok(());
371            };
372
373            // We use most of the postprocess infrastructure here. However,
374            // because the bokeh pass has an additional render target, we have
375            // to manage a secondary *auxiliary* texture alongside the textures
376            // managed by the postprocessing logic.
377            let postprocess = view_target.post_process_write();
378
379            let view_bind_group = if pipeline_render_info.is_dual_input {
380                let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = (
381                    auxiliary_dof_texture,
382                    view_bind_group_layouts.dual_input.as_ref(),
383                ) else {
384                    once!(warn!(
385                        "Should have created the auxiliary depth of field texture by now"
386                    ));
387                    continue;
388                };
389                render_context.render_device().create_bind_group(
390                    Some(pipeline_render_info.view_bind_group_label),
391                    dual_input_bind_group_layout,
392                    &BindGroupEntries::sequential((
393                        view_uniforms_binding,
394                        view_depth_texture.view(),
395                        postprocess.source,
396                        &auxiliary_dof_texture.default_view,
397                    )),
398                )
399            } else {
400                render_context.render_device().create_bind_group(
401                    Some(pipeline_render_info.view_bind_group_label),
402                    &view_bind_group_layouts.single_input,
403                    &BindGroupEntries::sequential((
404                        view_uniforms_binding,
405                        view_depth_texture.view(),
406                        postprocess.source,
407                    )),
408                )
409            };
410
411            // Push the first input attachment.
412            let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new();
413            color_attachments.push(Some(RenderPassColorAttachment {
414                view: postprocess.destination,
415                depth_slice: None,
416                resolve_target: None,
417                ops: Operations {
418                    load: LoadOp::Clear(default()),
419                    store: StoreOp::Store,
420                },
421            }));
422
423            // The first pass of the bokeh shader has two color outputs, not
424            // one. Handle this case by attaching the auxiliary texture, which
425            // should have been created by now in
426            // `prepare_auxiliary_depth_of_field_textures``.
427            if pipeline_render_info.is_dual_output {
428                let Some(auxiliary_dof_texture) = auxiliary_dof_texture else {
429                    once!(warn!(
430                        "Should have created the auxiliary depth of field texture by now"
431                    ));
432                    continue;
433                };
434                color_attachments.push(Some(RenderPassColorAttachment {
435                    view: &auxiliary_dof_texture.default_view,
436                    depth_slice: None,
437                    resolve_target: None,
438                    ops: Operations {
439                        load: LoadOp::Clear(default()),
440                        store: StoreOp::Store,
441                    },
442                }));
443            }
444
445            let render_pass_descriptor = RenderPassDescriptor {
446                label: Some(pipeline_render_info.pass_label),
447                color_attachments: &color_attachments,
448                ..default()
449            };
450
451            let mut render_pass = render_context
452                .command_encoder()
453                .begin_render_pass(&render_pass_descriptor);
454            let pass_span =
455                diagnostics.pass_span(&mut render_pass, pipeline_render_info.pass_label);
456
457            render_pass.set_pipeline(render_pipeline);
458            // Set the per-view bind group.
459            render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);
460            // Set the global bind group shared among all invocations of the shader.
461            render_pass.set_bind_group(
462                1,
463                global_bind_group,
464                &[depth_of_field_uniform_index.index()],
465            );
466            // Render the full-screen pass.
467            render_pass.draw(0..3, 0..1);
468
469            pass_span.end(&mut render_pass);
470        }
471
472        Ok(())
473    }
474}
475
476impl Default for DepthOfField {
477    fn default() -> Self {
478        let physical_camera_default = PhysicalCameraParameters::default();
479        Self {
480            focal_distance: 10.0,
481            aperture_f_stops: physical_camera_default.aperture_f_stops,
482            sensor_height: physical_camera_default.sensor_height,
483            max_circle_of_confusion_diameter: 64.0,
484            max_depth: f32::INFINITY,
485            mode: DepthOfFieldMode::Bokeh,
486        }
487    }
488}
489
490impl DepthOfField {
491    /// Initializes [`DepthOfField`] from a set of
492    /// [`PhysicalCameraParameters`].
493    ///
494    /// By passing the same [`PhysicalCameraParameters`] object to this function
495    /// and to [`bevy_camera::Exposure::from_physical_camera`], matching
496    /// results for both the exposure and depth of field effects can be
497    /// obtained.
498    ///
499    /// All fields of the returned [`DepthOfField`] other than
500    /// `focal_length` and `aperture_f_stops` are set to their default values.
501    pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {
502        DepthOfField {
503            sensor_height: camera.sensor_height,
504            aperture_f_stops: camera.aperture_f_stops,
505            ..default()
506        }
507    }
508}
509
510pub fn init_dof_global_bind_group_layout(mut commands: Commands, render_device: Res<RenderDevice>) {
511    // Create the bind group layout that will be shared among all instances
512    // of the depth of field shader.
513    let layout = render_device.create_bind_group_layout(
514        Some("depth of field global bind group layout"),
515        &BindGroupLayoutEntries::sequential(
516            ShaderStages::FRAGMENT,
517            (
518                // `dof_params`
519                uniform_buffer::<DepthOfFieldUniform>(true),
520                // `color_texture_sampler`
521                sampler(SamplerBindingType::Filtering),
522            ),
523        ),
524    );
525
526    // Create the color texture sampler.
527    let sampler = render_device.create_sampler(&SamplerDescriptor {
528        label: Some("depth of field sampler"),
529        mag_filter: FilterMode::Linear,
530        min_filter: FilterMode::Linear,
531        ..default()
532    });
533
534    commands.insert_resource(DepthOfFieldGlobalBindGroupLayout {
535        color_texture_sampler: sampler,
536        layout,
537    });
538}
539
540/// Creates the bind group layouts for the depth of field effect that are
541/// specific to each view.
542pub fn prepare_depth_of_field_view_bind_group_layouts(
543    mut commands: Commands,
544    view_targets: Query<(Entity, &DepthOfField, &Msaa)>,
545    render_device: Res<RenderDevice>,
546) {
547    for (view, depth_of_field, msaa) in view_targets.iter() {
548        // Create the bind group layout for the passes that take one input.
549        let single_input = render_device.create_bind_group_layout(
550            Some("depth of field bind group layout (single input)"),
551            &BindGroupLayoutEntries::sequential(
552                ShaderStages::FRAGMENT,
553                (
554                    uniform_buffer::<ViewUniform>(true),
555                    if *msaa != Msaa::Off {
556                        texture_depth_2d_multisampled()
557                    } else {
558                        texture_depth_2d()
559                    },
560                    texture_2d(TextureSampleType::Float { filterable: true }),
561                ),
562            ),
563        );
564
565        // If needed, create the bind group layout for the second bokeh pass,
566        // which takes two inputs. We only need to do this if bokeh is in use.
567        let dual_input = match depth_of_field.mode {
568            DepthOfFieldMode::Gaussian => None,
569            DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout(
570                Some("depth of field bind group layout (dual input)"),
571                &BindGroupLayoutEntries::sequential(
572                    ShaderStages::FRAGMENT,
573                    (
574                        uniform_buffer::<ViewUniform>(true),
575                        if *msaa != Msaa::Off {
576                            texture_depth_2d_multisampled()
577                        } else {
578                            texture_depth_2d()
579                        },
580                        texture_2d(TextureSampleType::Float { filterable: true }),
581                        texture_2d(TextureSampleType::Float { filterable: true }),
582                    ),
583                ),
584            )),
585        };
586
587        commands
588            .entity(view)
589            .insert(ViewDepthOfFieldBindGroupLayouts {
590                single_input,
591                dual_input,
592            });
593    }
594}
595
596/// Configures depth textures so that the depth of field shader can read from
597/// them.
598///
599/// By default, the depth buffers that Bevy creates aren't able to be bound as
600/// textures. The depth of field shader, however, needs to read from them. So we
601/// need to set the appropriate flag to tell Bevy to make samplable depth
602/// buffers.
603pub fn configure_depth_of_field_view_targets(
604    mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,
605) {
606    for mut camera_3d in view_targets.iter_mut() {
607        let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);
608        depth_texture_usages |= TextureUsages::TEXTURE_BINDING;
609        camera_3d.depth_texture_usages = depth_texture_usages.into();
610    }
611}
612
613/// Creates depth of field bind group 1, which is shared among all instances of
614/// the depth of field shader.
615pub fn prepare_depth_of_field_global_bind_group(
616    global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
617    mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,
618    depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,
619    render_device: Res<RenderDevice>,
620) {
621    let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {
622        return;
623    };
624
625    **dof_bind_group = Some(render_device.create_bind_group(
626        Some("depth of field global bind group"),
627        &global_bind_group_layout.layout,
628        &BindGroupEntries::sequential((
629            depth_of_field_uniforms,                         // `dof_params`
630            &global_bind_group_layout.color_texture_sampler, // `color_texture_sampler`
631        )),
632    ));
633}
634
635/// Creates the second render target texture that the first pass of the bokeh
636/// effect needs.
637pub fn prepare_auxiliary_depth_of_field_textures(
638    mut commands: Commands,
639    render_device: Res<RenderDevice>,
640    mut texture_cache: ResMut<TextureCache>,
641    mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,
642) {
643    for (entity, view_target, depth_of_field) in view_targets.iter_mut() {
644        // An auxiliary texture is only needed for bokeh.
645        if depth_of_field.mode != DepthOfFieldMode::Bokeh {
646            continue;
647        }
648
649        // The texture matches the main view target texture.
650        let texture_descriptor = TextureDescriptor {
651            label: Some("depth of field auxiliary texture"),
652            size: view_target.main_texture().size(),
653            mip_level_count: 1,
654            sample_count: view_target.main_texture().sample_count(),
655            dimension: TextureDimension::D2,
656            format: view_target.main_texture_format(),
657            usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,
658            view_formats: &[],
659        };
660
661        let texture = texture_cache.get(&render_device, texture_descriptor);
662
663        commands
664            .entity(entity)
665            .insert(AuxiliaryDepthOfFieldTexture(texture));
666    }
667}
668
669/// Specializes the depth of field pipelines specific to a view.
670pub fn prepare_depth_of_field_pipelines(
671    mut commands: Commands,
672    pipeline_cache: Res<PipelineCache>,
673    mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,
674    global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,
675    view_targets: Query<(
676        Entity,
677        &ExtractedView,
678        &DepthOfField,
679        &ViewDepthOfFieldBindGroupLayouts,
680        &Msaa,
681    )>,
682    fullscreen_shader: Res<FullscreenShader>,
683    asset_server: Res<AssetServer>,
684) {
685    for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {
686        let dof_pipeline = DepthOfFieldPipeline {
687            view_bind_group_layouts: view_bind_group_layouts.clone(),
688            global_bind_group_layout: global_bind_group_layout.layout.clone(),
689            fullscreen_shader: fullscreen_shader.clone(),
690            fragment_shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"),
691        };
692
693        // We'll need these two flags to create the `DepthOfFieldPipelineKey`s.
694        let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);
695
696        // Go ahead and specialize the pipelines.
697        match depth_of_field.mode {
698            DepthOfFieldMode::Gaussian => {
699                commands
700                    .entity(entity)
701                    .insert(DepthOfFieldPipelines::Gaussian {
702                        horizontal: pipelines.specialize(
703                            &pipeline_cache,
704                            &dof_pipeline,
705                            DepthOfFieldPipelineKey {
706                                hdr,
707                                multisample,
708                                pass: DofPass::GaussianHorizontal,
709                            },
710                        ),
711                        vertical: pipelines.specialize(
712                            &pipeline_cache,
713                            &dof_pipeline,
714                            DepthOfFieldPipelineKey {
715                                hdr,
716                                multisample,
717                                pass: DofPass::GaussianVertical,
718                            },
719                        ),
720                    });
721            }
722
723            DepthOfFieldMode::Bokeh => {
724                commands
725                    .entity(entity)
726                    .insert(DepthOfFieldPipelines::Bokeh {
727                        pass_0: pipelines.specialize(
728                            &pipeline_cache,
729                            &dof_pipeline,
730                            DepthOfFieldPipelineKey {
731                                hdr,
732                                multisample,
733                                pass: DofPass::BokehPass0,
734                            },
735                        ),
736                        pass_1: pipelines.specialize(
737                            &pipeline_cache,
738                            &dof_pipeline,
739                            DepthOfFieldPipelineKey {
740                                hdr,
741                                multisample,
742                                pass: DofPass::BokehPass1,
743                            },
744                        ),
745                    });
746            }
747        }
748    }
749}
750
751impl SpecializedRenderPipeline for DepthOfFieldPipeline {
752    type Key = DepthOfFieldPipelineKey;
753
754    fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {
755        // Build up our pipeline layout.
756        let (mut layout, mut shader_defs) = (vec![], vec![]);
757        let mut targets = vec![Some(ColorTargetState {
758            format: if key.hdr {
759                ViewTarget::TEXTURE_FORMAT_HDR
760            } else {
761                TextureFormat::bevy_default()
762            },
763            blend: None,
764            write_mask: ColorWrites::ALL,
765        })];
766
767        // Select bind group 0, the view-specific bind group.
768        match key.pass {
769            DofPass::GaussianHorizontal | DofPass::GaussianVertical => {
770                // Gaussian blurs take only a single input and output.
771                layout.push(self.view_bind_group_layouts.single_input.clone());
772            }
773            DofPass::BokehPass0 => {
774                // The first bokeh pass takes one input and produces two outputs.
775                layout.push(self.view_bind_group_layouts.single_input.clone());
776                targets.push(targets[0].clone());
777            }
778            DofPass::BokehPass1 => {
779                // The second bokeh pass takes the two outputs from the first
780                // bokeh pass and produces a single output.
781                let dual_input_bind_group_layout = self
782                    .view_bind_group_layouts
783                    .dual_input
784                    .as_ref()
785                    .expect("Dual-input depth of field bind group should have been created by now")
786                    .clone();
787                layout.push(dual_input_bind_group_layout);
788                shader_defs.push("DUAL_INPUT".into());
789            }
790        }
791
792        // Add bind group 1, the global bind group.
793        layout.push(self.global_bind_group_layout.clone());
794
795        if key.multisample {
796            shader_defs.push("MULTISAMPLED".into());
797        }
798
799        RenderPipelineDescriptor {
800            label: Some("depth of field pipeline".into()),
801            layout,
802            vertex: self.fullscreen_shader.to_vertex_state(),
803            fragment: Some(FragmentState {
804                shader: self.fragment_shader.clone(),
805                shader_defs,
806                entry_point: Some(match key.pass {
807                    DofPass::GaussianHorizontal => "gaussian_horizontal".into(),
808                    DofPass::GaussianVertical => "gaussian_vertical".into(),
809                    DofPass::BokehPass0 => "bokeh_pass_0".into(),
810                    DofPass::BokehPass1 => "bokeh_pass_1".into(),
811                }),
812                targets,
813            }),
814            ..default()
815        }
816    }
817}
818
819/// Extracts all [`DepthOfField`] components into the render world.
820fn extract_depth_of_field_settings(
821    mut commands: Commands,
822    mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,
823) {
824    if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {
825        once!(info!(
826            "Disabling depth of field on this platform because depth textures aren't supported correctly"
827        ));
828        return;
829    }
830
831    for (entity, depth_of_field, projection) in query.iter_mut() {
832        let mut entity_commands = commands
833            .get_entity(entity)
834            .expect("Depth of field entity wasn't synced.");
835
836        // Depth of field is nonsensical without a perspective projection.
837        let Projection::Perspective(ref perspective_projection) = *projection else {
838            // TODO: needs better strategy for cleaning up
839            entity_commands.remove::<(
840                DepthOfField,
841                DepthOfFieldUniform,
842                // components added in prepare systems (because `DepthOfFieldNode` does not query extracted components)
843                DepthOfFieldPipelines,
844                AuxiliaryDepthOfFieldTexture,
845                ViewDepthOfFieldBindGroupLayouts,
846            )>();
847            continue;
848        };
849
850        let focal_length =
851            calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);
852
853        // Convert `DepthOfField` to `DepthOfFieldUniform`.
854        entity_commands.insert((
855            *depth_of_field,
856            DepthOfFieldUniform {
857                focal_distance: depth_of_field.focal_distance,
858                focal_length,
859                coc_scale_factor: focal_length * focal_length
860                    / (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),
861                max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,
862                max_depth: depth_of_field.max_depth,
863                pad_a: 0,
864                pad_b: 0,
865                pad_c: 0,
866            },
867        ));
868    }
869}
870
871/// Given the sensor height and the FOV, returns the focal length.
872///
873/// See <https://photo.stackexchange.com/a/97218>.
874pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {
875    0.5 * sensor_height / ops::tan(0.5 * fov)
876}
877
878impl DepthOfFieldPipelines {
879    /// Populates the information that the `DepthOfFieldNode` needs for the two
880    /// depth of field render passes.
881    fn pipeline_render_info(&self) -> [DepthOfFieldPipelineRenderInfo; 2] {
882        match *self {
883            DepthOfFieldPipelines::Gaussian {
884                horizontal: horizontal_pipeline,
885                vertical: vertical_pipeline,
886            } => [
887                DepthOfFieldPipelineRenderInfo {
888                    pass_label: "depth of field pass (horizontal Gaussian)",
889                    view_bind_group_label: "depth of field view bind group (horizontal Gaussian)",
890                    pipeline: horizontal_pipeline,
891                    is_dual_input: false,
892                    is_dual_output: false,
893                },
894                DepthOfFieldPipelineRenderInfo {
895                    pass_label: "depth of field pass (vertical Gaussian)",
896                    view_bind_group_label: "depth of field view bind group (vertical Gaussian)",
897                    pipeline: vertical_pipeline,
898                    is_dual_input: false,
899                    is_dual_output: false,
900                },
901            ],
902
903            DepthOfFieldPipelines::Bokeh {
904                pass_0: pass_0_pipeline,
905                pass_1: pass_1_pipeline,
906            } => [
907                DepthOfFieldPipelineRenderInfo {
908                    pass_label: "depth of field pass (bokeh pass 0)",
909                    view_bind_group_label: "depth of field view bind group (bokeh pass 0)",
910                    pipeline: pass_0_pipeline,
911                    is_dual_input: false,
912                    is_dual_output: true,
913                },
914                DepthOfFieldPipelineRenderInfo {
915                    pass_label: "depth of field pass (bokeh pass 1)",
916                    view_bind_group_label: "depth of field view bind group (bokeh pass 1)",
917                    pipeline: pass_1_pipeline,
918                    is_dual_input: true,
919                    is_dual_output: false,
920                },
921            ],
922        }
923    }
924}