Implement Shadows #24

Merged
SeanOMik merged 28 commits from feat/shadow-maps into main 2024-08-10 03:10:30 +00:00
5 changed files with 59 additions and 48 deletions
Showing only changes of commit ff06bd55f3 - Show all commits

View File

@ -166,7 +166,7 @@ fn setup_scene_plugin(game: &mut Game) {
light_tran, light_tran,
)); ));
world.spawn(( /* world.spawn((
//cube_mesh.clone(), //cube_mesh.clone(),
PointLight { PointLight {
enabled: true, enabled: true,
@ -188,7 +188,7 @@ fn setup_scene_plugin(game: &mut Game) {
..Default::default() ..Default::default()
}, },
Transform::from_xyz(-0.5, 2.0, -5.0), Transform::from_xyz(-0.5, 2.0, -5.0),
)); )); */
} }
let mut camera = CameraComponent::new_3d(); let mut camera = CameraComponent::new_3d();

View File

@ -142,7 +142,7 @@ impl Node for MeshPass {
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {
binding: 1, binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT, visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering), ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Comparison),
count: None, count: None,
}, },
wgpu::BindGroupLayoutEntry { wgpu::BindGroupLayoutEntry {

View File

@ -114,6 +114,7 @@ impl ShadowMapsPass {
min_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear,
mipmap_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::FilterMode::Linear,
border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite), border_color: Some(wgpu::SamplerBorderColor::OpaqueWhite),
compare: Some(wgpu::CompareFunction::LessEqual),
..Default::default() ..Default::default()
}); });

View File

@ -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(value.base_color.x, value.base_color.y, value.base_color.z),
//diffuse: glam::Vec3::new(1.0, 0.5, 0.31), //diffuse: glam::Vec3::new(1.0, 0.5, 0.31),
//specular: glam::Vec3::new(0.5, 0.5, 0.5), //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), diffuse: glam::Vec3::new(1.0, 1.0, 1.0),
shininess: 32.0, shininess: 32.0,

View File

