Skip to content

Commit

Permalink
Implement percentage-closer filtering (PCF) for point lights. (#12910)
Browse files Browse the repository at this point in the history
I ported the two existing PCF techniques to the cubemap domain as best I
could. Generally, the technique is to create a 2D orthonormal basis
using Gram-Schmidt normalization, then apply the technique over that
basis. The results look fine, though the shadow bias often needs
adjusting.

For comparison, Unity uses a 4-tap pattern for PCF on point lights of
(1, 1, 1), (-1, -1, 1), (-1, 1, -1), (1, -1, -1). I tried this but
didn't like the look, so I went with the design above, which ports the
2D techniques to the 3D domain. There's surprisingly little material on
point light PCF.

I've gone through every example using point lights and verified that the
shadow maps look fine, adjusting biases as necessary.

Fixes #3628.

---

## Changelog

### Added
* Shadows from point lights now support percentage-closer filtering
(PCF), and as a result look less aliased.

### Changed
* `ShadowFilteringMethod::Castano13` and
`ShadowFilteringMethod::Jimenez14` have been renamed to
`ShadowFilteringMethod::Gaussian` and `ShadowFilteringMethod::Temporal`
respectively.

## Migration Guide

* `ShadowFilteringMethod::Castano13` and
`ShadowFilteringMethod::Jimenez14` have been renamed to
`ShadowFilteringMethod::Gaussian` and `ShadowFilteringMethod::Temporal`
respectively.
  • Loading branch information
pcwalton authored Apr 10, 2024
1 parent ddcbb3c commit d59b1e7
Show file tree
Hide file tree
Showing 11 changed files with 282 additions and 68 deletions.
16 changes: 8 additions & 8 deletions crates/bevy_pbr/src/deferred/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,10 +308,10 @@ impl SpecializedRenderPipeline for DeferredLightingLayout {
key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS);
if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 {
shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13 {
shader_defs.push("SHADOW_FILTER_METHOD_CASTANO_13".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14 {
shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN {
shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL {
shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into());
}

#[cfg(all(feature = "webgl", target_arch = "wasm32", not(feature = "webgpu")))]
Expand Down Expand Up @@ -489,11 +489,11 @@ pub fn prepare_deferred_lighting_pipelines(
ShadowFilteringMethod::Hardware2x2 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
}
ShadowFilteringMethod::Castano13 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13;
ShadowFilteringMethod::Gaussian => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
}
ShadowFilteringMethod::Jimenez14 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14;
ShadowFilteringMethod::Temporal => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
}
}

Expand Down
25 changes: 16 additions & 9 deletions crates/bevy_pbr/src/light/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -460,29 +460,36 @@ pub struct TransmittedShadowReceiver;
///
/// The different modes use different approaches to
/// [Percentage Closer Filtering](https://developer.nvidia.com/gpugems/gpugems/part-ii-lighting-and-shadows/chapter-11-shadow-map-antialiasing).
///
/// Currently does not affect point lights.
#[derive(Component, ExtractComponent, Reflect, Clone, Copy, PartialEq, Eq, Default)]
#[reflect(Component, Default)]
pub enum ShadowFilteringMethod {
/// Hardware 2x2.
///
/// Fast but poor quality.
Hardware2x2,
/// Method by Ignacio Castaño for The Witness using 9 samples and smart
/// filtering to achieve the same as a regular 5x5 filter kernel.
/// Approximates a fixed Gaussian blur, good when TAA isn't in use.
///
/// Good quality, good performance.
///
/// For directional and spot lights, this uses a [method by Ignacio Castaño
/// for *The Witness*] using 9 samples and smart filtering to achieve the same
/// as a regular 5x5 filter kernel.
///
/// [method by Ignacio Castaño for *The Witness*]: https://web.archive.org/web/20230210095515/http://the-witness.net/news/2013/09/shadow-mapping-summary-part-1/
#[default]
Castano13,
/// Method by Jorge Jimenez for Call of Duty: Advanced Warfare using 8
/// samples in spiral pattern, randomly-rotated by interleaved gradient
/// noise with spatial variation.
Gaussian,
/// A randomized filter that varies over time, good when TAA is in use.
///
/// Good quality when used with
/// [`TemporalAntiAliasSettings`](bevy_core_pipeline::experimental::taa::TemporalAntiAliasSettings)
/// and good performance.
Jimenez14,
///
/// For directional and spot lights, this uses a [method by Jorge Jimenez for
/// *Call of Duty: Advanced Warfare*] using 8 samples in spiral pattern,
/// randomly-rotated by interleaved gradient noise with spatial variation.
///
/// [method by Jorge Jimenez for *Call of Duty: Advanced Warfare*]: https://www.iryoku.com/next-generation-post-processing-in-call-of-duty-advanced-warfare/
Temporal,
}

#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
Expand Down
2 changes: 1 addition & 1 deletion crates/bevy_pbr/src/light/point_light.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,6 @@ impl Default for PointLight {
}

impl PointLight {
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.02;
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.08;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
}
8 changes: 4 additions & 4 deletions crates/bevy_pbr/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,11 +618,11 @@ pub fn queue_material_meshes<M: Material>(
ShadowFilteringMethod::Hardware2x2 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
}
ShadowFilteringMethod::Castano13 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13;
ShadowFilteringMethod::Gaussian => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
}
ShadowFilteringMethod::Jimenez14 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14;
ShadowFilteringMethod::Temporal => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
}
}

