From ff06bd55f3cf26d03ed96fcc12397d14fd1d68c6 Mon Sep 17 00:00:00 2001 From: SeanOMik Date: Sun, 14 Jul 2024 19:06:38 -0400 Subject: [PATCH] render: simple PCF --- examples/shadows/src/main.rs | 4 +- lyra-game/src/render/graph/passes/meshes.rs | 2 +- lyra-game/src/render/graph/passes/shadows.rs | 1 + lyra-game/src/render/material.rs | 2 +- lyra-game/src/render/shaders/base.wgsl | 98 +++++++++++--------- 5 files changed, 59 insertions(+), 48 deletions(-) diff --git a/examples/shadows/src/main.rs b/examples/shadows/src/main.rs index 49aa82a..5cef2a6 100644 --- a/examples/shadows/src/main.rs +++ b/examples/shadows/src/main.rs @@ -166,7 +166,7 @@ fn setup_scene_plugin(game: &mut Game) { light_tran, )); - world.spawn(( + /* world.spawn(( //cube_mesh.clone(), PointLight { enabled: true, @@ -188,7 +188,7 @@ fn setup_scene_plugin(game: &mut Game) { ..Default::default() }, Transform::from_xyz(-0.5, 2.0, -5.0), - )); + )); */ } let mut camera = CameraComponent::new_3d(); diff --git a/lyra-game/src/render/graph/passes/meshes.rs b/lyra-game/src/render/graph/passes/meshes.rs index 981e7c9..c0a315f 100644 --- a/lyra-game/src/render/graph/passes/meshes.rs +++ b/lyra-game/src/render/graph/passes/meshes.rs @@ -142,7 +142,7 @@ impl Node for MeshPass { wgpu::BindGroupLayoutEntry { binding: 1, visibility: wgpu::ShaderStages::FRAGMENT, - ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), + ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison), count: None, }, wgpu::BindGroupLayoutEntry { diff --git a/lyra-game/src/render/graph/passes/shadows.rs b/lyra-game/src/render/graph/passes/shadows.rs index f11b8c1..15621bf 100644 --- a/lyra-game/src/render/graph/passes/shadows.rs +++ b/lyra-game/src/render/graph/passes/shadows.rs @@ -114,6 +114,7 @@ impl ShadowMapsPass { min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::FilterMode::Linear, border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite), + compare: Some(wgpu::CompareFunction::LessEqual), ..Default::default() }); diff --git a/lyra-game/src/render/material.rs b/lyra-game/src/render/material.rs index cdbccbe..9fafa40 100755 --- a/lyra-game/src/render/material.rs +++ b/lyra-game/src/render/material.rs @@ -58,7 +58,7 @@ impl Material { //diffuse: glam::Vec3::new(value.base_color.x, value.base_color.y, value.base_color.z), //diffuse: glam::Vec3::new(1.0, 0.5, 0.31), //specular: glam::Vec3::new(0.5, 0.5, 0.5), - ambient: glam::Vec3::new(1.0, 1.0, 1.0), + ambient: glam::Vec3::new(1.0, 1.0, 1.0) * 0.5, diffuse: glam::Vec3::new(1.0, 1.0, 1.0), shininess: 32.0, diff --git a/lyra-game/src/render/shaders/base.wgsl b/lyra-game/src/render/shaders/base.wgsl index d6f15db..1862ba0 100755 --- a/lyra-game/src/render/shaders/base.wgsl +++ b/lyra-game/src/render/shaders/base.wgsl @@ -8,6 +8,8 @@ const LIGHT_TY_SPOT = 2u; const ALPHA_CUTOFF = 0.1; +const SHADOW_MAP_PCF_SIZE = 4.0; + struct VertexInput { @location(0) position: vec3, @location(1) tex_coords: vec2, @@ -128,7 +130,7 @@ var t_light_grid: texture_storage_2d; // rg32uint = vec2 u_shadow_maps_atlas_size: vec2; @group(5) @binding(3) @@ -165,7 +167,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]]; let frag_pos_light_space = shadow_u.light_space_matrix * vec4(in.world_position, 1.0); - let shadow = calc_shadow_dir_light(in.world_normal, light_dir, frag_pos_light_space, atlas_dimensions, shadow_u.atlas_frame); + let shadow = calc_shadow_dir_light(in.world_normal, light_dir, frag_pos_light_space, atlas_dimensions, shadow_u); light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color, shadow); } else if (light.light_ty == LIGHT_TY_POINT) { let shadow = calc_shadow_point(in.world_position, in.world_normal, light_dir, light, atlas_dimensions); @@ -240,45 +242,60 @@ fn get_side_idx(tex_coord: vec3) -> vec3 { return vec3(res, f32(cube_idx)); } -fn calc_shadow_dir_light(normal: vec3, light_dir: vec3, frag_pos_light_space: vec4, atlas_dimensions: vec2, atlas_region: TextureAtlasFrame) -> f32 { +fn calc_shadow_dir_light(normal: vec3, light_dir: vec3, frag_pos_light_space: vec4, atlas_dimensions: vec2, shadow_u: LightShadowMapUniform) -> f32 { var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w; // for some reason the y component is flipped after transforming proj_coords.y = -proj_coords.y; - // dont cast shadows outside the light's far plane - if (proj_coords.z > 1.0) { - return 0.0; - } - // Remap xy to [0.0, 1.0] let xy_remapped = proj_coords.xy * 0.5 + 0.5; - // no need to get the y since the maps are square - let atlas_start = f32(atlas_region.x) / f32(atlas_dimensions.x); - let atlas_end = f32(atlas_region.x + atlas_region.width) / f32(atlas_dimensions.x); - // lerp the tex coords to the shadow map for this light. - proj_coords.x = mix(atlas_start, atlas_end, xy_remapped.x); - proj_coords.y = mix(atlas_start, atlas_end, xy_remapped.y); - - // simulate `ClampToBorder`, not creating shadows past the shadow map regions - if (proj_coords.x > atlas_end && proj_coords.y > atlas_end) - || (proj_coords.x < atlas_start && proj_coords.y < atlas_start) { - return 0.0; - } - - // must manually apply offset to the texture coords since `textureSampleLevel` requires a - // const value. - let offset_coords = proj_coords.xy + (vec2(f32(atlas_region.x), f32(atlas_region.y)) / vec2(atlas_dimensions)); - let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, offset_coords, 0.0); - let current_depth = proj_coords.z; - + // get the atlas frame in [0; 1] in the atlas texture + // z is width, w is height + var region_rect = vec4(f32(shadow_u.atlas_frame.x), f32(shadow_u.atlas_frame.y), f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height)); + region_rect /= f32(atlas_dimensions.x); + let region_coords = vec2( + mix(region_rect.x, region_rect.x + region_rect.z, xy_remapped.x), + mix(region_rect.y, region_rect.y + region_rect.w, xy_remapped.y) + ); + // use a bias to avoid shadow acne let bias = max(0.05 * (1.0 - dot(normal, light_dir)), 0.005); - var shadow = 0.0; - if current_depth - bias > closest_depth { + let current_depth = proj_coords.z - bias; + + var shadow = pcf_dir_light(region_coords, current_depth, shadow_u); + + // dont cast shadows outside the light's far plane + if (proj_coords.z > 1.0) { shadow = 1.0; } + // dont cast shadows if the texture coords would go past the shadow maps + if (xy_remapped.x > 1.0 || xy_remapped.x < 0.0 || xy_remapped.y > 1.0 || xy_remapped.y < 0.0) { + shadow = 1.0; + } + + return shadow; +} + +/// Calculate the shadow coefficient using PCF of a directional light +fn pcf_dir_light(tex_coords: vec2, test_depth: f32, shadow_u: LightShadowMapUniform) -> f32 { + let half_filter_size = SHADOW_MAP_PCF_SIZE / 2.0; + let texel_size = 1.0 / vec2(f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height)); + + // Sample PCF + var shadow = 0.0; + for (var x = -half_filter_size; x <= half_filter_size; x += 1.0) { + for (var y = -half_filter_size; y <= half_filter_size; y += 1.0) { + let offset = tex_coords + vec2(x, y) * texel_size; + let pcf_depth = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas, offset, test_depth); + shadow += pcf_depth; + } + } + shadow /= pow(SHADOW_MAP_PCF_SIZE, 2.0); + // ensure the shadow value does not go above 1.0 + shadow = min(shadow, 1.0); + return shadow; } @@ -289,9 +306,9 @@ fn calc_shadow_point(world_pos: vec3, world_normal: vec3, light_dir: v let cube_idx = i32(temp.z); /// if an unknown cube side was returned, something is broken - if cube_idx == 0 { + /*if cube_idx == 0 { return 0.0; - } + }*/ var indices = light.light_shadow_uniform_index; let i = indices[cube_idx - 1]; @@ -303,27 +320,20 @@ fn calc_shadow_point(world_pos: vec3, world_normal: vec3, light_dir: v region_coords /= f32(atlas_dimensions.x); // simulate `ClampToBorder`, not creating shadows past the shadow map regions - if (coords_2d.x >= 1.0 || coords_2d.y >= 1.0) { + /*if (coords_2d.x >= 1.0 || coords_2d.y >= 1.0) { return 0.0; - } + }*/ // get the coords inside of the region coords_2d.x = mix(region_coords.x, region_coords.x + region_coords.z, coords_2d.x); coords_2d.y = mix(region_coords.y, region_coords.y + region_coords.w, coords_2d.y); - var closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, coords_2d, 0.0); - let current_depth = length(frag_to_light); - - // convert depth from [0; 1] to the original depth value - closest_depth *= u.far_plane; - // use a bias to avoid shadow acne let bias = max(0.05 * (1.0 - dot(world_normal, light_dir)), 0.005); + var current_depth = length(frag_to_light) - bias; + current_depth /= u.far_plane; - var shadow = 0.0; - if current_depth - bias > closest_depth { - shadow = 1.0; - } + var shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas, coords_2d, current_depth); return shadow; } @@ -369,7 +379,7 @@ fn blinn_phong_dir_light(world_pos: vec3, world_norm: vec3, dir_light: diffuse_color *= dir_light.diffuse; specular_color *= dir_light.specular;*/ - return (ambient_color + (1.0 - shadow) * (diffuse_color + specular_color)) * dir_light.intensity; + return (ambient_color + (shadow) * (diffuse_color + specular_color)) * dir_light.intensity; } fn blinn_phong_point_light(world_pos: vec3, world_norm: vec3, point_light: Light, material: Material, specular_factor: vec3, shadow: f32) -> vec3 {