@ -8,6 +8,8 @@ const LIGHT_TY_SPOT = 2u;
const ALPHA_CUTOFF = 0.1; const ALPHA_CUTOFF = 0.1;
const SHADOW_MAP_PCF_SIZE = 4.0;
struct VertexInput { struct VertexInput {
@location(0) position: vec3<f32>, @location(0) position: vec3<f32>,
@location(1) tex_coords: vec2<f32>, @location(1) tex_coords: vec2<f32>,
@ -128,7 +130,7 @@ var t_light_grid: texture_storage_2d<rg32uint, read_write>; // rg32uint = vec2<u
@group(5) @binding(0) @group(5) @binding(0)
var t_shadow_maps_atlas: texture_depth_2d; var t_shadow_maps_atlas: texture_depth_2d;
@group(5) @binding(1) @group(5) @binding(1)
var s_shadow_maps_atlas: sampler; var s_shadow_maps_atlas: sampler_comparison;
@group(5) @binding(2) @group(5) @binding(2)
var<uniform> u_shadow_maps_atlas_size: vec2<u32>; var<uniform> u_shadow_maps_atlas_size: vec2<u32>;
@group(5) @binding(3) @group(5) @binding(3)
@ -165,7 +167,7 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]]; let shadow_u: LightShadowMapUniform = u_light_shadow[light.light_shadow_uniform_index[0]];
let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(in.world_position, 1.0); let frag_pos_light_space = shadow_u.light_space_matrix * vec4<f32>(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); 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) { } else if (light.light_ty == LIGHT_TY_POINT) {
let shadow = calc_shadow_point(in.world_position, in.world_normal, light_dir, light, atlas_dimensions); 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<f32>) -> vec3<f32> {
return vec3<f32>(res, f32(cube_idx)); return vec3<f32>(res, f32(cube_idx));
} }
fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: vec4<f32>, atlas_dimensions: vec2<i32>, atlas_region: TextureAtlasFrame) -> f32 { fn calc_shadow_dir_light(normal: vec3<f32>, light_dir: vec3<f32>, frag_pos_light_space: vec4<f32>, atlas_dimensions: vec2<i32>, shadow_u: LightShadowMapUniform) -> f32 {
var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w; var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w;
// for some reason the y component is flipped after transforming // for some reason the y component is flipped after transforming
proj_coords.y = -proj_coords.y; 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] // Remap xy to [0.0, 1.0]
let xy_remapped = proj_coords.xy * 0.5 + 0.5; let xy_remapped = proj_coords.xy * 0.5 + 0.5;
// no need to get the y since the maps are square // get the atlas frame in [0; 1] in the atlas texture
let atlas_start = f32(atlas_region.x) / f32(atlas_dimensions.x); // z is width, w is height
let atlas_end = f32(atlas_region.x + atlas_region.width) / f32(atlas_dimensions.x); var region_rect = vec4<f32>(f32(shadow_u.atlas_frame.x), f32(shadow_u.atlas_frame.y), f32(shadow_u.atlas_frame.width), f32(shadow_u.atlas_frame.height));
// lerp the tex coords to the shadow map for this light. region_rect /= f32(atlas_dimensions.x);
proj_coords.x = mix(atlas_start, atlas_end, xy_remapped.x); let region_coords = vec2<f32>(
proj_coords.y = mix(atlas_start, atlas_end, xy_remapped.y); 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)
// 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>(f32(atlas_region.x), f32(atlas_region.y)) / vec2<f32>(atlas_dimensions));
let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, offset_coords, 0.0);
let current_depth = proj_coords.z;
// use a bias to avoid shadow acne // use a bias to avoid shadow acne
let bias = max(0.05 * (1.0 - dot(normal, light_dir)), 0.005); let bias = max(0.05 * (1.0 - dot(normal, light_dir)), 0.005);
var shadow = 0.0; let current_depth = proj_coords.z - bias;
if current_depth - bias > closest_depth {
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; 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<f32>, test_depth: f32, shadow_u: LightShadowMapUniform) -> f32 {
let half_filter_size = SHADOW_MAP_PCF_SIZE / 2.0;
let texel_size = 1.0 / vec2<f32>(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<f32>(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; return shadow;
} }
@ -289,9 +306,9 @@ fn calc_shadow_point(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: v
let cube_idx = i32(temp.z); let cube_idx = i32(temp.z);
/// if an unknown cube side was returned, something is broken /// if an unknown cube side was returned, something is broken
if cube_idx == 0 { /*if cube_idx == 0 {
return 0.0; return 0.0;
} }*/
var indices = light.light_shadow_uniform_index; var indices = light.light_shadow_uniform_index;
let i = indices[cube_idx - 1]; let i = indices[cube_idx - 1];
@ -303,27 +320,20 @@ fn calc_shadow_point(world_pos: vec3<f32>, world_normal: vec3<f32>, light_dir: v
region_coords /= f32(atlas_dimensions.x); region_coords /= f32(atlas_dimensions.x);
// simulate `ClampToBorder`, not creating shadows past the shadow map regions // 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; return 0.0;
} }*/
// get the coords inside of the region // 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.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); 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 // use a bias to avoid shadow acne
let bias = max(0.05 * (1.0 - dot(world_normal, light_dir)), 0.005); 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; var shadow = textureSampleCompare(t_shadow_maps_atlas, s_shadow_maps_atlas, coords_2d, current_depth);
if current_depth - bias > closest_depth {
shadow = 1.0;
}
return shadow; return shadow;
} }
@ -369,7 +379,7 @@ fn blinn_phong_dir_light(world_pos: vec3<f32>, world_norm: vec3<f32>, dir_light:
diffuse_color *= dir_light.diffuse; diffuse_color *= dir_light.diffuse;
specular_color *= dir_light.specular;*/ 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<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> { fn blinn_phong_point_light(world_pos: vec3<f32>, world_norm: vec3<f32>, point_light: Light, material: Material, specular_factor: vec3<f32>, shadow: f32) -> vec3<f32> {