Path: blob/main/crates/bevy_post_process/src/dof/mod.rs
6596 views
//! Depth of field, a postprocessing effect that simulates camera focus.1//!2//! By default, Bevy renders all objects in full focus: regardless of depth, all3//! objects are rendered perfectly sharp (up to output resolution). Real lenses,4//! however, can only focus on objects at a specific distance. The distance5//! between the nearest and furthest objects that are in focus is known as6//! [depth of field], and this term is used more generally in computer graphics7//! to refer to the effect that simulates focus of lenses.8//!9//! Attaching [`DepthOfField`] to a camera causes Bevy to simulate the10//! focus of a camera lens. Generally, Bevy's implementation of depth of field11//! is optimized for speed instead of physical accuracy. Nevertheless, the depth12//! of field effect in Bevy is based on physical parameters.13//!14//! [Depth of field]: https://en.wikipedia.org/wiki/Depth_of_field1516use bevy_app::{App, Plugin};17use bevy_asset::{embedded_asset, load_embedded_asset, AssetServer, Handle};18use bevy_camera::{Camera3d, PhysicalCameraParameters, Projection};19use bevy_derive::{Deref, DerefMut};20use bevy_ecs::{21component::Component,22entity::Entity,23query::{QueryItem, With},24reflect::ReflectComponent,25resource::Resource,26schedule::IntoScheduleConfigs as _,27system::{lifetimeless::Read, Commands, Query, Res, ResMut},28world::World,29};30use bevy_image::BevyDefault as _;31use bevy_math::ops;32use bevy_reflect::{prelude::ReflectDefault, Reflect};33use bevy_render::{34diagnostic::RecordDiagnostics,35extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},36render_graph::{37NodeRunError, RenderGraphContext, RenderGraphExt as _, ViewNode, ViewNodeRunner,38},39render_resource::{40binding_types::{41sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,42},43BindGroup, BindGroupEntries, BindGroupLayout, BindGroupLayoutEntries,44CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, LoadOp,45Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,46RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,47ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp,48TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,49},50renderer::{RenderContext, RenderDevice},51sync_component::SyncComponentPlugin,52sync_world::RenderEntity,53texture::{CachedTexture, TextureCache},54view::{55prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,56ViewUniformOffset, ViewUniforms,57},58Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,59};60use bevy_shader::Shader;61use bevy_utils::{default, once};62use smallvec::SmallVec;63use tracing::{info, warn};6465use bevy_core_pipeline::{66core_3d::{67graph::{Core3d, Node3d},68DEPTH_TEXTURE_SAMPLING_SUPPORTED,69},70FullscreenShader,71};7273/// A plugin that adds support for the depth of field effect to Bevy.74#[derive(Default)]75pub struct DepthOfFieldPlugin;7677/// A component that enables a [depth of field] postprocessing effect when attached to a [`Camera3d`],78/// simulating the focus of a camera lens.79///80/// [depth of field]: https://en.wikipedia.org/wiki/Depth_of_field81#[derive(Component, Clone, Copy, Reflect)]82#[reflect(Component, Clone, Default)]83pub struct DepthOfField {84/// The appearance of the effect.85pub mode: DepthOfFieldMode,8687/// The distance in meters to the location in focus.88pub focal_distance: f32,8990/// The height of the [image sensor format] in meters.91///92/// Focal length is derived from the FOV and this value. The default is93/// 18.66mm, matching the [Super 35] format, which is popular in cinema.94///95/// [image sensor format]: https://en.wikipedia.org/wiki/Image_sensor_format96///97/// [Super 35]: https://en.wikipedia.org/wiki/Super_3598pub sensor_height: f32,99100/// Along with the focal length, controls how much objects not in focus are101/// blurred.102pub aperture_f_stops: f32,103104/// The maximum diameter, in pixels, that we allow a circle of confusion to be.105///106/// A circle of confusion essentially describes the size of a blur.107///108/// This value is nonphysical but is useful for avoiding pathologically-slow109/// behavior.110pub max_circle_of_confusion_diameter: f32,111112/// Objects are never considered to be farther away than this distance as113/// far as depth of field is concerned, even if they actually are.114///115/// This is primarily useful for skyboxes and background colors. The Bevy116/// renderer considers them to be infinitely far away. Without this value,117/// that would cause the circle of confusion to be infinitely large, capped118/// only by the `max_circle_of_confusion_diameter`. As that's unsightly,119/// this value can be used to essentially adjust how "far away" the skybox120/// or background are.121pub max_depth: f32,122}123124/// Controls the appearance of the effect.125#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]126#[reflect(Default, Clone, PartialEq)]127pub enum DepthOfFieldMode {128/// A more accurate simulation, in which circles of confusion generate129/// "spots" of light.130///131/// For more information, see [Wikipedia's article on *bokeh*].132///133/// This doesn't work on WebGPU.134///135/// [Wikipedia's article on *bokeh*]: https://en.wikipedia.org/wiki/Bokeh136Bokeh,137138/// A faster simulation, in which out-of-focus areas are simply blurred.139///140/// This is less accurate to actual lens behavior and is generally less141/// aesthetically pleasing but requires less video memory bandwidth.142///143/// This is the default.144///145/// This works on native and WebGPU.146/// If targeting native platforms, consider using [`DepthOfFieldMode::Bokeh`] instead.147#[default]148Gaussian,149}150151/// Data about the depth of field effect that's uploaded to the GPU.152#[derive(Clone, Copy, Component, ShaderType)]153pub struct DepthOfFieldUniform {154/// The distance in meters to the location in focus.155focal_distance: f32,156157/// The focal length. See the comment in `DepthOfFieldParams` in `dof.wgsl`158/// for more information.159focal_length: f32,160161/// The premultiplied factor that we scale the circle of confusion by.162///163/// This is calculated as `focal_length² / (sensor_height *164/// aperture_f_stops)`.165coc_scale_factor: f32,166167/// The maximum circle of confusion diameter in pixels. See the comment in168/// [`DepthOfField`] for more information.169max_circle_of_confusion_diameter: f32,170171/// The depth value that we clamp distant objects to. See the comment in172/// [`DepthOfField`] for more information.173max_depth: f32,174175/// Padding.176pad_a: u32,177/// Padding.178pad_b: u32,179/// Padding.180pad_c: u32,181}182183/// A key that uniquely identifies depth of field pipelines.184#[derive(Clone, Copy, PartialEq, Eq, Hash)]185pub struct DepthOfFieldPipelineKey {186/// Whether we're doing Gaussian or bokeh blur.187pass: DofPass,188/// Whether we're using HDR.189hdr: bool,190/// Whether the render target is multisampled.191multisample: bool,192}193194/// Identifies a specific depth of field render pass.195#[derive(Clone, Copy, PartialEq, Eq, Hash)]196enum DofPass {197/// The first, horizontal, Gaussian blur pass.198GaussianHorizontal,199/// The second, vertical, Gaussian blur pass.200GaussianVertical,201/// The first bokeh pass: vertical and diagonal.202BokehPass0,203/// The second bokeh pass: two diagonals.204BokehPass1,205}206207impl Plugin for DepthOfFieldPlugin {208fn build(&self, app: &mut App) {209embedded_asset!(app, "dof.wgsl");210211app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());212213app.add_plugins(SyncComponentPlugin::<DepthOfField>::default());214215let Some(render_app) = app.get_sub_app_mut(RenderApp) else {216return;217};218219render_app220.init_resource::<SpecializedRenderPipelines<DepthOfFieldPipeline>>()221.init_resource::<DepthOfFieldGlobalBindGroup>()222.add_systems(RenderStartup, init_dof_global_bind_group_layout)223.add_systems(ExtractSchedule, extract_depth_of_field_settings)224.add_systems(225Render,226(227configure_depth_of_field_view_targets,228prepare_auxiliary_depth_of_field_textures,229)230.after(prepare_view_targets)231.in_set(RenderSystems::ManageViews),232)233.add_systems(234Render,235(236prepare_depth_of_field_view_bind_group_layouts,237prepare_depth_of_field_pipelines,238)239.chain()240.in_set(RenderSystems::Prepare),241)242.add_systems(243Render,244prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups),245)246.add_render_graph_node::<ViewNodeRunner<DepthOfFieldNode>>(Core3d, Node3d::DepthOfField)247.add_render_graph_edges(248Core3d,249(Node3d::Bloom, Node3d::DepthOfField, Node3d::Tonemapping),250);251}252}253254/// The node in the render graph for depth of field.255#[derive(Default)]256pub struct DepthOfFieldNode;257258/// The layout for the bind group shared among all invocations of the depth of259/// field shader.260#[derive(Resource, Clone)]261pub struct DepthOfFieldGlobalBindGroupLayout {262/// The layout.263layout: BindGroupLayout,264/// The sampler used to sample from the color buffer or buffers.265color_texture_sampler: Sampler,266}267268/// The bind group shared among all invocations of the depth of field shader,269/// regardless of view.270#[derive(Resource, Default, Deref, DerefMut)]271pub struct DepthOfFieldGlobalBindGroup(Option<BindGroup>);272273#[derive(Component)]274pub enum DepthOfFieldPipelines {275Gaussian {276horizontal: CachedRenderPipelineId,277vertical: CachedRenderPipelineId,278},279Bokeh {280pass_0: CachedRenderPipelineId,281pass_1: CachedRenderPipelineId,282},283}284285struct DepthOfFieldPipelineRenderInfo {286pass_label: &'static str,287view_bind_group_label: &'static str,288pipeline: CachedRenderPipelineId,289is_dual_input: bool,290is_dual_output: bool,291}292293/// The extra texture used as the second render target for the hexagonal bokeh294/// blur.295///296/// This is the same size and format as the main view target texture. It'll only297/// be present if bokeh is being used.298#[derive(Component, Deref, DerefMut)]299pub struct AuxiliaryDepthOfFieldTexture(CachedTexture);300301/// Bind group layouts for depth of field specific to a single view.302#[derive(Component, Clone)]303pub struct ViewDepthOfFieldBindGroupLayouts {304/// The bind group layout for passes that take only one input.305single_input: BindGroupLayout,306307/// The bind group layout for the second bokeh pass, which takes two inputs.308///309/// This will only be present if bokeh is in use.310dual_input: Option<BindGroupLayout>,311}312313/// Information needed to specialize the pipeline corresponding to a pass of the314/// depth of field shader.315pub struct DepthOfFieldPipeline {316/// The bind group layouts specific to each view.317view_bind_group_layouts: ViewDepthOfFieldBindGroupLayouts,318/// The bind group layout shared among all invocations of the depth of field319/// shader.320global_bind_group_layout: BindGroupLayout,321/// The asset handle for the fullscreen vertex shader.322fullscreen_shader: FullscreenShader,323/// The fragment shader asset handle.324fragment_shader: Handle<Shader>,325}326327impl ViewNode for DepthOfFieldNode {328type ViewQuery = (329Read<ViewUniformOffset>,330Read<ViewTarget>,331Read<ViewDepthTexture>,332Read<DepthOfFieldPipelines>,333Read<ViewDepthOfFieldBindGroupLayouts>,334Read<DynamicUniformIndex<DepthOfFieldUniform>>,335Option<Read<AuxiliaryDepthOfFieldTexture>>,336);337338fn run<'w>(339&self,340_: &mut RenderGraphContext,341render_context: &mut RenderContext<'w>,342(343view_uniform_offset,344view_target,345view_depth_texture,346view_pipelines,347view_bind_group_layouts,348depth_of_field_uniform_index,349auxiliary_dof_texture,350): QueryItem<'w, '_, Self::ViewQuery>,351world: &'w World,352) -> Result<(), NodeRunError> {353let pipeline_cache = world.resource::<PipelineCache>();354let view_uniforms = world.resource::<ViewUniforms>();355let global_bind_group = world.resource::<DepthOfFieldGlobalBindGroup>();356357let diagnostics = render_context.diagnostic_recorder();358359// We can be in either Gaussian blur or bokeh mode here. Both modes are360// similar, consisting of two passes each. We factor out the information361// specific to each pass into362// [`DepthOfFieldPipelines::pipeline_render_info`].363for pipeline_render_info in view_pipelines.pipeline_render_info().iter() {364let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = (365pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline),366view_uniforms.uniforms.binding(),367&**global_bind_group,368) else {369return Ok(());370};371372// We use most of the postprocess infrastructure here. However,373// because the bokeh pass has an additional render target, we have374// to manage a secondary *auxiliary* texture alongside the textures375// managed by the postprocessing logic.376let postprocess = view_target.post_process_write();377378let view_bind_group = if pipeline_render_info.is_dual_input {379let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = (380auxiliary_dof_texture,381view_bind_group_layouts.dual_input.as_ref(),382) else {383once!(warn!(384"Should have created the auxiliary depth of field texture by now"385));386continue;387};388render_context.render_device().create_bind_group(389Some(pipeline_render_info.view_bind_group_label),390dual_input_bind_group_layout,391&BindGroupEntries::sequential((392view_uniforms_binding,393view_depth_texture.view(),394postprocess.source,395&auxiliary_dof_texture.default_view,396)),397)398} else {399render_context.render_device().create_bind_group(400Some(pipeline_render_info.view_bind_group_label),401&view_bind_group_layouts.single_input,402&BindGroupEntries::sequential((403view_uniforms_binding,404view_depth_texture.view(),405postprocess.source,406)),407)408};409410// Push the first input attachment.411let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new();412color_attachments.push(Some(RenderPassColorAttachment {413view: postprocess.destination,414depth_slice: None,415resolve_target: None,416ops: Operations {417load: LoadOp::Clear(default()),418store: StoreOp::Store,419},420}));421422// The first pass of the bokeh shader has two color outputs, not423// one. Handle this case by attaching the auxiliary texture, which424// should have been created by now in425// `prepare_auxiliary_depth_of_field_textures``.426if pipeline_render_info.is_dual_output {427let Some(auxiliary_dof_texture) = auxiliary_dof_texture else {428once!(warn!(429"Should have created the auxiliary depth of field texture by now"430));431continue;432};433color_attachments.push(Some(RenderPassColorAttachment {434view: &auxiliary_dof_texture.default_view,435depth_slice: None,436resolve_target: None,437ops: Operations {438load: LoadOp::Clear(default()),439store: StoreOp::Store,440},441}));442}443444let render_pass_descriptor = RenderPassDescriptor {445label: Some(pipeline_render_info.pass_label),446color_attachments: &color_attachments,447..default()448};449450let mut render_pass = render_context451.command_encoder()452.begin_render_pass(&render_pass_descriptor);453let pass_span =454diagnostics.pass_span(&mut render_pass, pipeline_render_info.pass_label);455456render_pass.set_pipeline(render_pipeline);457// Set the per-view bind group.458render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);459// Set the global bind group shared among all invocations of the shader.460render_pass.set_bind_group(4611,462global_bind_group,463&[depth_of_field_uniform_index.index()],464);465// Render the full-screen pass.466render_pass.draw(0..3, 0..1);467468pass_span.end(&mut render_pass);469}470471Ok(())472}473}474475impl Default for DepthOfField {476fn default() -> Self {477let physical_camera_default = PhysicalCameraParameters::default();478Self {479focal_distance: 10.0,480aperture_f_stops: physical_camera_default.aperture_f_stops,481sensor_height: physical_camera_default.sensor_height,482max_circle_of_confusion_diameter: 64.0,483max_depth: f32::INFINITY,484mode: DepthOfFieldMode::Bokeh,485}486}487}488489impl DepthOfField {490/// Initializes [`DepthOfField`] from a set of491/// [`PhysicalCameraParameters`].492///493/// By passing the same [`PhysicalCameraParameters`] object to this function494/// and to [`bevy_camera::Exposure::from_physical_camera`], matching495/// results for both the exposure and depth of field effects can be496/// obtained.497///498/// All fields of the returned [`DepthOfField`] other than499/// `focal_length` and `aperture_f_stops` are set to their default values.500pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {501DepthOfField {502sensor_height: camera.sensor_height,503aperture_f_stops: camera.aperture_f_stops,504..default()505}506}507}508509pub fn init_dof_global_bind_group_layout(mut commands: Commands, render_device: Res<RenderDevice>) {510// Create the bind group layout that will be shared among all instances511// of the depth of field shader.512let layout = render_device.create_bind_group_layout(513Some("depth of field global bind group layout"),514&BindGroupLayoutEntries::sequential(515ShaderStages::FRAGMENT,516(517// `dof_params`518uniform_buffer::<DepthOfFieldUniform>(true),519// `color_texture_sampler`520sampler(SamplerBindingType::Filtering),521),522),523);524525// Create the color texture sampler.526let sampler = render_device.create_sampler(&SamplerDescriptor {527label: Some("depth of field sampler"),528mag_filter: FilterMode::Linear,529min_filter: FilterMode::Linear,530..default()531});532533commands.insert_resource(DepthOfFieldGlobalBindGroupLayout {534color_texture_sampler: sampler,535layout,536});537}538539/// Creates the bind group layouts for the depth of field effect that are540/// specific to each view.541pub fn prepare_depth_of_field_view_bind_group_layouts(542mut commands: Commands,543view_targets: Query<(Entity, &DepthOfField, &Msaa)>,544render_device: Res<RenderDevice>,545) {546for (view, depth_of_field, msaa) in view_targets.iter() {547// Create the bind group layout for the passes that take one input.548let single_input = render_device.create_bind_group_layout(549Some("depth of field bind group layout (single input)"),550&BindGroupLayoutEntries::sequential(551ShaderStages::FRAGMENT,552(553uniform_buffer::<ViewUniform>(true),554if *msaa != Msaa::Off {555texture_depth_2d_multisampled()556} else {557texture_depth_2d()558},559texture_2d(TextureSampleType::Float { filterable: true }),560),561),562);563564// If needed, create the bind group layout for the second bokeh pass,565// which takes two inputs. We only need to do this if bokeh is in use.566let dual_input = match depth_of_field.mode {567DepthOfFieldMode::Gaussian => None,568DepthOfFieldMode::Bokeh => Some(render_device.create_bind_group_layout(569Some("depth of field bind group layout (dual input)"),570&BindGroupLayoutEntries::sequential(571ShaderStages::FRAGMENT,572(573uniform_buffer::<ViewUniform>(true),574if *msaa != Msaa::Off {575texture_depth_2d_multisampled()576} else {577texture_depth_2d()578},579texture_2d(TextureSampleType::Float { filterable: true }),580texture_2d(TextureSampleType::Float { filterable: true }),581),582),583)),584};585586commands587.entity(view)588.insert(ViewDepthOfFieldBindGroupLayouts {589single_input,590dual_input,591});592}593}594595/// Configures depth textures so that the depth of field shader can read from596/// them.597///598/// By default, the depth buffers that Bevy creates aren't able to be bound as599/// textures. The depth of field shader, however, needs to read from them. So we600/// need to set the appropriate flag to tell Bevy to make samplable depth601/// buffers.602pub fn configure_depth_of_field_view_targets(603mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,604) {605for mut camera_3d in view_targets.iter_mut() {606let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);607depth_texture_usages |= TextureUsages::TEXTURE_BINDING;608camera_3d.depth_texture_usages = depth_texture_usages.into();609}610}611612/// Creates depth of field bind group 1, which is shared among all instances of613/// the depth of field shader.614pub fn prepare_depth_of_field_global_bind_group(615global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,616mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,617depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,618render_device: Res<RenderDevice>,619) {620let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {621return;622};623624**dof_bind_group = Some(render_device.create_bind_group(625Some("depth of field global bind group"),626&global_bind_group_layout.layout,627&BindGroupEntries::sequential((628depth_of_field_uniforms, // `dof_params`629&global_bind_group_layout.color_texture_sampler, // `color_texture_sampler`630)),631));632}633634/// Creates the second render target texture that the first pass of the bokeh635/// effect needs.636pub fn prepare_auxiliary_depth_of_field_textures(637mut commands: Commands,638render_device: Res<RenderDevice>,639mut texture_cache: ResMut<TextureCache>,640mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,641) {642for (entity, view_target, depth_of_field) in view_targets.iter_mut() {643// An auxiliary texture is only needed for bokeh.644if depth_of_field.mode != DepthOfFieldMode::Bokeh {645continue;646}647648// The texture matches the main view target texture.649let texture_descriptor = TextureDescriptor {650label: Some("depth of field auxiliary texture"),651size: view_target.main_texture().size(),652mip_level_count: 1,653sample_count: view_target.main_texture().sample_count(),654dimension: TextureDimension::D2,655format: view_target.main_texture_format(),656usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,657view_formats: &[],658};659660let texture = texture_cache.get(&render_device, texture_descriptor);661662commands663.entity(entity)664.insert(AuxiliaryDepthOfFieldTexture(texture));665}666}667668/// Specializes the depth of field pipelines specific to a view.669pub fn prepare_depth_of_field_pipelines(670mut commands: Commands,671pipeline_cache: Res<PipelineCache>,672mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,673global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,674view_targets: Query<(675Entity,676&ExtractedView,677&DepthOfField,678&ViewDepthOfFieldBindGroupLayouts,679&Msaa,680)>,681fullscreen_shader: Res<FullscreenShader>,682asset_server: Res<AssetServer>,683) {684for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {685let dof_pipeline = DepthOfFieldPipeline {686view_bind_group_layouts: view_bind_group_layouts.clone(),687global_bind_group_layout: global_bind_group_layout.layout.clone(),688fullscreen_shader: fullscreen_shader.clone(),689fragment_shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"),690};691692// We'll need these two flags to create the `DepthOfFieldPipelineKey`s.693let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);694695// Go ahead and specialize the pipelines.696match depth_of_field.mode {697DepthOfFieldMode::Gaussian => {698commands699.entity(entity)700.insert(DepthOfFieldPipelines::Gaussian {701horizontal: pipelines.specialize(702&pipeline_cache,703&dof_pipeline,704DepthOfFieldPipelineKey {705hdr,706multisample,707pass: DofPass::GaussianHorizontal,708},709),710vertical: pipelines.specialize(711&pipeline_cache,712&dof_pipeline,713DepthOfFieldPipelineKey {714hdr,715multisample,716pass: DofPass::GaussianVertical,717},718),719});720}721722DepthOfFieldMode::Bokeh => {723commands724.entity(entity)725.insert(DepthOfFieldPipelines::Bokeh {726pass_0: pipelines.specialize(727&pipeline_cache,728&dof_pipeline,729DepthOfFieldPipelineKey {730hdr,731multisample,732pass: DofPass::BokehPass0,733},734),735pass_1: pipelines.specialize(736&pipeline_cache,737&dof_pipeline,738DepthOfFieldPipelineKey {739hdr,740multisample,741pass: DofPass::BokehPass1,742},743),744});745}746}747}748}749750impl SpecializedRenderPipeline for DepthOfFieldPipeline {751type Key = DepthOfFieldPipelineKey;752753fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {754// Build up our pipeline layout.755let (mut layout, mut shader_defs) = (vec![], vec![]);756let mut targets = vec![Some(ColorTargetState {757format: if key.hdr {758ViewTarget::TEXTURE_FORMAT_HDR759} else {760TextureFormat::bevy_default()761},762blend: None,763write_mask: ColorWrites::ALL,764})];765766// Select bind group 0, the view-specific bind group.767match key.pass {768DofPass::GaussianHorizontal | DofPass::GaussianVertical => {769// Gaussian blurs take only a single input and output.770layout.push(self.view_bind_group_layouts.single_input.clone());771}772DofPass::BokehPass0 => {773// The first bokeh pass takes one input and produces two outputs.774layout.push(self.view_bind_group_layouts.single_input.clone());775targets.push(targets[0].clone());776}777DofPass::BokehPass1 => {778// The second bokeh pass takes the two outputs from the first779// bokeh pass and produces a single output.780let dual_input_bind_group_layout = self781.view_bind_group_layouts782.dual_input783.as_ref()784.expect("Dual-input depth of field bind group should have been created by now")785.clone();786layout.push(dual_input_bind_group_layout);787shader_defs.push("DUAL_INPUT".into());788}789}790791// Add bind group 1, the global bind group.792layout.push(self.global_bind_group_layout.clone());793794if key.multisample {795shader_defs.push("MULTISAMPLED".into());796}797798RenderPipelineDescriptor {799label: Some("depth of field pipeline".into()),800layout,801vertex: self.fullscreen_shader.to_vertex_state(),802fragment: Some(FragmentState {803shader: self.fragment_shader.clone(),804shader_defs,805entry_point: Some(match key.pass {806DofPass::GaussianHorizontal => "gaussian_horizontal".into(),807DofPass::GaussianVertical => "gaussian_vertical".into(),808DofPass::BokehPass0 => "bokeh_pass_0".into(),809DofPass::BokehPass1 => "bokeh_pass_1".into(),810}),811targets,812}),813..default()814}815}816}817818/// Extracts all [`DepthOfField`] components into the render world.819fn extract_depth_of_field_settings(820mut commands: Commands,821mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,822) {823if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {824once!(info!(825"Disabling depth of field on this platform because depth textures aren't supported correctly"826));827return;828}829830for (entity, depth_of_field, projection) in query.iter_mut() {831let mut entity_commands = commands832.get_entity(entity)833.expect("Depth of field entity wasn't synced.");834835// Depth of field is nonsensical without a perspective projection.836let Projection::Perspective(ref perspective_projection) = *projection else {837// TODO: needs better strategy for cleaning up838entity_commands.remove::<(839DepthOfField,840DepthOfFieldUniform,841// components added in prepare systems (because `DepthOfFieldNode` does not query extracted components)842DepthOfFieldPipelines,843AuxiliaryDepthOfFieldTexture,844ViewDepthOfFieldBindGroupLayouts,845)>();846continue;847};848849let focal_length =850calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);851852// Convert `DepthOfField` to `DepthOfFieldUniform`.853entity_commands.insert((854*depth_of_field,855DepthOfFieldUniform {856focal_distance: depth_of_field.focal_distance,857focal_length,858coc_scale_factor: focal_length * focal_length859/ (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),860max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,861max_depth: depth_of_field.max_depth,862pad_a: 0,863pad_b: 0,864pad_c: 0,865},866));867}868}869870/// Given the sensor height and the FOV, returns the focal length.871///872/// See <https://photo.stackexchange.com/a/97218>.873pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {8740.5 * sensor_height / ops::tan(0.5 * fov)875}876877impl DepthOfFieldPipelines {878/// Populates the information that the `DepthOfFieldNode` needs for the two879/// depth of field render passes.880fn pipeline_render_info(&self) -> [DepthOfFieldPipelineRenderInfo; 2] {881match *self {882DepthOfFieldPipelines::Gaussian {883horizontal: horizontal_pipeline,884vertical: vertical_pipeline,885} => [886DepthOfFieldPipelineRenderInfo {887pass_label: "depth of field pass (horizontal Gaussian)",888view_bind_group_label: "depth of field view bind group (horizontal Gaussian)",889pipeline: horizontal_pipeline,890is_dual_input: false,891is_dual_output: false,892},893DepthOfFieldPipelineRenderInfo {894pass_label: "depth of field pass (vertical Gaussian)",895view_bind_group_label: "depth of field view bind group (vertical Gaussian)",896pipeline: vertical_pipeline,897is_dual_input: false,898is_dual_output: false,899},900],901902DepthOfFieldPipelines::Bokeh {903pass_0: pass_0_pipeline,904pass_1: pass_1_pipeline,905} => [906DepthOfFieldPipelineRenderInfo {907pass_label: "depth of field pass (bokeh pass 0)",908view_bind_group_label: "depth of field view bind group (bokeh pass 0)",909pipeline: pass_0_pipeline,910is_dual_input: false,911is_dual_output: true,912},913DepthOfFieldPipelineRenderInfo {914pass_label: "depth of field pass (bokeh pass 1)",915view_bind_group_label: "depth of field view bind group (bokeh pass 1)",916pipeline: pass_1_pipeline,917is_dual_input: true,918is_dual_output: false,919},920],921}922}923}924925926