From 895b98b7c4e250595647c24b3f878f28397598a2 Mon Sep 17 00:00:00 2001 From: 443eb9#C <443eb9@gmail.com> Date: Tue, 12 Mar 2024 20:31:07 +0800 Subject: [PATCH] Added: alpha map shadow clipping --- RELEASE_DRAFT.md | 4 +++- src/ecs/catalinzz.rs | 2 +- src/render/catalinzz/graph.rs | 2 ++ src/render/catalinzz/mod.rs | 17 +++++++++++---- src/render/catalinzz/pipeline.rs | 14 +++++++++++-- .../shaders/shadow_2d_main_pass.wgsl | 21 +++++++++++++------ .../catalinzz/shaders/shadow_2d_prepass.wgsl | 16 +++++++++++--- 7 files changed, 59 insertions(+), 17 deletions(-) diff --git a/RELEASE_DRAFT.md b/RELEASE_DRAFT.md index 271d0cd..89625ee 100644 --- a/RELEASE_DRAFT.md +++ b/RELEASE_DRAFT.md @@ -4,7 +4,9 @@ - Moved redundant Bevy features into dev-dependencies. - Added `alpha_threshold` to `ShadowMap2dConfig`. - Samples of PCF is no longer limited to 32. -- Added `SpotLight2d` +- Added `SpotLight2d`. +- Added alpha map to clip shadows, which improved shadow accuracy. +- Added `catalinzz` feature. It will be enabled as default. In the future, in order to support more fancy features, there will be more shading approaches like SDF+RayMarching and Ray Tracing, and you can choose according to your needs. # What's Fixed: diff --git a/src/ecs/catalinzz.rs b/src/ecs/catalinzz.rs index 76483d1..ee3f7c1 100644 --- a/src/ecs/catalinzz.rs +++ b/src/ecs/catalinzz.rs @@ -32,7 +32,7 @@ impl Default for ShadowMap2dConfig { far: 1000., size: 512, offset: Vec2::ZERO, - bias: 0., + bias: 0.005, alpha_threshold: 0.9, pcf: Default::default(), } diff --git a/src/render/catalinzz/graph.rs b/src/render/catalinzz/graph.rs index a2a5a87..53e8a03 100644 --- a/src/render/catalinzz/graph.rs +++ b/src/render/catalinzz/graph.rs @@ -144,6 +144,7 @@ impl Node for Shadow2dPrepassNode { &pipeline.prepass_layout, &BindGroupEntries::sequential(( &shadow_view.attachment.texture.default_view, + shadow_map_storage.alpha_map_view(), shadow_map_storage.texture_view_primary(), gpu_meta_buffers.shadow_map_meta_buffer_binding(), )), @@ -402,6 +403,7 @@ impl Node for Shadow2dMainPass { &BindGroupEntries::sequential(( post_process.source, &pipeline.main_texture_sampler, + shadow_map_storage.alpha_map_view(), shadow_map_storage.final_texture_view(), view_uniforms.uniforms.binding().unwrap(), gpu_meta_buffers.shadow_map_meta_buffer_binding(), diff --git a/src/render/catalinzz/mod.rs b/src/render/catalinzz/mod.rs index 9f0ddd2..0f374b6 100644 --- a/src/render/catalinzz/mod.rs +++ b/src/render/catalinzz/mod.rs @@ -66,6 +66,8 @@ pub const SHADOW_MAP_FORMAT: TextureFormat = TextureFormat::Rgba32Float; #[cfg(not(feature = "compatibility"))] pub const SHADOW_MAP_FORMAT: TextureFormat = TextureFormat::Rg32Float; +pub const ALPHA_MAP_FORMAT: TextureFormat = TextureFormat::R32Float; + pub struct CatalinzzApproachPlugin; impl Plugin for CatalinzzApproachPlugin { @@ -303,6 +305,7 @@ pub struct ShadowMap2dStorage { meta: ShadowMap2dMeta, primary_shadow_map: Option, secondary_shadow_map: Option, + alpha_map: Option, work_group_count_per_light: UVec3, work_group_count_total: UVec3, num_reductions: u32, @@ -320,8 +323,9 @@ impl ShadowMap2dStorage { } self.meta = meta; - self.primary_shadow_map = Some(self.create_shadow_map(render_device)); - self.secondary_shadow_map = Some(self.create_shadow_map(render_device)); + self.primary_shadow_map = Some(self.create_shadow_map(render_device, SHADOW_MAP_FORMAT)); + self.secondary_shadow_map = Some(self.create_shadow_map(render_device, SHADOW_MAP_FORMAT)); + self.alpha_map = Some(self.create_shadow_map(render_device, ALPHA_MAP_FORMAT)); self.work_group_count_per_light = UVec3 { x: meta.size.div_ceil(SHADOW_PREPASS_WORKGROUP_SIZE.x), y: meta.size.div_ceil(SHADOW_PREPASS_WORKGROUP_SIZE.y), @@ -353,6 +357,11 @@ impl ShadowMap2dStorage { &self.secondary_shadow_map.as_ref().unwrap().texture_view } + #[inline] + pub fn alpha_map_view(&self) -> &TextureView { + &self.alpha_map.as_ref().unwrap().texture_view + } + #[inline] pub fn final_texture_view(&self) -> &TextureView { if self.num_reductions % 2 == 0 { @@ -377,7 +386,7 @@ impl ShadowMap2dStorage { self.num_reductions } - fn create_shadow_map(&self, render_device: &RenderDevice) -> GpuImage { + fn create_shadow_map(&self, render_device: &RenderDevice, format: TextureFormat) -> GpuImage { let meta = self.meta; let shadow_map = render_device.create_texture(&TextureDescriptor { @@ -390,7 +399,7 @@ impl ShadowMap2dStorage { mip_level_count: 1, sample_count: 1, dimension: TextureDimension::D2, - format: SHADOW_MAP_FORMAT, + format, usage: TextureUsages::STORAGE_BINDING | TextureUsages::TEXTURE_BINDING | TextureUsages::RENDER_ATTACHMENT, diff --git a/src/render/catalinzz/pipeline.rs b/src/render/catalinzz/pipeline.rs index 19f7983..b142436 100644 --- a/src/render/catalinzz/pipeline.rs +++ b/src/render/catalinzz/pipeline.rs @@ -24,8 +24,8 @@ use bevy::render::render_resource::binding_types as binding; use crate::render::light::{GpuAmbientLight2d, GpuPointLight2d}; use super::{ - GpuShadowMapMeta, SHADOW_DISTORT_PASS_SHADER, SHADOW_MAIN_PASS_SHADER, SHADOW_MAP_FORMAT, - SHADOW_PREPASS_SHADER, SHADOW_REDUCTION_PASS_SHADER, + GpuShadowMapMeta, ALPHA_MAP_FORMAT, SHADOW_DISTORT_PASS_SHADER, SHADOW_MAIN_PASS_SHADER, + SHADOW_MAP_FORMAT, SHADOW_PREPASS_SHADER, SHADOW_REDUCTION_PASS_SHADER, }; fn get_shader_defs() -> Vec { @@ -53,6 +53,11 @@ impl FromWorld for Shadow2dPrepassPipeline { ( // Main texture binding::texture_2d(TextureSampleType::Float { filterable: true }), + // Alpha map + binding::texture_storage_2d_array( + ALPHA_MAP_FORMAT, + StorageTextureAccess::WriteOnly, + ), // Shadow map binding::texture_storage_2d_array( SHADOW_MAP_FORMAT, @@ -201,6 +206,11 @@ impl FromWorld for Shadow2dMainPassPipeline { // Main texture binding::texture_2d(TextureSampleType::Float { filterable: true }), binding::sampler(SamplerBindingType::Filtering), + // Alpha map + binding::texture_storage_2d_array( + ALPHA_MAP_FORMAT, + StorageTextureAccess::ReadOnly, + ), // Shadow map binding::texture_storage_2d_array( SHADOW_MAP_FORMAT, diff --git a/src/render/catalinzz/shaders/shadow_2d_main_pass.wgsl b/src/render/catalinzz/shaders/shadow_2d_main_pass.wgsl index 6d892e7..abc8c40 100644 --- a/src/render/catalinzz/shaders/shadow_2d_main_pass.wgsl +++ b/src/render/catalinzz/shaders/shadow_2d_main_pass.wgsl @@ -13,6 +13,9 @@ var main_tex: texture_2d; var main_tex_sampler: sampler; @group(0) @binding(2) +var alpha_map: texture_storage_2d_array; + +@group(0) @binding(3) var shadow_map: texture_storage_2d_array< #ifdef COMPATIBILITY rgba32float, @@ -22,19 +25,19 @@ var shadow_map: texture_storage_2d_array< read >; -@group(0) @binding(3) +@group(0) @binding(4) var main_view: View; -@group(0) @binding(4) +@group(0) @binding(5) var shadow_map_meta: ShadowMapMeta; -@group(0) @binding(5) +@group(0) @binding(6) var ambient_light: AmbientLight2d; -@group(0) @binding(6) +@group(0) @binding(7) var poisson_disk: array; -@group(0) @binding(7) +@group(0) @binding(8) var point_lights: array; fn get_caster_distance_h(rel_ss: vec2f, i_light: u32) -> f32 { @@ -63,7 +66,8 @@ fn pcf(rel_ss: vec2f, sample_count: u32, sample_radius: f32, i_light: u32) -> f3 let sample_ss = rel_ss + poisson_disk[i] * sample_radius; let dist = get_caster_distance(sample_ss, i_light); - if dist > length(sample_ss) + shadow_map_meta.bias { + if dist > length(sample_ss) - shadow_map_meta.bias + && get_alpha(sample_ss, i_light) < shadow_map_meta.alpha_threshold { visibility += 1.; } } @@ -71,6 +75,11 @@ fn pcf(rel_ss: vec2f, sample_count: u32, sample_radius: f32, i_light: u32) -> f3 return visibility; } +fn get_alpha(rel_ss: vec2f, i_light: u32) -> f32 { + let px = (rel_ss / 2. + 1.) / 2. * f32(shadow_map_meta.size); + return textureLoad(alpha_map, vec2i(px), i_light).r; +} + @fragment fn dbg_output_shadow_map(in: FullscreenVertexOutput) -> @location(0) vec4f { return textureLoad(shadow_map, vec2u(in.uv * vec2f(shadow_map_meta.size)), 0); diff --git a/src/render/catalinzz/shaders/shadow_2d_prepass.wgsl b/src/render/catalinzz/shaders/shadow_2d_prepass.wgsl index c032d07..d4a1f52 100644 --- a/src/render/catalinzz/shaders/shadow_2d_prepass.wgsl +++ b/src/render/catalinzz/shaders/shadow_2d_prepass.wgsl @@ -4,6 +4,9 @@ var main_tex: texture_2d; @group(0) @binding(1) +var alpha_map: texture_storage_2d_array; + +@group(0) @binding(2) var shadow_map: texture_storage_2d_array< #ifdef COMPATIBILITY rgba32float, @@ -13,7 +16,7 @@ var shadow_map: texture_storage_2d_array< write >; -@group(0) @binding(2) +@group(0) @binding(3) var shadow_map_meta: ShadowMapMeta; @compute @workgroup_size(16, 16, 1) @@ -25,13 +28,20 @@ fn main(@builtin(global_invocation_id) invocation_id: vec3u) { } var d = 1.; - if textureLoad(main_tex, px, 0).a > shadow_map_meta.alpha_threshold { + let alpha = textureLoad(main_tex, px, 0).a; + if alpha > shadow_map_meta.alpha_threshold { d = length(vec2f(px) / vec2f(f32(shadow_map_meta.size)) - vec2f(0.5)) * 2.; + textureStore( + alpha_map, + px, + shadow_map_meta.index, + vec4f(alpha, 0., 0., 0.), + ); } textureStore( shadow_map, - vec2u(px.x, px.y), + px, shadow_map_meta.index, vec4f(d, 0., 0., 0.), );