Skip to main content

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