mirror of
https://github.com/bevyengine/bevy
synced 2025-01-21 09:34:29 +00:00
1f95a484ed
# Objective - Improve antialiasing for non-point light shadow edges. - Very partially addresses https://github.com/bevyengine/bevy/issues/3628. ## Solution - Implements "The Witness"'s shadow map sampling technique. - Ported from @superdump's old branch, all credit to them :) - Implements "Call of Duty: Advanced Warfare"'s stochastic shadow map sampling technique when the velocity prepass is enabled, for use with TAA. - Uses interleaved gradient noise to generate a random angle, and then averages 8 samples in a spiral pattern, rotated by the random angle. - I also tried spatiotemporal blue noise, but it was far too noisy to be filtered by TAA alone. In the future, we should try spatiotemporal blue noise + a specialized shadow denoiser such as https://gpuopen.com/fidelityfx-denoiser/#shadow. This approach would also be useful for hybrid rasterized applications with raytraced shadows. - The COD presentation has an interesting temporal dithering of the noise for use with temporal supersampling that we should revisit when we get DLSS/FSR/other TSR. --- ## Changelog * Added `ShadowFilteringMethod`. Improved directional light and spotlight shadow edges to be less aliased. ## Migration Guide * Shadows cast by directional lights or spotlights now have smoother edges. To revert to the old behavior, add `ShadowFilteringMethod::Hardware2x2` to your cameras. --------- Co-authored-by: IceSentry <c.giguere42@gmail.com> Co-authored-by: Daniel Chia <danstryder@gmail.com> Co-authored-by: robtfm <50659922+robtfm@users.noreply.github.com> Co-authored-by: Brandon Dyer <brandondyer64@gmail.com> Co-authored-by: Edgar Geier <geieredgar@gmail.com> Co-authored-by: Robert Swain <robert.swain@gmail.com> Co-authored-by: Elabajaba <Elabajaba@users.noreply.github.com> Co-authored-by: IceSentry <IceSentry@users.noreply.github.com>
132 lines
6.4 KiB
WebGPU Shading Language
132 lines
6.4 KiB
WebGPU Shading Language
#define_import_path bevy_pbr::shadow_sampling
|
|
|
|
#import bevy_pbr::mesh_view_bindings as view_bindings
|
|
#import bevy_pbr::utils PI
|
|
|
|
// Do the lookup, using HW 2x2 PCF and comparison
|
|
fn sample_shadow_map_hardware(light_local: vec2<f32>, depth: f32, array_index: i32) -> f32 {
|
|
#ifdef NO_ARRAY_TEXTURES_SUPPORT
|
|
return textureSampleCompareLevel(
|
|
view_bindings::directional_shadow_textures,
|
|
view_bindings::directional_shadow_textures_sampler,
|
|
light_local,
|
|
depth,
|
|
);
|
|
#else
|
|
return textureSampleCompareLevel(
|
|
view_bindings::directional_shadow_textures,
|
|
view_bindings::directional_shadow_textures_sampler,
|
|
light_local,
|
|
array_index,
|
|
depth,
|
|
);
|
|
#endif
|
|
}
|
|
|
|
// https://web.archive.org/web/20230210095515/http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1
|
|
fn sample_shadow_map_castano_thirteen(light_local: vec2<f32>, depth: f32, array_index: i32) -> f32 {
|
|
let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
|
|
let inv_shadow_map_size = 1.0 / shadow_map_size;
|
|
|
|
let uv = light_local * shadow_map_size;
|
|
var base_uv = floor(uv + 0.5);
|
|
let s = (uv.x + 0.5 - base_uv.x);
|
|
let t = (uv.y + 0.5 - base_uv.y);
|
|
base_uv -= 0.5;
|
|
base_uv *= inv_shadow_map_size;
|
|
|
|
let uw0 = (4.0 - 3.0 * s);
|
|
let uw1 = 7.0;
|
|
let uw2 = (1.0 + 3.0 * s);
|
|
|
|
let u0 = (3.0 - 2.0 * s) / uw0 - 2.0;
|
|
let u1 = (3.0 + s) / uw1;
|
|
let u2 = s / uw2 + 2.0;
|
|
|
|
let vw0 = (4.0 - 3.0 * t);
|
|
let vw1 = 7.0;
|
|
let vw2 = (1.0 + 3.0 * t);
|
|
|
|
let v0 = (3.0 - 2.0 * t) / vw0 - 2.0;
|
|
let v1 = (3.0 + t) / vw1;
|
|
let v2 = t / vw2 + 2.0;
|
|
|
|
var sum = 0.0;
|
|
|
|
sum += uw0 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u0, v0) * inv_shadow_map_size), depth, array_index);
|
|
sum += uw1 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u1, v0) * inv_shadow_map_size), depth, array_index);
|
|
sum += uw2 * vw0 * sample_shadow_map_hardware(base_uv + (vec2(u2, v0) * inv_shadow_map_size), depth, array_index);
|
|
|
|
sum += uw0 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u0, v1) * inv_shadow_map_size), depth, array_index);
|
|
sum += uw1 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u1, v1) * inv_shadow_map_size), depth, array_index);
|
|
sum += uw2 * vw1 * sample_shadow_map_hardware(base_uv + (vec2(u2, v1) * inv_shadow_map_size), depth, array_index);
|
|
|
|
sum += uw0 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u0, v2) * inv_shadow_map_size), depth, array_index);
|
|
sum += uw1 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u1, v2) * inv_shadow_map_size), depth, array_index);
|
|
sum += uw2 * vw2 * sample_shadow_map_hardware(base_uv + (vec2(u2, v2) * inv_shadow_map_size), depth, array_index);
|
|
|
|
return sum * (1.0 / 144.0);
|
|
}
|
|
|
|
// https://blog.demofox.org/2022/01/01/interleaved-gradient-noise-a-different-kind-of-low-discrepancy-sequence
|
|
fn interleaved_gradient_noise(pixel_coordinates: vec2<f32>) -> f32 {
|
|
let frame = f32(view_bindings::globals.frame_count % 64u);
|
|
let xy = pixel_coordinates + 5.588238 * frame;
|
|
return fract(52.9829189 * fract(0.06711056 * xy.x + 0.00583715 * xy.y));
|
|
}
|
|
|
|
fn map(min1: f32, max1: f32, min2: f32, max2: f32, value: f32) -> f32 {
|
|
return min2 + (value - min1) * (max2 - min2) / (max1 - min1);
|
|
}
|
|
|
|
fn sample_shadow_map_jimenez_fourteen(light_local: vec2<f32>, depth: f32, array_index: i32, texel_size: f32) -> f32 {
|
|
let shadow_map_size = vec2<f32>(textureDimensions(view_bindings::directional_shadow_textures));
|
|
|
|
let random_angle = 2.0 * PI * interleaved_gradient_noise(light_local * shadow_map_size);
|
|
let m = vec2(sin(random_angle), cos(random_angle));
|
|
let rotation_matrix = mat2x2(
|
|
m.y, -m.x,
|
|
m.x, m.y
|
|
);
|
|
|
|
// Empirically chosen fudge factor to make PCF look better across different CSM cascades
|
|
let f = map(0.00390625, 0.022949219, 0.015, 0.035, texel_size);
|
|
let uv_offset_scale = f / (texel_size * shadow_map_size);
|
|
|
|
// https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare (slides 120-135)
|
|
let sample_offset1 = (rotation_matrix * vec2(-0.7071, 0.7071)) * uv_offset_scale;
|
|
let sample_offset2 = (rotation_matrix * vec2(-0.0000, -0.8750)) * uv_offset_scale;
|
|
let sample_offset3 = (rotation_matrix * vec2( 0.5303, 0.5303)) * uv_offset_scale;
|
|
let sample_offset4 = (rotation_matrix * vec2(-0.6250, -0.0000)) * uv_offset_scale;
|
|
let sample_offset5 = (rotation_matrix * vec2( 0.3536, -0.3536)) * uv_offset_scale;
|
|
let sample_offset6 = (rotation_matrix * vec2(-0.0000, 0.3750)) * uv_offset_scale;
|
|
let sample_offset7 = (rotation_matrix * vec2(-0.1768, -0.1768)) * uv_offset_scale;
|
|
let sample_offset8 = (rotation_matrix * vec2( 0.1250, 0.0000)) * uv_offset_scale;
|
|
|
|
var sum = 0.0;
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset1, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset2, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset3, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset4, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset5, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset6, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset7, depth, array_index);
|
|
sum += sample_shadow_map_hardware(light_local + sample_offset8, depth, array_index);
|
|
return sum / 8.0;
|
|
}
|
|
|
|
fn sample_shadow_map(light_local: vec2<f32>, depth: f32, array_index: i32, texel_size: f32) -> f32 {
|
|
#ifdef SHADOW_FILTER_METHOD_CASTANO_13
|
|
return sample_shadow_map_castano_thirteen(light_local, depth, array_index);
|
|
#else ifdef SHADOW_FILTER_METHOD_JIMENEZ_14
|
|
return sample_shadow_map_jimenez_fourteen(light_local, depth, array_index, texel_size);
|
|
#else ifdef SHADOW_FILTER_METHOD_HARDWARE_2X2
|
|
return sample_shadow_map_hardware(light_local, depth, array_index);
|
|
#else
|
|
// This needs a default return value to avoid shader compilation errors if it's compiled with no SHADOW_FILTER_METHOD_* defined.
|
|
// (eg. if the normal prepass is enabled it ends up compiling this due to the normal prepass depending on pbr_functions, which depends on shadows)
|
|
// This should never actually get used, as anyone using bevy's lighting/shadows should always have a SHADOW_FILTER_METHOD defined.
|
|
// Set to 0 to make it obvious that something is wrong.
|
|
return 0.0;
|
|
#endif
|
|
}
|