Search across packages, versions, files, declarations, and comments. Use multiple terms to narrow results (AND), and add @vertex, @fragment, or @compute to filter by shader stage.
1//! Performs depth of field postprocessing, with both Gaussian and bokeh kernels.
2//!
3//! Gaussian blur is performed as a separable convolution: first blurring in the
4//! X direction, and then in the Y direction. This is asymptotically more
5//! efficient than performing a 2D convolution.
6//!
7//! The Bokeh blur uses a similar, but more complex, separable convolution
8//! technique. The algorithm is described in Colin Barré-Brisebois, "Hexagonal
9//! Bokeh Blur Revisited" [1]. It's motivated by the observation that we can use
10//! separable convolutions not only to produce boxes but to produce
11//! parallelograms. Thus, by performing three separable convolutions in sequence,
12//! we can produce a hexagonal shape. The first and second convolutions are done
13//! simultaneously using multiple render targets to cut the total number of
14//! passes down to two.
15//!
16//! [1]: https://colinbarrebrisebois.com/2017/04/18/hexagonal-bokeh-blur-revisited-part-2-improved-2-pass-version/
18import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput;
19import bevy_pbr::render::mesh_view_bindings::view;
20import bevy_pbr::render::view_transformations::depth_ndc_to_view_z;
21import package::gaussian_blur::gaussian_blur;
22import bevy_render::view::View;
24/// Parameters that control the depth of field effect. See
25/// `bevy_core_pipeline::dof::DepthOfFieldUniforms` for information on what these
26/// parameters mean.
27struct DepthOfFieldParams {
28 /// The distance in meters to the location in focus.
29 focal_distance: f32,
31 /// The [focal length]. Physically speaking, this represents "the distance
32 /// from the center of the lens to the principal foci of the lens". The
33 /// default value, 50 mm, is considered representative of human eyesight.
34 /// Real-world lenses range from anywhere from 5 mm for "fisheye" lenses to
35 /// 2000 mm for "super-telephoto" lenses designed for very distant objects.
36 ///
37 /// The higher the value, the more blurry objects not in focus will be.
38 ///
39 /// [focal length]: https://en.wikipedia.org/wiki/Focal_length
40 focal_length: f32,
42 /// The premultiplied factor that we scale the circle of confusion by.
43 ///
44 /// This is calculated as `focal_length² / (sensor_height * aperture_f_stops)`.
45 coc_scale_factor: f32,
47 /// The maximum diameter, in pixels, that we allow a circle of confusion to be.
48 ///
49 /// A circle of confusion essentially describes the size of a blur.
50 ///
51 /// This value is nonphysical but is useful for avoiding pathologically-slow
52 /// behavior.
53 max_circle_of_confusion_diameter: f32,
55 /// The depth value that we clamp distant objects to. See the comment in
56 /// [`DepthOfField`] for more information.
57 max_depth: f32,
59 /// Padding.
60 pad_a: u32,
61 /// Padding.
62 pad_b: u32,
63 /// Padding.
64 pad_c: u32,
67/// The first bokeh pass outputs to two render targets. We declare them here.
68struct DualOutput {
69 /// The vertical output.
70 @location(0) output_0: vec4<f32>,
71 /// The diagonal output.
72 @location(1) output_1: vec4<f32>,
75// @group(0) @binding(0) is `mesh_view_bindings::view`.
77/// The depth texture for the main view.
78@if(MULTISAMPLED)
79@group(0) @binding(1) var depth_texture: texture_depth_multisampled_2d;
80@else // MULTISAMPLED
81@group(0) @binding(1) var depth_texture: texture_depth_2d;
83/// The main color texture.
84@group(0) @binding(2) var color_texture_a: texture_2d<f32>;
86/// The auxiliary color texture that we're sampling from. This is only used as
87/// part of the second bokeh pass.
88@if(DUAL_INPUT)
89@group(0) @binding(3) var color_texture_b: texture_2d<f32>;
91// The global uniforms, representing data backed by buffers shared among all
92// views in the scene.
94/// The parameters that control the depth of field effect.
95@group(1) @binding(0) var<uniform> dof_params: DepthOfFieldParams;
97/// The sampler that's used to fetch texels from the source color buffer.
98@group(1) @binding(1) var color_texture_sampler: sampler;
100/// used to ensure `depth * (focus - f)` is always a positive number,
101const EPSILON: f32 = 1.19209290e-07;
102/// cos(-30°), used for the bokeh blur.
103const COS_NEG_FRAC_PI_6: f32 = 0.8660254037844387;
104/// sin(-30°), used for the bokeh blur.
105const SIN_NEG_FRAC_PI_6: f32 = -0.5;
106/// cos(-150°), used for the bokeh blur.
107const COS_NEG_FRAC_PI_5_6: f32 = -0.8660254037844387;
108/// sin(-150°), used for the bokeh blur.
109const SIN_NEG_FRAC_PI_5_6: f32 = -0.5;
111/// Calculates and returns the diameter (not the radius) of the [circle of
112/// confusion].
113///
114/// [circle of confusion]: https://en.wikipedia.org/wiki/Circle_of_confusion
115fn calculate_circle_of_confusion(in_frag_coord: vec4<f32>) -> f32 {
116 // Unpack the depth of field parameters.
117 let focus = dof_params.focal_distance;
118 let f = dof_params.focal_length;
119 let scale = dof_params.coc_scale_factor;
120 let max_coc_diameter = dof_params.max_circle_of_confusion_diameter;
122 // Sample the depth.
123 let frag_coord = vec2<i32>(floor(in_frag_coord.xy));
124 let raw_depth = textureLoad(depth_texture, frag_coord, 0);
125 let depth = min(-depth_ndc_to_view_z(raw_depth), dof_params.max_depth);
127 // Calculate the circle of confusion.
128 //
129 // This is just the formula from Wikipedia [1].
130 //
131 // [1]: https://en.wikipedia.org/wiki/Circle_of_confusion#Determining_a_circle_of_confusion_diameter_from_the_object_field
132 let candidate_coc = scale * abs(depth - focus) / (depth * max(focus - f, EPSILON));
134 let framebuffer_size = vec2<f32>(textureDimensions(color_texture_a));
135 return clamp(candidate_coc * framebuffer_size.y, 0.0, max_coc_diameter);
138/// Performs a box blur in a single direction, sampling `color_texture_a`.
139///
140/// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
141/// `position` input to the fragment).
142///
143/// * `coc` is the diameter (not the radius) of the circle of confusion for this
144/// fragment.
145///
146/// * `frag_offset` is the vector, in screen-space units, from one sample to the
147/// next. This need not be horizontal or vertical.
148fn box_blur_a(frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f32> {
149 let support = i32(round(coc * 0.5));
150 let uv = frag_coord.xy / vec2<f32>(textureDimensions(color_texture_a));
151 let offset = frag_offset / vec2<f32>(textureDimensions(color_texture_a));
153 // Accumulate samples in a single direction.
154 var sum = vec3(0.0);
155 for (var i = 0; i <= support; i += 1) {
156 sum += textureSampleLevel(
157 color_texture_a, color_texture_sampler, uv + offset * f32(i), 0.0).rgb;
158 }
160 return vec4(sum / vec3(1.0 + f32(support)), 1.0);
163/// Performs a box blur in a single direction, sampling `color_texture_b`.
164///
165/// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
166/// `position` input to the fragment).
167///
168/// * `coc` is the diameter (not the radius) of the circle of confusion for this
169/// fragment.
170///
171/// * `frag_offset` is the vector, in screen-space units, from one sample to the
172/// next. This need not be horizontal or vertical.
173@if(DUAL_INPUT)
174fn box_blur_b(frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f32> {
175 let support = i32(round(coc * 0.5));
176 let uv = frag_coord.xy / vec2<f32>(textureDimensions(color_texture_b));
177 let offset = frag_offset / vec2<f32>(textureDimensions(color_texture_b));
179 // Accumulate samples in a single direction.
180 var sum = vec3(0.0);
181 for (var i = 0; i <= support; i += 1) {
182 sum += textureSampleLevel(
183 color_texture_b, color_texture_sampler, uv + offset * f32(i), 0.0).rgb;
184 }
186 return vec4(sum / vec3(1.0 + f32(support)), 1.0);
189/// Calculates the horizontal component of the separable Gaussian blur.
190@fragment
191fn gaussian_horizontal(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
192 let coc = calculate_circle_of_confusion(in.position);
193 return gaussian_blur(color_texture_a, color_texture_sampler, in.position, coc, vec2(1.0, 0.0));
196/// Calculates the vertical component of the separable Gaussian blur.
197@fragment
198fn gaussian_vertical(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
199 let coc = calculate_circle_of_confusion(in.position);
200 return gaussian_blur(color_texture_a, color_texture_sampler, in.position, coc, vec2(0.0, 1.0));
203/// Calculates the vertical and first diagonal components of the separable
204/// hexagonal bokeh blur.
205///
206/// ╱
207/// ╱
208/// •
209/// │
210/// │
211@fragment
212fn bokeh_pass_a(in: FullscreenVertexOutput) -> DualOutput {
213 let coc = calculate_circle_of_confusion(in.position);
214 let vertical = box_blur_a(in.position, coc, vec2(0.0, 1.0));
215 let diagonal = box_blur_a(in.position, coc, vec2(COS_NEG_FRAC_PI_6, SIN_NEG_FRAC_PI_6));
217 // Note that the diagonal part is pre-mixed with the vertical component.
218 var output: DualOutput;
219 output.output_0 = vertical;
220 output.output_1 = mix(vertical, diagonal, 0.5);
221 return output;
224/// Calculates the second diagonal components of the separable hexagonal bokeh
225/// blur.
226///
227/// ╲ ╱
228/// ╲ ╱
229/// •
230@if(DUAL_INPUT)
231@fragment
232fn bokeh_pass_b(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
233 let coc = calculate_circle_of_confusion(in.position);
234 let output_0 = box_blur_a(in.position, coc, vec2(COS_NEG_FRAC_PI_6, SIN_NEG_FRAC_PI_6));
235 let output_1 = box_blur_b(in.position, coc, vec2(COS_NEG_FRAC_PI_5_6, SIN_NEG_FRAC_PI_5_6));
236 return mix(output_0, output_1, 0.5);
Contents