diff --git a/examples/shadows/src/main.rs b/examples/shadows/src/main.rs index 5e3a9fc..036f360 100644 --- a/examples/shadows/src/main.rs +++ b/examples/shadows/src/main.rs @@ -139,16 +139,17 @@ fn setup_scene_plugin(game: &mut Game) { world.spawn(( platform_mesh.clone(), WorldTransform::default(), - Transform::from_xyz(0.0, -5.0, -5.0), + //Transform::from_xyz(0.0, -5.0, -5.0), + Transform::new(math::vec3(0.0, -5.0, -5.0), math::Quat::IDENTITY, math::vec3(5.0, 1.0, 5.0)), )); { - let mut light_tran = Transform::from_xyz(-5.5, 2.5, -3.0); + let mut light_tran = Transform::from_xyz(0.0, 2.5, 0.0); light_tran.scale = Vec3::new(0.5, 0.5, 0.5); light_tran.rotate_x(math::Angle::Degrees(-45.0)); light_tran.rotate_y(math::Angle::Degrees(-35.0)); world.spawn(( - cube_mesh.clone(), + //cube_mesh.clone(), DirectionalLight { enabled: true, color: Vec3::new(1.0, 0.95, 0.9), diff --git a/lyra-game/src/render/graph/passes/shadows.rs b/lyra-game/src/render/graph/passes/shadows.rs index cb2cc55..fc75b4a 100644 --- a/lyra-game/src/render/graph/passes/shadows.rs +++ b/lyra-game/src/render/graph/passes/shadows.rs @@ -100,9 +100,9 @@ impl ShadowMapsPass { let sampler = device.create_sampler(&wgpu::SamplerDescriptor { label: Some("sampler_shadow_map_atlas"), - address_mode_u: wgpu::AddressMode::ClampToEdge, - address_mode_v: wgpu::AddressMode::ClampToEdge, - address_mode_w: wgpu::AddressMode::ClampToEdge, + address_mode_u: wgpu::AddressMode::ClampToBorder, + address_mode_v: wgpu::AddressMode::ClampToBorder, + address_mode_w: wgpu::AddressMode::ClampToBorder, mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::FilterMode::Linear, @@ -126,15 +126,15 @@ impl ShadowMapsPass { fn create_depth_map(&mut self, device: &wgpu::Device, entity: Entity, light_pos: Transform) { const NEAR_PLANE: f32 = 0.1; - const FAR_PLANE: f32 = 80.0; + const FAR_PLANE: f32 = 25.5; let ortho_proj = - glam::Mat4::orthographic_rh_gl(-20.0, 20.0, -20.0, 20.0, NEAR_PLANE, FAR_PLANE); + glam::Mat4::orthographic_rh(-10.0, 10.0, -10.0, 10.0, NEAR_PLANE, FAR_PLANE); let look_view = glam::Mat4::look_to_rh(light_pos.translation, light_pos.forward(), light_pos.up()); - let light_proj = OPENGL_TO_WGPU_MATRIX * (ortho_proj * look_view); + let light_proj = ortho_proj * look_view; let light_projection_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { diff --git a/lyra-game/src/render/renderer.rs b/lyra-game/src/render/renderer.rs index e162f55..592f541 100755 --- a/lyra-game/src/render/renderer.rs +++ b/lyra-game/src/render/renderer.rs @@ -90,7 +90,7 @@ impl BasicRenderer { let (device, queue) = adapter.request_device( &wgpu::DeviceDescriptor { - features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES, + features: wgpu::Features::TEXTURE_ADAPTER_SPECIFIC_FORMAT_FEATURES | wgpu::Features::ADDRESS_MODE_CLAMP_TO_BORDER, // WebGL does not support all wgpu features. // Not sure if the engine will ever completely support WASM, // but its here just in case diff --git a/lyra-game/src/render/shaders/base.wgsl b/lyra-game/src/render/shaders/base.wgsl index b695844..79cf8f1 100755 --- a/lyra-game/src/render/shaders/base.wgsl +++ b/lyra-game/src/render/shaders/base.wgsl @@ -19,6 +19,7 @@ struct VertexOutput { @location(0) tex_coords: vec2, @location(1) world_position: vec3, @location(2) world_normal: vec3, + @location(3) frag_pos_light_space: vec4, } struct TransformData { @@ -70,16 +71,18 @@ fn vs_main( ) -> VertexOutput { var out: VertexOutput; + var world_position: vec4 = u_model_transform_data.transform * vec4(model.position, 1.0); + out.world_position = world_position.xyz; + out.tex_coords = model.tex_coords; - out.clip_position = u_camera.view_projection * u_model_transform_data.transform * vec4(model.position, 1.0); + out.clip_position = u_camera.view_projection * world_position; // the normal mat is actually only a mat3x3, but there's a bug in wgpu: https://github.com/gfx-rs/wgpu-rs/issues/36 let normal_mat4 = u_model_transform_data.normal_matrix; let normal_mat = mat3x3(normal_mat4[0].xyz, normal_mat4[1].xyz, normal_mat4[2].xyz); out.world_normal = normalize(normal_mat * model.normal, ); - var world_position: vec4 = u_model_transform_data.transform * vec4(model.position, 1.0); - out.world_position = world_position.xyz; + out.frag_pos_light_space = u_light_space_matrix * world_position; return out; } @@ -115,7 +118,7 @@ var u_light_indices: array; var t_light_grid: texture_storage_2d; // vec2 @group(5) @binding(0) -var t_shadow_maps_atlas: texture_2d; +var t_shadow_maps_atlas: texture_depth_2d; @group(5) @binding(1) var s_shadow_maps_atlas: sampler; @group(5) @binding(2) @@ -146,7 +149,27 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { let light: Light = u_lights.data[light_index]; if (light.light_ty == LIGHT_TY_DIRECTIONAL) { - light_res += blinn_phong_dir_light(in.world_position, in.world_normal, light, u_material, specular_color); + /*let shadow = calc_shadow(in.frag_pos_light_space); + return vec4(vec3(shadow), 1.0);*/ + + /*var proj_coords = in.frag_pos_light_space / in.frag_pos_light_space.w; + proj_coords = proj_coords * 0.5 + 0.5; + + let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, proj_coords.xy, 0.0); + let current_depth = proj_coords.z; + + if current_depth > closest_depth { + return vec4(vec3(current_depth), 1.0); + } else { + return vec4(vec3(closest_depth), 1.0); + }*/ + + //return vec4(vec3(closest_depth), 1.0); + //let shadow = select(0.0, 1.0, current_depth > closest_depth); + let light_dir = normalize(-light.direction); + + let shadow = calc_shadow(in.world_normal, light_dir, in.frag_pos_light_space); + 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) { light_res += blinn_phong_point_light(in.world_position, in.world_normal, light, u_material, specular_color); } else if (light.light_ty == LIGHT_TY_SPOT) { @@ -158,6 +181,34 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(light_object_res, object_color.a); } +fn calc_shadow(normal: vec3, light_dir: vec3, frag_pos_light_space: vec4) -> f32 { + var proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w; + // for some reason the y component is clipped after transforming + proj_coords.y = -proj_coords.y; + + // Remap xy to [0.0, 1.0] + let xy_remapped = proj_coords.xy * 0.5 + 0.5; + proj_coords.x = xy_remapped.x; + proj_coords.y = xy_remapped.y; + + let closest_depth = textureSampleLevel(t_shadow_maps_atlas, s_shadow_maps_atlas, proj_coords.xy, 0.0); + let current_depth = proj_coords.z; + + // 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 { + shadow = 1.0; + } + + // dont cast shadows outside the light's far plane + if (proj_coords.z > 1.0) { + shadow = 0.0; + } + + return shadow; +} + fn debug_grid(in: VertexOutput) -> vec4 { let tile_index_float: vec2 = in.clip_position.xy / 16.0; let tile_index = vec2(floor(tile_index_float)); @@ -173,7 +224,7 @@ fn debug_grid(in: VertexOutput) -> vec4 { return vec4(ratio, ratio, ratio, 1.0); } -fn blinn_phong_dir_light(world_pos: vec3, world_norm: vec3, dir_light: Light, material: Material, specular_factor: vec3) -> vec3 { +fn blinn_phong_dir_light(world_pos: vec3, world_norm: vec3, dir_light: Light, material: Material, specular_factor: vec3, shadow: f32) -> vec3 { let light_color = dir_light.color.xyz; let camera_view_pos = u_camera.position; @@ -199,7 +250,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 + diffuse_color + specular_color) * dir_light.intensity; + return (ambient_color + (1.0 - 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) -> vec3 {