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