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 the4// X direction, and then in the Y direction. This is asymptotically more5// efficient than performing a 2D convolution.6//7// The Bokeh blur uses a similar, but more complex, separable convolution8// technique. The algorithm is described in Colin Barré-Brisebois, "Hexagonal9// Bokeh Blur Revisited" [1]. It's motivated by the observation that we can use10// separable convolutions not only to produce boxes but to produce11// parallelograms. Thus, by performing three separable convolutions in sequence,12// we can produce a hexagonal shape. The first and second convolutions are done13// simultaneously using multiple render targets to cut the total number of14// 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::FullscreenVertexOutput19#import bevy_pbr::mesh_view_bindings::view20#import bevy_pbr::view_transformations::depth_ndc_to_view_z21#import bevy_post_process::gaussian_blur::gaussian_blur22#import bevy_render::view::View24// Parameters that control the depth of field effect. See25// `bevy_core_pipeline::dof::DepthOfFieldUniforms` for information on what these26// 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 distance32 /// from the center of the lens to the principal foci of the lens". The33 /// default value, 50 mm, is considered representative of human eyesight.34 /// Real-world lenses range from anywhere from 5 mm for "fisheye" lenses to35 /// 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_length40 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-slow52 /// behavior.53 max_circle_of_confusion_diameter: f32,55 /// The depth value that we clamp distant objects to. See the comment in56 /// [`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,65}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>,73}75// @group(0) @binding(0) is `mesh_view_bindings::view`.77// The depth texture for the main view.78#ifdef MULTISAMPLED79@group(0) @binding(1) var depth_texture: texture_depth_multisampled_2d;80#else // MULTISAMPLED81@group(0) @binding(1) var depth_texture: texture_depth_2d;82#endif // MULTISAMPLED84// 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 as88// part of the second bokeh pass.89#ifdef DUAL_INPUT90@group(0) @binding(3) var color_texture_b: texture_2d<f32>;91#endif // DUAL_INPUT93// The global uniforms, representing data backed by buffers shared among all94// 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 of114// confusion].115//116// [circle of confusion]: https://en.wikipedia.org/wiki/Circle_of_confusion117fn 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_field134 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);138}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. the143// `position` input to the fragment).144//145// * `coc` is the diameter (not the radius) of the circle of confusion for this146// fragment.147//148// * `frag_offset` is the vector, in screen-space units, from one sample to the149// 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);163}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. the168// `position` input to the fragment).169//170// * `coc` is the diameter (not the radius) of the circle of confusion for this171// fragment.172//173// * `frag_offset` is the vector, in screen-space units, from one sample to the174// next. This need not be horizontal or vertical.175#ifdef DUAL_INPUT176fn 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);189}190#endif192// Calculates the horizontal component of the separable Gaussian blur.193@fragment194fn 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));197}199// Calculates the vertical component of the separable Gaussian blur.200@fragment201fn 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));204}206// Calculates the vertical and first diagonal components of the separable207// hexagonal bokeh blur.208//209// ╱210// ╱211// •212// │213// │214@fragment215fn 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;225}227// Calculates the second diagonal components of the separable hexagonal bokeh228// blur.229//230// ╲ ╱231// ╲ ╱232// •233#ifdef DUAL_INPUT234@fragment235fn 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);240}241#endif