PCF For DirectionalLight/SpotLight Shadows (#8006)
# 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>
This commit is contained in:
parent
154a490445
commit
1f95a484ed
@ -55,10 +55,10 @@ use bevy_app::prelude::*;
|
|||||||
use bevy_asset::{load_internal_asset, AssetApp, Assets, Handle};
|
use bevy_asset::{load_internal_asset, AssetApp, Assets, Handle};
|
||||||
use bevy_ecs::prelude::*;
|
use bevy_ecs::prelude::*;
|
||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::CameraUpdateSystem, extract_resource::ExtractResourcePlugin, prelude::Color,
|
camera::CameraUpdateSystem, extract_component::ExtractComponentPlugin,
|
||||||
render_asset::prepare_assets, render_graph::RenderGraph, render_phase::sort_phase_system,
|
extract_resource::ExtractResourcePlugin, prelude::Color, render_asset::prepare_assets,
|
||||||
render_resource::Shader, texture::Image, view::VisibilitySystems, ExtractSchedule, Render,
|
render_graph::RenderGraph, render_phase::sort_phase_system, render_resource::Shader,
|
||||||
RenderApp, RenderSet,
|
texture::Image, view::VisibilitySystems, ExtractSchedule, Render, RenderApp, RenderSet,
|
||||||
};
|
};
|
||||||
use bevy_transform::TransformSystem;
|
use bevy_transform::TransformSystem;
|
||||||
use environment_map::EnvironmentMapPlugin;
|
use environment_map::EnvironmentMapPlugin;
|
||||||
@ -69,6 +69,7 @@ pub const UTILS_HANDLE: Handle<Shader> = Handle::weak_from_u128(1900548483293416
|
|||||||
pub const CLUSTERED_FORWARD_HANDLE: Handle<Shader> = Handle::weak_from_u128(166852093121196815);
|
pub const CLUSTERED_FORWARD_HANDLE: Handle<Shader> = Handle::weak_from_u128(166852093121196815);
|
||||||
pub const PBR_LIGHTING_HANDLE: Handle<Shader> = Handle::weak_from_u128(14170772752254856967);
|
pub const PBR_LIGHTING_HANDLE: Handle<Shader> = Handle::weak_from_u128(14170772752254856967);
|
||||||
pub const SHADOWS_HANDLE: Handle<Shader> = Handle::weak_from_u128(11350275143789590502);
|
pub const SHADOWS_HANDLE: Handle<Shader> = Handle::weak_from_u128(11350275143789590502);
|
||||||
|
pub const SHADOW_SAMPLING_HANDLE: Handle<Shader> = Handle::weak_from_u128(3145627513789590502);
|
||||||
pub const PBR_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4805239651767701046);
|
pub const PBR_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(4805239651767701046);
|
||||||
pub const PBR_PREPASS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(9407115064344201137);
|
pub const PBR_PREPASS_SHADER_HANDLE: Handle<Shader> = Handle::weak_from_u128(9407115064344201137);
|
||||||
pub const PBR_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(16550102964439850292);
|
pub const PBR_FUNCTIONS_HANDLE: Handle<Shader> = Handle::weak_from_u128(16550102964439850292);
|
||||||
@ -124,6 +125,12 @@ impl Plugin for PbrPlugin {
|
|||||||
"render/shadows.wgsl",
|
"render/shadows.wgsl",
|
||||||
Shader::from_wgsl
|
Shader::from_wgsl
|
||||||
);
|
);
|
||||||
|
load_internal_asset!(
|
||||||
|
app,
|
||||||
|
SHADOW_SAMPLING_HANDLE,
|
||||||
|
"render/shadow_sampling.wgsl",
|
||||||
|
Shader::from_wgsl
|
||||||
|
);
|
||||||
load_internal_asset!(
|
load_internal_asset!(
|
||||||
app,
|
app,
|
||||||
PBR_FUNCTIONS_HANDLE,
|
PBR_FUNCTIONS_HANDLE,
|
||||||
@ -168,6 +175,7 @@ impl Plugin for PbrPlugin {
|
|||||||
.register_type::<PointLight>()
|
.register_type::<PointLight>()
|
||||||
.register_type::<PointLightShadowMap>()
|
.register_type::<PointLightShadowMap>()
|
||||||
.register_type::<SpotLight>()
|
.register_type::<SpotLight>()
|
||||||
|
.register_type::<ShadowFilteringMethod>()
|
||||||
.init_resource::<AmbientLight>()
|
.init_resource::<AmbientLight>()
|
||||||
.init_resource::<GlobalVisiblePointLights>()
|
.init_resource::<GlobalVisiblePointLights>()
|
||||||
.init_resource::<DirectionalLightShadowMap>()
|
.init_resource::<DirectionalLightShadowMap>()
|
||||||
@ -182,6 +190,7 @@ impl Plugin for PbrPlugin {
|
|||||||
EnvironmentMapPlugin,
|
EnvironmentMapPlugin,
|
||||||
ExtractResourcePlugin::<AmbientLight>::default(),
|
ExtractResourcePlugin::<AmbientLight>::default(),
|
||||||
FogPlugin,
|
FogPlugin,
|
||||||
|
ExtractComponentPlugin::<ShadowFilteringMethod>::default(),
|
||||||
))
|
))
|
||||||
.configure_sets(
|
.configure_sets(
|
||||||
PostUpdate,
|
PostUpdate,
|
||||||
|
@ -6,6 +6,7 @@ use bevy_reflect::prelude::*;
|
|||||||
use bevy_render::{
|
use bevy_render::{
|
||||||
camera::Camera,
|
camera::Camera,
|
||||||
color::Color,
|
color::Color,
|
||||||
|
extract_component::ExtractComponent,
|
||||||
extract_resource::ExtractResource,
|
extract_resource::ExtractResource,
|
||||||
prelude::Projection,
|
prelude::Projection,
|
||||||
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
|
primitives::{Aabb, CascadesFrusta, CubemapFrusta, Frustum, HalfSpace, Sphere},
|
||||||
@ -606,6 +607,36 @@ pub struct NotShadowCaster;
|
|||||||
#[reflect(Component, Default)]
|
#[reflect(Component, Default)]
|
||||||
pub struct NotShadowReceiver;
|
pub struct NotShadowReceiver;
|
||||||
|
|
||||||
|
/// Add this component to a [`Camera3d`](bevy_core_pipeline::core_3d::Camera3d)
|
||||||
|
/// to control how to anti-alias shadow edges.
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// Good quality, good performance.
|
||||||
|
#[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.
|
||||||
|
///
|
||||||
|
/// Good quality when used with
|
||||||
|
/// [`TemporalAntiAliasSettings`](bevy_core_pipeline::experimental::taa::TemporalAntiAliasSettings)
|
||||||
|
/// and good performance.
|
||||||
|
Jimenez14,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemSet)]
|
||||||
pub enum SimulationLightSystems {
|
pub enum SimulationLightSystems {
|
||||||
AddClusters,
|
AddClusters,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
render, AlphaMode, DrawMesh, DrawPrepass, EnvironmentMapLight, MeshPipeline, MeshPipelineKey,
|
render, AlphaMode, DrawMesh, DrawPrepass, EnvironmentMapLight, MeshPipeline, MeshPipelineKey,
|
||||||
PrepassPipelinePlugin, PrepassPlugin, RenderMeshInstances, ScreenSpaceAmbientOcclusionSettings,
|
PrepassPipelinePlugin, PrepassPlugin, RenderMeshInstances, ScreenSpaceAmbientOcclusionSettings,
|
||||||
SetMeshBindGroup, SetMeshViewBindGroup, Shadow,
|
SetMeshBindGroup, SetMeshViewBindGroup, Shadow, ShadowFilteringMethod,
|
||||||
};
|
};
|
||||||
use bevy_app::{App, Plugin};
|
use bevy_app::{App, Plugin};
|
||||||
use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle};
|
use bevy_asset::{Asset, AssetApp, AssetEvent, AssetId, AssetServer, Assets, Handle};
|
||||||
@ -440,6 +440,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
Option<&Tonemapping>,
|
Option<&Tonemapping>,
|
||||||
Option<&DebandDither>,
|
Option<&DebandDither>,
|
||||||
Option<&EnvironmentMapLight>,
|
Option<&EnvironmentMapLight>,
|
||||||
|
Option<&ShadowFilteringMethod>,
|
||||||
Option<&ScreenSpaceAmbientOcclusionSettings>,
|
Option<&ScreenSpaceAmbientOcclusionSettings>,
|
||||||
Option<&NormalPrepass>,
|
Option<&NormalPrepass>,
|
||||||
Option<&TemporalAntiAliasSettings>,
|
Option<&TemporalAntiAliasSettings>,
|
||||||
@ -456,6 +457,7 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
tonemapping,
|
tonemapping,
|
||||||
dither,
|
dither,
|
||||||
environment_map,
|
environment_map,
|
||||||
|
shadow_filter_method,
|
||||||
ssao,
|
ssao,
|
||||||
normal_prepass,
|
normal_prepass,
|
||||||
taa_settings,
|
taa_settings,
|
||||||
@ -482,6 +484,19 @@ pub fn queue_material_meshes<M: Material>(
|
|||||||
if environment_map_loaded {
|
if environment_map_loaded {
|
||||||
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
|
view_key |= MeshPipelineKey::ENVIRONMENT_MAP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match shadow_filter_method.unwrap_or(&ShadowFilteringMethod::default()) {
|
||||||
|
ShadowFilteringMethod::Hardware2x2 => {
|
||||||
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_HARDWARE_2X2;
|
||||||
|
}
|
||||||
|
ShadowFilteringMethod::Castano13 => {
|
||||||
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_CASTANO_13;
|
||||||
|
}
|
||||||
|
ShadowFilteringMethod::Jimenez14 => {
|
||||||
|
view_key |= MeshPipelineKey::SHADOW_FILTER_METHOD_JIMENEZ_14;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !view.hdr {
|
if !view.hdr {
|
||||||
if let Some(tonemapping) = tonemapping {
|
if let Some(tonemapping) = tonemapping {
|
||||||
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
|
view_key |= MeshPipelineKey::TONEMAP_IN_SHADER;
|
||||||
|
@ -659,24 +659,35 @@ bitflags::bitflags! {
|
|||||||
const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
const TONEMAP_METHOD_ACES_FITTED = 3 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||||
const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
const TONEMAP_METHOD_AGX = 4 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||||
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
const TONEMAP_METHOD_SOMEWHAT_BORING_DISPLAY_TRANSFORM = 5 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||||
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
const TONEMAP_METHOD_TONY_MC_MAPFACE = 6 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
||||||
const TONEMAP_METHOD_BLENDER_FILMIC = 7 << Self::TONEMAP_METHOD_SHIFT_BITS;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MeshPipelineKey {
|
impl MeshPipelineKey {
|
||||||
const MSAA_MASK_BITS: u32 = 0b111;
|
const MSAA_MASK_BITS: u32 = 0b111;
|
||||||
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
const MSAA_SHIFT_BITS: u32 = 32 - Self::MSAA_MASK_BITS.count_ones();
|
||||||
|
|
||||||
const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;
|
const PRIMITIVE_TOPOLOGY_MASK_BITS: u32 = 0b111;
|
||||||
const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 =
|
const PRIMITIVE_TOPOLOGY_SHIFT_BITS: u32 =
|
||||||
Self::MSAA_SHIFT_BITS - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones();
|
Self::MSAA_SHIFT_BITS - Self::PRIMITIVE_TOPOLOGY_MASK_BITS.count_ones();
|
||||||
|
|
||||||
const BLEND_MASK_BITS: u32 = 0b11;
|
const BLEND_MASK_BITS: u32 = 0b11;
|
||||||
const BLEND_SHIFT_BITS: u32 =
|
const BLEND_SHIFT_BITS: u32 =
|
||||||
Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::BLEND_MASK_BITS.count_ones();
|
Self::PRIMITIVE_TOPOLOGY_SHIFT_BITS - Self::BLEND_MASK_BITS.count_ones();
|
||||||
|
|
||||||
const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;
|
const TONEMAP_METHOD_MASK_BITS: u32 = 0b111;
|
||||||
const TONEMAP_METHOD_SHIFT_BITS: u32 =
|
const TONEMAP_METHOD_SHIFT_BITS: u32 =
|
||||||
Self::BLEND_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();
|
Self::BLEND_SHIFT_BITS - Self::TONEMAP_METHOD_MASK_BITS.count_ones();
|
||||||
|
|
||||||
|
const SHADOW_FILTER_METHOD_MASK_BITS: u32 = 0b11;
|
||||||
|
const SHADOW_FILTER_METHOD_SHIFT_BITS: u32 =
|
||||||
|
Self::TONEMAP_METHOD_SHIFT_BITS - Self::SHADOW_FILTER_METHOD_MASK_BITS.count_ones();
|
||||||
|
|
||||||
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
|
pub fn from_msaa_samples(msaa_samples: u32) -> Self {
|
||||||
let msaa_bits =
|
let msaa_bits =
|
||||||
(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
|
(msaa_samples.trailing_zeros() & Self::MSAA_MASK_BITS) << Self::MSAA_SHIFT_BITS;
|
||||||
@ -904,6 +915,16 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
|||||||
shader_defs.push("TAA".into());
|
shader_defs.push("TAA".into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let shadow_filter_method =
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
let format = if key.contains(MeshPipelineKey::HDR) {
|
let format = if key.contains(MeshPipelineKey::HDR) {
|
||||||
ViewTarget::TEXTURE_FORMAT_HDR
|
ViewTarget::TEXTURE_FORMAT_HDR
|
||||||
} else {
|
} else {
|
||||||
@ -1069,10 +1090,12 @@ pub fn prepare_mesh_view_bind_groups(
|
|||||||
Option<&EnvironmentMapLight>,
|
Option<&EnvironmentMapLight>,
|
||||||
&Tonemapping,
|
&Tonemapping,
|
||||||
)>,
|
)>,
|
||||||
images: Res<RenderAssets<Image>>,
|
(images, mut fallback_images, mut fallback_depths, fallback_cubemap): (
|
||||||
mut fallback_images: FallbackImagesMsaa,
|
Res<RenderAssets<Image>>,
|
||||||
mut fallback_depths: FallbackImagesDepth,
|
FallbackImagesMsaa,
|
||||||
fallback_cubemap: Res<FallbackImageCubemap>,
|
FallbackImagesDepth,
|
||||||
|
Res<FallbackImageCubemap>,
|
||||||
|
),
|
||||||
msaa: Res<Msaa>,
|
msaa: Res<Msaa>,
|
||||||
globals_buffer: Res<GlobalsBuffer>,
|
globals_buffer: Res<GlobalsBuffer>,
|
||||||
tonemapping_luts: Res<TonemappingLuts>,
|
tonemapping_luts: Res<TonemappingLuts>,
|
||||||
|
132
crates/bevy_pbr/src/render/shadow_sampling.wgsl
Normal file
132
crates/bevy_pbr/src/render/shadow_sampling.wgsl
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
#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
|
||||||
|
}
|
@ -3,6 +3,8 @@
|
|||||||
#import bevy_pbr::mesh_view_types POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE
|
#import bevy_pbr::mesh_view_types POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE
|
||||||
#import bevy_pbr::mesh_view_bindings as view_bindings
|
#import bevy_pbr::mesh_view_bindings as view_bindings
|
||||||
#import bevy_pbr::utils hsv2rgb
|
#import bevy_pbr::utils hsv2rgb
|
||||||
|
#import bevy_pbr::shadow_sampling sample_shadow_map
|
||||||
|
|
||||||
const flip_z: vec3<f32> = vec3<f32>(1.0, 1.0, -1.0);
|
const flip_z: vec3<f32> = vec3<f32>(1.0, 1.0, -1.0);
|
||||||
|
|
||||||
fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
|
fn fetch_point_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
|
||||||
@ -95,13 +97,9 @@ fn fetch_spot_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: ve
|
|||||||
// 0.1 must match POINT_LIGHT_NEAR_Z
|
// 0.1 must match POINT_LIGHT_NEAR_Z
|
||||||
let depth = 0.1 / -projected_position.z;
|
let depth = 0.1 / -projected_position.z;
|
||||||
|
|
||||||
#ifdef NO_ARRAY_TEXTURES_SUPPORT
|
// Number determined by trial and error that gave nice results.
|
||||||
return textureSampleCompare(view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler,
|
let texel_size = 0.0134277345;
|
||||||
shadow_uv, depth);
|
return sample_shadow_map(shadow_uv, depth, i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset, texel_size);
|
||||||
#else
|
|
||||||
return textureSampleCompareLevel(view_bindings::directional_shadow_textures, view_bindings::directional_shadow_textures_sampler,
|
|
||||||
shadow_uv, i32(light_id) + view_bindings::lights.spot_light_shadowmap_offset, depth);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_cascade_index(light_id: u32, view_z: f32) -> u32 {
|
fn get_cascade_index(light_id: u32, view_z: f32) -> u32 {
|
||||||
@ -115,7 +113,7 @@ fn get_cascade_index(light_id: u32, view_z: f32) -> u32 {
|
|||||||
return (*light).num_cascades;
|
return (*light).num_cascades;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
|
fn sample_directional_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>) -> f32 {
|
||||||
let light = &view_bindings::lights.directional_lights[light_id];
|
let light = &view_bindings::lights.directional_lights[light_id];
|
||||||
let cascade = &(*light).cascades[cascade_index];
|
let cascade = &(*light).cascades[cascade_index];
|
||||||
|
|
||||||
@ -141,25 +139,9 @@ fn sample_cascade(light_id: u32, cascade_index: u32, frag_position: vec4<f32>, s
|
|||||||
let light_local = offset_position_ndc.xy * flip_correction + vec2<f32>(0.5, 0.5);
|
let light_local = offset_position_ndc.xy * flip_correction + vec2<f32>(0.5, 0.5);
|
||||||
|
|
||||||
let depth = offset_position_ndc.z;
|
let depth = offset_position_ndc.z;
|
||||||
// do the lookup, using HW PCF and comparison
|
|
||||||
// NOTE: Due to non-uniform control flow above, we must use the level variant of the texture
|
let array_index = i32((*light).depth_texture_base_index + cascade_index);
|
||||||
// sampler to avoid use of implicit derivatives causing possible undefined behavior.
|
return sample_shadow_map(light_local, depth, array_index, (*cascade).texel_size);
|
||||||
#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,
|
|
||||||
i32((*light).depth_texture_base_index + cascade_index),
|
|
||||||
depth
|
|
||||||
);
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>, view_z: f32) -> f32 {
|
fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_normal: vec3<f32>, view_z: f32) -> f32 {
|
||||||
@ -170,7 +152,7 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
|
|||||||
return 1.0;
|
return 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
var shadow = sample_cascade(light_id, cascade_index, frag_position, surface_normal);
|
var shadow = sample_directional_cascade(light_id, cascade_index, frag_position, surface_normal);
|
||||||
|
|
||||||
// Blend with the next cascade, if there is one.
|
// Blend with the next cascade, if there is one.
|
||||||
let next_cascade_index = cascade_index + 1u;
|
let next_cascade_index = cascade_index + 1u;
|
||||||
@ -178,7 +160,7 @@ fn fetch_directional_shadow(light_id: u32, frag_position: vec4<f32>, surface_nor
|
|||||||
let this_far_bound = (*light).cascades[cascade_index].far_bound;
|
let this_far_bound = (*light).cascades[cascade_index].far_bound;
|
||||||
let next_near_bound = (1.0 - (*light).cascades_overlap_proportion) * this_far_bound;
|
let next_near_bound = (1.0 - (*light).cascades_overlap_proportion) * this_far_bound;
|
||||||
if (-view_z >= next_near_bound) {
|
if (-view_z >= next_near_bound) {
|
||||||
let next_shadow = sample_cascade(light_id, next_cascade_index, frag_position, surface_normal);
|
let next_shadow = sample_directional_cascade(light_id, next_cascade_index, frag_position, surface_normal);
|
||||||
shadow = mix(shadow, next_shadow, (-view_z - next_near_bound) / (this_far_bound - next_near_bound));
|
shadow = mix(shadow, next_shadow, (-view_z - next_near_bound) / (this_far_bound - next_near_bound));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user