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