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