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/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. 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@if(MULTISAMPLED)79@group(0) @binding(1) var depth_texture: texture_depth_multisampled_2d;80@else // MULTISAMPLED81@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 as87/// 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 all92// 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 of112/// confusion].113///114/// [circle of confusion]: https://en.wikipedia.org/wiki/Circle_of_confusion115fn 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_field132 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);136}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. the141/// `position` input to the fragment).142///143/// * `coc` is the diameter (not the radius) of the circle of confusion for this144/// fragment.145///146/// * `frag_offset` is the vector, in screen-space units, from one sample to the147/// 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);161}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. the166/// `position` input to the fragment).167///168/// * `coc` is the diameter (not the radius) of the circle of confusion for this169/// fragment.170///171/// * `frag_offset` is the vector, in screen-space units, from one sample to the172/// 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);187}189/// Calculates the horizontal component of the separable Gaussian blur.190@fragment191fn 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));194}196/// Calculates the vertical component of the separable Gaussian blur.197@fragment198fn 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));201}203/// Calculates the vertical and first diagonal components of the separable204/// hexagonal bokeh blur.205///206/// ╱207/// ╱208/// •209/// │210/// │211@fragment212fn 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;222}224/// Calculates the second diagonal components of the separable hexagonal bokeh225/// blur.226///227/// ╲ ╱228/// ╲ ╱229/// •230@if(DUAL_INPUT)231@fragment232fn 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);237}