Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 3 additions & 65 deletions crates/bevy_post_process/src/dof/dof.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
#import bevy_core_pipeline::fullscreen_vertex_shader::FullscreenVertexOutput
#import bevy_pbr::mesh_view_bindings::view
#import bevy_pbr::view_transformations::depth_ndc_to_view_z
#import bevy_post_process::gaussian_blur::gaussian_blur
#import bevy_render::view::View

// Parameters that control the depth of field effect. See
Expand Down Expand Up @@ -136,69 +137,6 @@ fn calculate_circle_of_confusion(in_frag_coord: vec4<f32>) -> f32 {
return clamp(candidate_coc * framebuffer_size.y, 0.0, max_coc_diameter);
}

// Performs a single direction of the separable Gaussian blur kernel.
//
// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
// `position` input to the fragment).
//
// * `coc` is the diameter (not the radius) of the circle of confusion for this
// fragment.
//
// * `frag_offset` is the vector, in screen-space units, from one sample to the
// next. For a horizontal blur this will be `vec2(1.0, 0.0)`; for a vertical
// blur this will be `vec2(0.0, 1.0)`.
//
// Returns the resulting color of the fragment.
fn gaussian_blur(frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f32> {
// Usually σ (the standard deviation) is half the radius, and the radius is
// half the CoC. So we multiply by 0.25.
let sigma = coc * 0.25;

// 1.5σ is a good, somewhat aggressive default for support—the number of
// texels on each side of the center that we process.
let support = i32(ceil(sigma * 1.5));
let uv = frag_coord.xy / vec2<f32>(textureDimensions(color_texture_a));
let offset = frag_offset / vec2<f32>(textureDimensions(color_texture_a));

// The probability density function of the Gaussian blur is (up to constant factors) `exp(-1 / 2σ² *
// x²). We precalculate the constant factor here to avoid having to
// calculate it in the inner loop.
let exp_factor = -1.0 / (2.0 * sigma * sigma);

// Accumulate samples on both sides of the current texel. Go two at a time,
// taking advantage of bilinear filtering.
var sum = textureSampleLevel(color_texture_a, color_texture_sampler, uv, 0.0).rgb;
var weight_sum = 1.0;
for (var i = 1; i <= support; i += 2) {
// This is a well-known trick to reduce the number of needed texture
// samples by a factor of two. We seek to accumulate two adjacent
// samples c₀ and c₁ with weights w₀ and w₁ respectively, with a single
// texture sample at a carefully chosen location. Observe that:
//
// k ⋅ lerp(c₀, c₁, t) = w₀⋅c₀ + w₁⋅c₁
//
// w₁
// if k = w₀ + w₁ and t = ───────
// w₀ + w₁
//
// Therefore, if we sample at a distance of t = w₁ / (w₀ + w₁) texels in
// between the two texel centers and scale by k = w₀ + w₁ afterward, we
// effectively evaluate w₀⋅c₀ + w₁⋅c₁ with a single texture lookup.
let w0 = exp(exp_factor * f32(i) * f32(i));
let w1 = exp(exp_factor * f32(i + 1) * f32(i + 1));
let uv_offset = offset * (f32(i) + w1 / (w0 + w1));
let weight = w0 + w1;

sum += (
textureSampleLevel(color_texture_a, color_texture_sampler, uv + uv_offset, 0.0).rgb +
textureSampleLevel(color_texture_a, color_texture_sampler, uv - uv_offset, 0.0).rgb
) * weight;
weight_sum += weight * 2.0;
}

return vec4(sum / weight_sum, 1.0);
}

// Performs a box blur in a single direction, sampling `color_texture_a`.
//
// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
Expand Down Expand Up @@ -255,14 +193,14 @@ fn box_blur_b(frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f
@fragment
fn gaussian_horizontal(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let coc = calculate_circle_of_confusion(in.position);
return gaussian_blur(in.position, coc, vec2(1.0, 0.0));
return gaussian_blur(color_texture_a, color_texture_sampler, in.position, coc, vec2(1.0, 0.0));
}

// Calculates the vertical component of the separable Gaussian blur.
@fragment
fn gaussian_vertical(in: FullscreenVertexOutput) -> @location(0) vec4<f32> {
let coc = calculate_circle_of_confusion(in.position);
return gaussian_blur(in.position, coc, vec2(0.0, 1.0));
return gaussian_blur(color_texture_a, color_texture_sampler, in.position, coc, vec2(0.0, 1.0));
}

// Calculates the vertical and first diagonal components of the separable
Expand Down
75 changes: 75 additions & 0 deletions crates/bevy_post_process/src/gaussian_blur.wgsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// A library containing a 1D Gaussian blur kernel.
//
// This is used by depth of field, but you can also use it in custom
// postprocessing passes.

#define_import_path bevy_post_process::gaussian_blur

// Performs a single direction of the separable Gaussian blur kernel.
//
// * `color_texture` is the texture to blur.
//
// * `color_texture_sampler` is a sampler to sample the texture. It must have
// linear filtering for both minification and magnification.
//
// * `frag_coord` is the screen-space pixel coordinate of the fragment (i.e. the
// `position` input to the fragment).
//
// * `coc` is the diameter (not the radius) of the circle of confusion for this
// fragment.
//
// * `frag_offset` is the vector, in screen-space units, from one sample to the
// next. For a horizontal blur this will be `vec2(1.0, 0.0)`; for a vertical
// blur this will be `vec2(0.0, 1.0)`.
//
// Returns the resulting color of the fragment.
fn gaussian_blur(color_texture: texture_2d<f32>, color_texture_sampler: sampler, frag_coord: vec4<f32>, coc: f32, frag_offset: vec2<f32>) -> vec4<f32> {
// Usually σ (the standard deviation) is half the radius, and the radius is
// half the CoC. So we multiply by 0.25.
let sigma = coc * 0.25;

// 1.5σ is a good, somewhat aggressive default for support—the number of
// texels on each side of the center that we process.
let support = i32(ceil(sigma * 1.5));
let uv = frag_coord.xy / vec2<f32>(textureDimensions(color_texture));
let offset = frag_offset / vec2<f32>(textureDimensions(color_texture));

// The probability density function of the Gaussian blur is (up to constant factors) `exp(-1 / 2σ² *
// x²). We precalculate the constant factor here to avoid having to
// calculate it in the inner loop.
let exp_factor = -1.0 / (2.0 * sigma * sigma);

// Accumulate samples on both sides of the current texel. Go two at a time,
// taking advantage of bilinear filtering.
var sum = textureSampleLevel(color_texture, color_texture_sampler, uv, 0.0).rgb;
var weight_sum = 1.0;
for (var i = 1; i <= support; i += 2) {
// This is a well-known trick to reduce the number of needed texture
// samples by a factor of two. We seek to accumulate two adjacent
// samples c₀ and c₁ with weights w₀ and w₁ respectively, with a single
// texture sample at a carefully chosen location. Observe that:
//
// k ⋅ lerp(c₀, c₁, t) = w₀⋅c₀ + w₁⋅c₁
//
// w₁
// if k = w₀ + w₁ and t = ───────
// w₀ + w₁
//
// Therefore, if we sample at a distance of t = w₁ / (w₀ + w₁) texels in
// between the two texel centers and scale by k = w₀ + w₁ afterward, we
// effectively evaluate w₀⋅c₀ + w₁⋅c₁ with a single texture lookup.
let w0 = exp(exp_factor * f32(i) * f32(i));
let w1 = exp(exp_factor * f32(i + 1) * f32(i + 1));
let uv_offset = offset * (f32(i) + w1 / (w0 + w1));
let weight = w0 + w1;

sum += (
textureSampleLevel(color_texture, color_texture_sampler, uv + uv_offset, 0.0).rgb +
textureSampleLevel(color_texture, color_texture_sampler, uv - uv_offset, 0.0).rgb
) * weight;
weight_sum += weight * 2.0;
}

return vec4(sum / weight_sum, 1.0);
}

3 changes: 3 additions & 0 deletions crates/bevy_post_process/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ use crate::{
motion_blur::MotionBlurPlugin, msaa_writeback::MsaaWritebackPlugin,
};
use bevy_app::{App, Plugin};
use bevy_shader::load_shader_library;

/// Adds bloom, motion blur, depth of field, and chromatic aberration support.
#[derive(Default)]
pub struct PostProcessPlugin;

impl Plugin for PostProcessPlugin {
fn build(&self, app: &mut App) {
load_shader_library!(app, "gaussian_blur.wgsl");

app.add_plugins((
MsaaWritebackPlugin,
BloomPlugin,
Expand Down