Path: blob/main/crates/bevy_post_process/src/dof/mod.rs
9402 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::With,24reflect::ReflectComponent,25resource::Resource,26schedule::IntoScheduleConfigs as _,27system::{Commands, Query, Res, ResMut},28};29use bevy_image::BevyDefault as _;30use bevy_math::ops;31use bevy_reflect::{prelude::ReflectDefault, Reflect};32use bevy_render::{33extract_component::{ComponentUniforms, DynamicUniformIndex, UniformComponentPlugin},34render_resource::{35binding_types::{36sampler, texture_2d, texture_depth_2d, texture_depth_2d_multisampled, uniform_buffer,37},38BindGroup, BindGroupEntries, BindGroupLayoutDescriptor, BindGroupLayoutEntries,39CachedRenderPipelineId, ColorTargetState, ColorWrites, FilterMode, FragmentState, LoadOp,40Operations, PipelineCache, RenderPassColorAttachment, RenderPassDescriptor,41RenderPipelineDescriptor, Sampler, SamplerBindingType, SamplerDescriptor, ShaderStages,42ShaderType, SpecializedRenderPipeline, SpecializedRenderPipelines, StoreOp,43TextureDescriptor, TextureDimension, TextureFormat, TextureSampleType, TextureUsages,44},45renderer::{RenderContext, RenderDevice, ViewQuery},46sync_component::{SyncComponent, SyncComponentPlugin},47sync_world::RenderEntity,48texture::{CachedTexture, TextureCache},49view::{50prepare_view_targets, ExtractedView, Msaa, ViewDepthTexture, ViewTarget, ViewUniform,51ViewUniformOffset, ViewUniforms,52},53Extract, ExtractSchedule, Render, RenderApp, RenderStartup, RenderSystems,54};55use bevy_shader::Shader;56use bevy_utils::{default, once};57use smallvec::SmallVec;58use tracing::{info, warn};5960use crate::bloom::bloom;61use bevy_core_pipeline::{62core_3d::DEPTH_TEXTURE_SAMPLING_SUPPORTED, schedule::Core3d, tonemapping::tonemapping,63FullscreenShader,64};6566/// A plugin that adds support for the depth of field effect to Bevy.67#[derive(Default)]68pub struct DepthOfFieldPlugin;6970/// A component that enables a [depth of field] postprocessing effect when attached to a [`Camera3d`],71/// simulating the focus of a camera lens.72///73/// [depth of field]: https://en.wikipedia.org/wiki/Depth_of_field74#[derive(Component, Clone, Copy, Reflect)]75#[reflect(Component, Clone, Default)]76pub struct DepthOfField {77/// The appearance of the effect.78pub mode: DepthOfFieldMode,7980/// The distance in meters to the location in focus.81pub focal_distance: f32,8283/// The height of the [image sensor format] in meters.84///85/// Focal length is derived from the FOV and this value. The default is86/// 18.66mm, matching the [Super 35] format, which is popular in cinema.87///88/// [image sensor format]: https://en.wikipedia.org/wiki/Image_sensor_format89///90/// [Super 35]: https://en.wikipedia.org/wiki/Super_3591pub sensor_height: f32,9293/// Along with the focal length, controls how much objects not in focus are94/// blurred.95pub aperture_f_stops: f32,9697/// The maximum diameter, in pixels, that we allow a circle of confusion to be.98///99/// A circle of confusion essentially describes the size of a blur.100///101/// This value is nonphysical but is useful for avoiding pathologically-slow102/// behavior.103pub max_circle_of_confusion_diameter: f32,104105/// Objects are never considered to be farther away than this distance as106/// far as depth of field is concerned, even if they actually are.107///108/// This is primarily useful for skyboxes and background colors. The Bevy109/// renderer considers them to be infinitely far away. Without this value,110/// that would cause the circle of confusion to be infinitely large, capped111/// only by the `max_circle_of_confusion_diameter`. As that's unsightly,112/// this value can be used to essentially adjust how "far away" the skybox113/// or background are.114pub max_depth: f32,115}116117/// Controls the appearance of the effect.118#[derive(Clone, Copy, Default, PartialEq, Debug, Reflect)]119#[reflect(Default, Clone, PartialEq)]120pub enum DepthOfFieldMode {121/// A more accurate simulation, in which circles of confusion generate122/// "spots" of light.123///124/// For more information, see [Wikipedia's article on *bokeh*].125///126/// This doesn't work on WebGPU.127///128/// [Wikipedia's article on *bokeh*]: https://en.wikipedia.org/wiki/Bokeh129Bokeh,130131/// 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 less134/// aesthetically pleasing but requires less video memory bandwidth.135///136/// This is the default.137///138/// This works on native and WebGPU.139/// If targeting native platforms, consider using [`DepthOfFieldMode::Bokeh`] instead.140#[default]141Gaussian,142}143144/// Data about the depth of field effect that's uploaded to the GPU.145#[derive(Clone, Copy, Component, ShaderType)]146pub struct DepthOfFieldUniform {147/// The distance in meters to the location in focus.148focal_distance: f32,149150/// The focal length. See the comment in `DepthOfFieldParams` in `dof.wgsl`151/// for more information.152focal_length: f32,153154/// The premultiplied factor that we scale the circle of confusion by.155///156/// This is calculated as `focal_length² / (sensor_height *157/// aperture_f_stops)`.158coc_scale_factor: f32,159160/// The maximum circle of confusion diameter in pixels. See the comment in161/// [`DepthOfField`] for more information.162max_circle_of_confusion_diameter: f32,163164/// The depth value that we clamp distant objects to. See the comment in165/// [`DepthOfField`] for more information.166max_depth: f32,167168/// Padding.169pad_a: u32,170/// Padding.171pad_b: u32,172/// Padding.173pad_c: u32,174}175176/// A key that uniquely identifies depth of field pipelines.177#[derive(Clone, Copy, PartialEq, Eq, Hash)]178pub struct DepthOfFieldPipelineKey {179/// Whether we're doing Gaussian or bokeh blur.180pass: DofPass,181/// Whether we're using HDR.182hdr: bool,183/// Whether the render target is multisampled.184multisample: bool,185}186187/// Identifies a specific depth of field render pass.188#[derive(Clone, Copy, PartialEq, Eq, Hash)]189enum DofPass {190/// The first, horizontal, Gaussian blur pass.191GaussianHorizontal,192/// The second, vertical, Gaussian blur pass.193GaussianVertical,194/// The first bokeh pass: vertical and diagonal.195BokehPass0,196/// The second bokeh pass: two diagonals.197BokehPass1,198}199200impl Plugin for DepthOfFieldPlugin {201fn build(&self, app: &mut App) {202embedded_asset!(app, "dof.wgsl");203204app.add_plugins(UniformComponentPlugin::<DepthOfFieldUniform>::default());205206app.add_plugins(SyncComponentPlugin::<DepthOfField>::default());207208let Some(render_app) = app.get_sub_app_mut(RenderApp) else {209return;210};211212render_app213.init_resource::<SpecializedRenderPipelines<DepthOfFieldPipeline>>()214.init_resource::<DepthOfFieldGlobalBindGroup>()215.add_systems(RenderStartup, init_dof_global_bind_group_layout)216.add_systems(ExtractSchedule, extract_depth_of_field_settings)217.add_systems(218Render,219(220configure_depth_of_field_view_targets,221prepare_auxiliary_depth_of_field_textures,222)223.after(prepare_view_targets)224.in_set(RenderSystems::ManageViews),225)226.add_systems(227Render,228(229prepare_depth_of_field_view_bind_group_layouts,230prepare_depth_of_field_pipelines,231)232.chain()233.in_set(RenderSystems::Prepare),234)235.add_systems(236Render,237prepare_depth_of_field_global_bind_group.in_set(RenderSystems::PrepareBindGroups),238)239.add_systems(Core3d, depth_of_field.after(bloom).before(tonemapping));240}241}242243/// The layout for the bind group shared among all invocations of the depth of244/// field shader.245#[derive(Resource, Clone)]246pub struct DepthOfFieldGlobalBindGroupLayout {247/// The layout.248layout: BindGroupLayoutDescriptor,249/// The sampler used to sample from the color buffer or buffers.250color_texture_sampler: Sampler,251}252253/// The bind group shared among all invocations of the depth of field shader,254/// regardless of view.255#[derive(Resource, Default, Deref, DerefMut)]256pub struct DepthOfFieldGlobalBindGroup(Option<BindGroup>);257258#[derive(Component)]259pub enum DepthOfFieldPipelines {260Gaussian {261horizontal: CachedRenderPipelineId,262vertical: CachedRenderPipelineId,263},264Bokeh {265pass_0: CachedRenderPipelineId,266pass_1: CachedRenderPipelineId,267},268}269270struct DepthOfFieldPipelineRenderInfo {271pass_label: &'static str,272view_bind_group_label: &'static str,273pipeline: CachedRenderPipelineId,274is_dual_input: bool,275is_dual_output: bool,276}277278/// The extra texture used as the second render target for the hexagonal bokeh279/// blur.280///281/// This is the same size and format as the main view target texture. It'll only282/// be present if bokeh is being used.283#[derive(Component, Deref, DerefMut)]284pub struct AuxiliaryDepthOfFieldTexture(CachedTexture);285286/// Bind group layouts for depth of field specific to a single view.287#[derive(Component, Clone)]288pub struct ViewDepthOfFieldBindGroupLayouts {289/// The bind group layout for passes that take only one input.290single_input: BindGroupLayoutDescriptor,291292/// The bind group layout for the second bokeh pass, which takes two inputs.293///294/// This will only be present if bokeh is in use.295dual_input: Option<BindGroupLayoutDescriptor>,296}297298/// Information needed to specialize the pipeline corresponding to a pass of the299/// depth of field shader.300pub struct DepthOfFieldPipeline {301/// The bind group layouts specific to each view.302view_bind_group_layouts: ViewDepthOfFieldBindGroupLayouts,303/// The bind group layout shared among all invocations of the depth of field304/// shader.305global_bind_group_layout: BindGroupLayoutDescriptor,306/// The asset handle for the fullscreen vertex shader.307fullscreen_shader: FullscreenShader,308/// The fragment shader asset handle.309fragment_shader: Handle<Shader>,310}311312impl Default for DepthOfField {313fn default() -> Self {314let physical_camera_default = PhysicalCameraParameters::default();315Self {316focal_distance: 10.0,317aperture_f_stops: physical_camera_default.aperture_f_stops,318sensor_height: physical_camera_default.sensor_height,319max_circle_of_confusion_diameter: 64.0,320max_depth: f32::INFINITY,321mode: DepthOfFieldMode::default(),322}323}324}325326impl DepthOfField {327/// Initializes [`DepthOfField`] from a set of328/// [`PhysicalCameraParameters`].329///330/// By passing the same [`PhysicalCameraParameters`] object to this function331/// and to [`bevy_camera::Exposure::from_physical_camera`], matching332/// results for both the exposure and depth of field effects can be333/// obtained.334///335/// All fields of the returned [`DepthOfField`] other than336/// `focal_length` and `aperture_f_stops` are set to their default values.337pub fn from_physical_camera(camera: &PhysicalCameraParameters) -> DepthOfField {338DepthOfField {339sensor_height: camera.sensor_height,340aperture_f_stops: camera.aperture_f_stops,341..default()342}343}344}345346pub fn init_dof_global_bind_group_layout(mut commands: Commands, render_device: Res<RenderDevice>) {347// Create the bind group layout that will be shared among all instances348// of the depth of field shader.349let layout = BindGroupLayoutDescriptor::new(350"depth of field global bind group layout",351&BindGroupLayoutEntries::sequential(352ShaderStages::FRAGMENT,353(354// `dof_params`355uniform_buffer::<DepthOfFieldUniform>(true),356// `color_texture_sampler`357sampler(SamplerBindingType::Filtering),358),359),360);361362// Create the color texture sampler.363let sampler = render_device.create_sampler(&SamplerDescriptor {364label: Some("depth of field sampler"),365mag_filter: FilterMode::Linear,366min_filter: FilterMode::Linear,367..default()368});369370commands.insert_resource(DepthOfFieldGlobalBindGroupLayout {371color_texture_sampler: sampler,372layout,373});374}375376/// Creates the bind group layouts for the depth of field effect that are377/// specific to each view.378pub fn prepare_depth_of_field_view_bind_group_layouts(379mut commands: Commands,380view_targets: Query<(Entity, &DepthOfField, &Msaa)>,381) {382for (view, depth_of_field, msaa) in view_targets.iter() {383// Create the bind group layout for the passes that take one input.384let single_input = BindGroupLayoutDescriptor::new(385"depth of field bind group layout (single input)",386&BindGroupLayoutEntries::sequential(387ShaderStages::FRAGMENT,388(389uniform_buffer::<ViewUniform>(true),390if *msaa != Msaa::Off {391texture_depth_2d_multisampled()392} else {393texture_depth_2d()394},395texture_2d(TextureSampleType::Float { filterable: true }),396),397),398);399400// If needed, create the bind group layout for the second bokeh pass,401// which takes two inputs. We only need to do this if bokeh is in use.402let dual_input = match depth_of_field.mode {403DepthOfFieldMode::Gaussian => None,404DepthOfFieldMode::Bokeh => Some(BindGroupLayoutDescriptor::new(405"depth of field bind group layout (dual input)",406&BindGroupLayoutEntries::sequential(407ShaderStages::FRAGMENT,408(409uniform_buffer::<ViewUniform>(true),410if *msaa != Msaa::Off {411texture_depth_2d_multisampled()412} else {413texture_depth_2d()414},415texture_2d(TextureSampleType::Float { filterable: true }),416texture_2d(TextureSampleType::Float { filterable: true }),417),418),419)),420};421422commands423.entity(view)424.insert(ViewDepthOfFieldBindGroupLayouts {425single_input,426dual_input,427});428}429}430431/// Configures depth textures so that the depth of field shader can read from432/// them.433///434/// By default, the depth buffers that Bevy creates aren't able to be bound as435/// textures. The depth of field shader, however, needs to read from them. So we436/// need to set the appropriate flag to tell Bevy to make samplable depth437/// buffers.438pub fn configure_depth_of_field_view_targets(439mut view_targets: Query<&mut Camera3d, With<DepthOfField>>,440) {441for mut camera_3d in view_targets.iter_mut() {442let mut depth_texture_usages = TextureUsages::from(camera_3d.depth_texture_usages);443depth_texture_usages |= TextureUsages::TEXTURE_BINDING;444camera_3d.depth_texture_usages = depth_texture_usages.into();445}446}447448/// Creates depth of field bind group 1, which is shared among all instances of449/// the depth of field shader.450pub fn prepare_depth_of_field_global_bind_group(451global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,452mut dof_bind_group: ResMut<DepthOfFieldGlobalBindGroup>,453depth_of_field_uniforms: Res<ComponentUniforms<DepthOfFieldUniform>>,454render_device: Res<RenderDevice>,455pipeline_cache: Res<PipelineCache>,456) {457let Some(depth_of_field_uniforms) = depth_of_field_uniforms.binding() else {458return;459};460461**dof_bind_group = Some(render_device.create_bind_group(462Some("depth of field global bind group"),463&pipeline_cache.get_bind_group_layout(&global_bind_group_layout.layout),464&BindGroupEntries::sequential((465depth_of_field_uniforms, // `dof_params`466&global_bind_group_layout.color_texture_sampler, // `color_texture_sampler`467)),468));469}470471/// Creates the second render target texture that the first pass of the bokeh472/// effect needs.473pub fn prepare_auxiliary_depth_of_field_textures(474mut commands: Commands,475render_device: Res<RenderDevice>,476mut texture_cache: ResMut<TextureCache>,477mut view_targets: Query<(Entity, &ViewTarget, &DepthOfField)>,478) {479for (entity, view_target, depth_of_field) in view_targets.iter_mut() {480// An auxiliary texture is only needed for bokeh.481if depth_of_field.mode != DepthOfFieldMode::Bokeh {482continue;483}484485// The texture matches the main view target texture.486let texture_descriptor = TextureDescriptor {487label: Some("depth of field auxiliary texture"),488size: view_target.main_texture().size(),489mip_level_count: 1,490sample_count: view_target.main_texture().sample_count(),491dimension: TextureDimension::D2,492format: view_target.main_texture_format(),493usage: TextureUsages::RENDER_ATTACHMENT | TextureUsages::TEXTURE_BINDING,494view_formats: &[],495};496497let texture = texture_cache.get(&render_device, texture_descriptor);498499commands500.entity(entity)501.insert(AuxiliaryDepthOfFieldTexture(texture));502}503}504505/// Specializes the depth of field pipelines specific to a view.506pub fn prepare_depth_of_field_pipelines(507mut commands: Commands,508pipeline_cache: Res<PipelineCache>,509mut pipelines: ResMut<SpecializedRenderPipelines<DepthOfFieldPipeline>>,510global_bind_group_layout: Res<DepthOfFieldGlobalBindGroupLayout>,511view_targets: Query<(512Entity,513&ExtractedView,514&DepthOfField,515&ViewDepthOfFieldBindGroupLayouts,516&Msaa,517)>,518fullscreen_shader: Res<FullscreenShader>,519asset_server: Res<AssetServer>,520) {521for (entity, view, depth_of_field, view_bind_group_layouts, msaa) in view_targets.iter() {522let dof_pipeline = DepthOfFieldPipeline {523view_bind_group_layouts: view_bind_group_layouts.clone(),524global_bind_group_layout: global_bind_group_layout.layout.clone(),525fullscreen_shader: fullscreen_shader.clone(),526fragment_shader: load_embedded_asset!(asset_server.as_ref(), "dof.wgsl"),527};528529// We'll need these two flags to create the `DepthOfFieldPipelineKey`s.530let (hdr, multisample) = (view.hdr, *msaa != Msaa::Off);531532// Go ahead and specialize the pipelines.533match depth_of_field.mode {534DepthOfFieldMode::Gaussian => {535commands536.entity(entity)537.insert(DepthOfFieldPipelines::Gaussian {538horizontal: pipelines.specialize(539&pipeline_cache,540&dof_pipeline,541DepthOfFieldPipelineKey {542hdr,543multisample,544pass: DofPass::GaussianHorizontal,545},546),547vertical: pipelines.specialize(548&pipeline_cache,549&dof_pipeline,550DepthOfFieldPipelineKey {551hdr,552multisample,553pass: DofPass::GaussianVertical,554},555),556});557}558559DepthOfFieldMode::Bokeh => {560commands561.entity(entity)562.insert(DepthOfFieldPipelines::Bokeh {563pass_0: pipelines.specialize(564&pipeline_cache,565&dof_pipeline,566DepthOfFieldPipelineKey {567hdr,568multisample,569pass: DofPass::BokehPass0,570},571),572pass_1: pipelines.specialize(573&pipeline_cache,574&dof_pipeline,575DepthOfFieldPipelineKey {576hdr,577multisample,578pass: DofPass::BokehPass1,579},580),581});582}583}584}585}586587impl SpecializedRenderPipeline for DepthOfFieldPipeline {588type Key = DepthOfFieldPipelineKey;589590fn specialize(&self, key: Self::Key) -> RenderPipelineDescriptor {591// Build up our pipeline layout.592let (mut layout, mut shader_defs) = (vec![], vec![]);593let mut targets = vec![Some(ColorTargetState {594format: if key.hdr {595ViewTarget::TEXTURE_FORMAT_HDR596} else {597TextureFormat::bevy_default()598},599blend: None,600write_mask: ColorWrites::ALL,601})];602603// Select bind group 0, the view-specific bind group.604match key.pass {605DofPass::GaussianHorizontal | DofPass::GaussianVertical => {606// Gaussian blurs take only a single input and output.607layout.push(self.view_bind_group_layouts.single_input.clone());608}609DofPass::BokehPass0 => {610// The first bokeh pass takes one input and produces two outputs.611layout.push(self.view_bind_group_layouts.single_input.clone());612targets.push(targets[0].clone());613}614DofPass::BokehPass1 => {615// The second bokeh pass takes the two outputs from the first616// bokeh pass and produces a single output.617let dual_input_bind_group_layout = self618.view_bind_group_layouts619.dual_input620.as_ref()621.expect("Dual-input depth of field bind group should have been created by now")622.clone();623layout.push(dual_input_bind_group_layout);624shader_defs.push("DUAL_INPUT".into());625}626}627628// Add bind group 1, the global bind group.629layout.push(self.global_bind_group_layout.clone());630631if key.multisample {632shader_defs.push("MULTISAMPLED".into());633}634635RenderPipelineDescriptor {636label: Some("depth of field pipeline".into()),637layout,638vertex: self.fullscreen_shader.to_vertex_state(),639fragment: Some(FragmentState {640shader: self.fragment_shader.clone(),641shader_defs,642entry_point: Some(match key.pass {643DofPass::GaussianHorizontal => "gaussian_horizontal".into(),644DofPass::GaussianVertical => "gaussian_vertical".into(),645DofPass::BokehPass0 => "bokeh_pass_0".into(),646DofPass::BokehPass1 => "bokeh_pass_1".into(),647}),648targets,649}),650..default()651}652}653}654655impl SyncComponent for DepthOfField {656type Out = (657DepthOfField,658DepthOfFieldUniform,659DepthOfFieldPipelines,660AuxiliaryDepthOfFieldTexture,661ViewDepthOfFieldBindGroupLayouts,662);663}664665/// Extracts all [`DepthOfField`] components into the render world.666fn extract_depth_of_field_settings(667mut commands: Commands,668mut query: Extract<Query<(RenderEntity, &DepthOfField, &Projection)>>,669) {670if !DEPTH_TEXTURE_SAMPLING_SUPPORTED {671once!(info!(672"Disabling depth of field on this platform because depth textures aren't supported correctly"673));674return;675}676677for (entity, depth_of_field, projection) in query.iter_mut() {678let mut entity_commands = commands679.get_entity(entity)680.expect("Depth of field entity wasn't synced.");681682// Depth of field is nonsensical without a perspective projection.683let Projection::Perspective(ref perspective_projection) = *projection else {684entity_commands.remove::<<DepthOfField as SyncComponent>::Out>();685686continue;687};688689let focal_length =690calculate_focal_length(depth_of_field.sensor_height, perspective_projection.fov);691692// Convert `DepthOfField` to `DepthOfFieldUniform`.693entity_commands.insert((694*depth_of_field,695DepthOfFieldUniform {696focal_distance: depth_of_field.focal_distance,697focal_length,698coc_scale_factor: focal_length * focal_length699/ (depth_of_field.sensor_height * depth_of_field.aperture_f_stops),700max_circle_of_confusion_diameter: depth_of_field.max_circle_of_confusion_diameter,701max_depth: depth_of_field.max_depth,702pad_a: 0,703pad_b: 0,704pad_c: 0,705},706));707}708}709710/// Given the sensor height and the FOV, returns the focal length.711///712/// See <https://photo.stackexchange.com/a/97218>.713pub fn calculate_focal_length(sensor_height: f32, fov: f32) -> f32 {7140.5 * sensor_height / ops::tan(0.5 * fov)715}716717impl DepthOfFieldPipelines {718/// Populates the information that the `DepthOfFieldNode` needs for the two719/// depth of field render passes.720fn pipeline_render_info(&self) -> [DepthOfFieldPipelineRenderInfo; 2] {721match *self {722DepthOfFieldPipelines::Gaussian {723horizontal: horizontal_pipeline,724vertical: vertical_pipeline,725} => [726DepthOfFieldPipelineRenderInfo {727pass_label: "depth of field pass (horizontal Gaussian)",728view_bind_group_label: "depth of field view bind group (horizontal Gaussian)",729pipeline: horizontal_pipeline,730is_dual_input: false,731is_dual_output: false,732},733DepthOfFieldPipelineRenderInfo {734pass_label: "depth of field pass (vertical Gaussian)",735view_bind_group_label: "depth of field view bind group (vertical Gaussian)",736pipeline: vertical_pipeline,737is_dual_input: false,738is_dual_output: false,739},740],741742DepthOfFieldPipelines::Bokeh {743pass_0: pass_0_pipeline,744pass_1: pass_1_pipeline,745} => [746DepthOfFieldPipelineRenderInfo {747pass_label: "depth of field pass (bokeh pass 0)",748view_bind_group_label: "depth of field view bind group (bokeh pass 0)",749pipeline: pass_0_pipeline,750is_dual_input: false,751is_dual_output: true,752},753DepthOfFieldPipelineRenderInfo {754pass_label: "depth of field pass (bokeh pass 1)",755view_bind_group_label: "depth of field view bind group (bokeh pass 1)",756pipeline: pass_1_pipeline,757is_dual_input: true,758is_dual_output: false,759},760],761}762}763}764765pub(crate) fn depth_of_field(766view: ViewQuery<(767&ViewUniformOffset,768&ViewTarget,769&ViewDepthTexture,770&DepthOfFieldPipelines,771&ViewDepthOfFieldBindGroupLayouts,772&DynamicUniformIndex<DepthOfFieldUniform>,773Option<&AuxiliaryDepthOfFieldTexture>,774)>,775pipeline_cache: Res<PipelineCache>,776view_uniforms: Res<ViewUniforms>,777global_bind_group: Res<DepthOfFieldGlobalBindGroup>,778mut ctx: RenderContext,779) {780let (781view_uniform_offset,782view_target,783view_depth_texture,784view_pipelines,785view_bind_group_layouts,786depth_of_field_uniform_index,787auxiliary_dof_texture,788) = view.into_inner();789790// We can be in either Gaussian blur or bokeh mode here. Both modes are791// similar, consisting of two passes each.792for pipeline_render_info in view_pipelines.pipeline_render_info().iter() {793let (Some(render_pipeline), Some(view_uniforms_binding), Some(global_bind_group)) = (794pipeline_cache.get_render_pipeline(pipeline_render_info.pipeline),795view_uniforms.uniforms.binding(),796&**global_bind_group,797) else {798return;799};800801// We use most of the postprocess infrastructure here. However,802// because the bokeh pass has an additional render target, we have803// to manage a secondary *auxiliary* texture alongside the textures804// managed by the postprocessing logic.805let postprocess = view_target.post_process_write();806807let view_bind_group = if pipeline_render_info.is_dual_input {808let (Some(auxiliary_dof_texture), Some(dual_input_bind_group_layout)) = (809auxiliary_dof_texture,810view_bind_group_layouts.dual_input.as_ref(),811) else {812once!(warn!(813"Should have created the auxiliary depth of field texture by now"814));815continue;816};817ctx.render_device().create_bind_group(818Some(pipeline_render_info.view_bind_group_label),819&pipeline_cache.get_bind_group_layout(dual_input_bind_group_layout),820&BindGroupEntries::sequential((821view_uniforms_binding,822view_depth_texture.view(),823postprocess.source,824&auxiliary_dof_texture.default_view,825)),826)827} else {828ctx.render_device().create_bind_group(829Some(pipeline_render_info.view_bind_group_label),830&pipeline_cache.get_bind_group_layout(&view_bind_group_layouts.single_input),831&BindGroupEntries::sequential((832view_uniforms_binding,833view_depth_texture.view(),834postprocess.source,835)),836)837};838839// Push the first input attachment.840let mut color_attachments: SmallVec<[_; 2]> = SmallVec::new();841color_attachments.push(Some(RenderPassColorAttachment {842view: postprocess.destination,843depth_slice: None,844resolve_target: None,845ops: Operations {846load: LoadOp::Clear(default()),847store: StoreOp::Store,848},849}));850851// The first pass of the bokeh shader has two color outputs, not852// one. Handle this case by attaching the auxiliary texture, which853// should have been created by now in854// `prepare_auxiliary_depth_of_field_textures``.855if pipeline_render_info.is_dual_output {856let Some(auxiliary_dof_texture) = auxiliary_dof_texture else {857once!(warn!(858"Should have created the auxiliary depth of field texture by now"859));860continue;861};862color_attachments.push(Some(RenderPassColorAttachment {863view: &auxiliary_dof_texture.default_view,864depth_slice: None,865resolve_target: None,866ops: Operations {867load: LoadOp::Clear(default()),868store: StoreOp::Store,869},870}));871}872873let render_pass_descriptor = RenderPassDescriptor {874label: Some(pipeline_render_info.pass_label),875color_attachments: &color_attachments,876..default()877};878879let mut render_pass = ctx880.command_encoder()881.begin_render_pass(&render_pass_descriptor);882883render_pass.set_pipeline(render_pipeline);884// Set the per-view bind group.885render_pass.set_bind_group(0, &view_bind_group, &[view_uniform_offset.offset]);886// Set the global bind group shared among all invocations of the shader.887render_pass.set_bind_group(8881,889global_bind_group,890&[depth_of_field_uniform_index.index()],891);892// Render the full-screen pass.893render_pass.draw(0..3, 0..1);894}895}896897898