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