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/
18#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
19#import bevy_pbr::mesh_view_bindings::view
20#import bevy_pbr::view_transformations::depth_ndc_to_view_z
21#import bevy_post_process::gaussian_blur::gaussian_blur
22#import 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#ifdef 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;
82#endif // MULTISAMPLED
84// The main color texture.
85@group(0) @binding(2) var color_texture_a: texture_2d<f32>;
87// The auxiliary color texture that we're sampling from. This is only used as
88// part of the second bokeh pass.
89#ifdef DUAL_INPUT
90@group(0) @binding(3) var color_texture_b: texture_2d<f32>;
91#endif // DUAL_INPUT
93// The global uniforms, representing data backed by buffers shared among all
94// views in the scene.
96// The parameters that control the depth of field effect.
97@group(1) @binding(0) var<uniform> dof_params: DepthOfFieldParams;
99// The sampler that's used to fetch texels from the source color buffer.
100@group(1) @binding(1) var color_texture_sampler: sampler;
102// used to ensure `depth * (focus - f)` is always a positive number,
103const EPSILON: f32 = 1.19209290e-07;
104// cos(-30°), used for the bokeh blur.
105const COS_NEG_FRAC_PI_6: f32 = 0.8660254037844387;
106// sin(-30°), used for the bokeh blur.
107const SIN_NEG_FRAC_PI_6: f32 = -0.5;
108// cos(-150°), used for the bokeh blur.
109const COS_NEG_FRAC_PI_5_6: f32 = -0.8660254037844387;
110// sin(-150°), used for the bokeh blur.
111const SIN_NEG_FRAC_PI_5_6: f32 = -0.5;
113// Calculates and returns the diameter (not the radius) of the [circle of
114// confusion].
115//
116// [circle of confusion]: https://en.wikipedia.org/wiki/Circle_of_confusion
117fn calculate_circle_of_confusion(in_frag_coord: vec4<f32>) -> f32 {
118 // Unpack the depth of field parameters.
119 let focus = dof_params.focal_distance;
120 let f = dof_params.focal_length;
121 let scale = dof_params.coc_scale_factor;
122 let max_coc_diameter = dof_params.max_circle_of_confusion_diameter;
124 // Sample the depth.
125 let frag_coord = vec2<i32>(floor(in_frag_coord.xy));
126 let raw_depth = textureLoad(depth_texture, frag_coord, 0);
127 let depth = min(-depth_ndc_to_view_z(raw_depth), dof_params.max_depth);
129 // Calculate the circle of confusion.
130 //
131 // This is just the formula from Wikipedia [1].
132 //
133 // [1]: https://en.wikipedia.org/wiki/Circle_of_confusion#Determining_a_circle_of_confusion_diameter_from_the_object_field
134 let candidate_coc = scale * abs(depth - focus) / (depth * max(focus - f, EPSILON));
136 let framebuffer_size = vec2<f32>(textureDimensions(color_texture_a));
137 return clamp(candidate_coc * framebuffer_size.y, 0.0, max_coc_diameter);
140// Performs a box blur in a single direction, sampling `color_texture_a`.
141//
142// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
143// `position` input to the fragment).
144//
145// * `coc` is the diameter (not the radius) of the circle of confusion for this
146// fragment.
147//
148// * `frag_offset` is the vector, in screen-space units, from one sample to the
149// next. This need not be horizontal or vertical.
150fn box_blur_a(frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f32> {
151 let support = i32(round(coc * 0.5));
152 let uv = frag_coord.xy / vec2<f32>(textureDimensions(color_texture_a));
153 let offset = frag_offset / vec2<f32>(textureDimensions(color_texture_a));
155 // Accumulate samples in a single direction.
156 var sum = vec3(0.0);
157 for (var i = 0; i <= support; i += 1) {
158 sum += textureSampleLevel(
159 color_texture_a, color_texture_sampler, uv + offset * f32(i), 0.0).rgb;
160 }
162 return vec4(sum / vec3(1.0 + f32(support)), 1.0);
165// Performs a box blur in a single direction, sampling `color_texture_b`.
166//
167// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
168// `position` input to the fragment).
169//
170// * `coc` is the diameter (not the radius) of the circle of confusion for this
171// fragment.
172//
173// * `frag_offset` is the vector, in screen-space units, from one sample to the
174// next. This need not be horizontal or vertical.
175#ifdef DUAL_INPUT
176fn box_blur_b(frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f32> {
177 let support = i32(round(coc * 0.5));
178 let uv = frag_coord.xy / vec2<f32>(textureDimensions(color_texture_b));
179 let offset = frag_offset / vec2<f32>(textureDimensions(color_texture_b));
181 // Accumulate samples in a single direction.
182 var sum = vec3(0.0);
183 for (var i = 0; i <= support; i += 1) {
184 sum += textureSampleLevel(
185 color_texture_b, color_texture_sampler, uv + offset * f32(i), 0.0).rgb;
186 }
188 return vec4(sum / vec3(1.0 + f32(support)), 1.0);
190#endif
192// Calculates the horizontal component of the separable Gaussian blur.
193@fragment
194fn gaussian_horizontal(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
195 let coc = calculate_circle_of_confusion(in.position);
196 return gaussian_blur(color_texture_a, color_texture_sampler, in.position, coc, vec2(1.0, 0.0));
199// Calculates the vertical component of the separable Gaussian blur.
200@fragment
201fn gaussian_vertical(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
202 let coc = calculate_circle_of_confusion(in.position);
203 return gaussian_blur(color_texture_a, color_texture_sampler, in.position, coc, vec2(0.0, 1.0));
206// Calculates the vertical and first diagonal components of the separable
207// hexagonal bokeh blur.
208//
209// ╱
210// ╱
211// •
212// │
213// │
214@fragment
215fn bokeh_pass_a(in: FullscreenVertexOutput) -> DualOutput {
216 let coc = calculate_circle_of_confusion(in.position);
217 let vertical = box_blur_a(in.position, coc, vec2(0.0, 1.0));
218 let diagonal = box_blur_a(in.position, coc, vec2(COS_NEG_FRAC_PI_6, SIN_NEG_FRAC_PI_6));
220 // Note that the diagonal part is pre-mixed with the vertical component.
221 var output: DualOutput;
222 output.output_0 = vertical;
223 output.output_1 = mix(vertical, diagonal, 0.5);
224 return output;
227// Calculates the second diagonal components of the separable hexagonal bokeh
228// blur.
229//
230// ╲ ╱
231// ╲ ╱
232// •
233#ifdef DUAL_INPUT
234@fragment
235fn bokeh_pass_b(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
236 let coc = calculate_circle_of_confusion(in.position);
237 let output_0 = box_blur_a(in.position, coc, vec2(COS_NEG_FRAC_PI_6, SIN_NEG_FRAC_PI_6));
238 let output_1 = box_blur_b(in.position, coc, vec2(COS_NEG_FRAC_PI_5_6, SIN_NEG_FRAC_PI_5_6));
239 return mix(output_0, output_1, 0.5);
241#endif
Contents