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