Expand Down
8 changes: 4 additions & 4 deletions crates/bevy_pbr/src/meshlet/material_draw_prepare.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,11 +113,11 @@ pub fn prepare_material_meshlet_meshes_main_opaque_pass<M: Material>(
ShadowFilteringMethod::Hardware2x2 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
}
ShadowFilteringMethod::Castano13 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13;
ShadowFilteringMethod::Gaussian => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN;
}
ShadowFilteringMethod::Jimenez14 => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14;
ShadowFilteringMethod::Temporal => {
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL;
}
}

Expand Down
12 changes: 6 additions & 6 deletions crates/bevy_pbr/src/render/mesh.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1068,8 +1068,8 @@ bitflags::bitflags! {
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_RESERVED_BITS = Self::SHADOW_FILTER_METHOD_MASK_BITS << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_HARDWARE_2X2 = 0 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_CASTANO_13 = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_JIMENEZ_14 = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_GAUSSIAN = 1 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const SHADOW_FILTER_METHOD_TEMPORAL = 2 << Self::SHADOW_FILTER_METHOD_SHIFT_BITS;
const VIEW_PROJECTION_RESERVED_BITS = Self::VIEW_PROJECTION_MASK_BITS << Self::VIEW_PROJECTION_SHIFT_BITS;
const VIEW_PROJECTION_NONSTANDARD = 0 << Self::VIEW_PROJECTION_SHIFT_BITS;
const VIEW_PROJECTION_PERSPECTIVE = 1 << Self::VIEW_PROJECTION_SHIFT_BITS;
Expand Down Expand Up @@ -1397,10 +1397,10 @@ impl SpecializedMeshPipeline for MeshPipeline {
key.intersection(MeshPipelineKey::SHADOW_FILTER_METHOD_RESERVED_BITS);
if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2 {
shader_defs.push("SHADOW_FILTER_METHOD_HARDWARE_2X2".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13 {
shader_defs.push("SHADOW_FILTER_METHOD_CASTANO_13".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14 {
shader_defs.push("SHADOW_FILTER_METHOD_JIMENEZ_14".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_GAUSSIAN {
shader_defs.push("SHADOW_FILTER_METHOD_GAUSSIAN".into());
} else if shadow_filter_method == MeshPipelineKey::SHADOW_FILTER_METHOD_TEMPORAL {
shader_defs.push("SHADOW_FILTER_METHOD_TEMPORAL".into());
}

let blur_quality =
Expand Down
Loading

0 comments on commit d59b1e7

Please sign in to comment.