From 36bea90459196285de8d3a2f8d51ee5ef7fda2bd Mon Sep 17 00:00:00 2001 From: Nils Hasenbanck Date: Sat, 14 Dec 2024 09:42:20 +0100 Subject: [PATCH] Improve the entity depth calculation and shadow rendering --- korangar/src/graphics/instruction.rs | 2 + .../graphics/passes/directional_shadow/mod.rs | 10 +++ .../directional_shadow/shader/entity.wgsl | 78 +++++++----------- .../shader/entity_bindless.wgsl | 80 +++++++------------ .../directional_shadow/shader/indicator.wgsl | 2 + .../directional_shadow/shader/model.wgsl | 2 + .../passes/forward/shader/entity.wgsl | 48 ++++------- .../forward/shader/entity_bindless.wgsl | 48 ++++------- .../src/graphics/passes/point_shadow/mod.rs | 5 ++ .../passes/point_shadow/shader/entity.wgsl | 55 ++++++------- .../point_shadow/shader/entity_bindless.wgsl | 55 ++++++------- .../passes/point_shadow/shader/indicator.wgsl | 2 + .../passes/point_shadow/shader/model.wgsl | 2 + korangar/src/main.rs | 5 +- .../src/world/cameras/directional_shadow.rs | 38 +-------- korangar/src/world/light/mod.rs | 6 ++ 16 files changed, 178 insertions(+), 260 deletions(-) diff --git a/korangar/src/graphics/instruction.rs b/korangar/src/graphics/instruction.rs index ecd1925d..6bdbebcc 100644 --- a/korangar/src/graphics/instruction.rs +++ b/korangar/src/graphics/instruction.rs @@ -77,6 +77,7 @@ pub struct WaterInstruction<'a> { #[derive(Clone, Debug)] pub struct DirectionalShadowCasterInstruction { pub view_projection_matrix: Matrix4, + pub view_matrix: Matrix4, pub direction: Vector3, pub color: Color, } @@ -86,6 +87,7 @@ pub struct DirectionalShadowCasterInstruction { #[derive(Clone, Debug)] pub struct PointShadowCasterInstruction { pub view_projection_matrices: [Matrix4; 6], + pub view_matrices: [Matrix4; 6], pub position: Point3, pub color: Color, pub range: f32, diff --git a/korangar/src/graphics/passes/directional_shadow/mod.rs b/korangar/src/graphics/passes/directional_shadow/mod.rs index 98c58ae3..eed7f024 100644 --- a/korangar/src/graphics/passes/directional_shadow/mod.rs +++ b/korangar/src/graphics/passes/directional_shadow/mod.rs @@ -6,6 +6,7 @@ use std::num::NonZeroU64; use std::sync::OnceLock; use bytemuck::{Pod, Zeroable}; +use cgmath::{Matrix4, SquareMatrix}; pub(crate) use entity::DirectionalShadowEntityDrawer; pub(crate) use indicator::DirectionalShadowIndicatorDrawer; pub(crate) use model::DirectionalShadowModelDrawer; @@ -26,6 +27,8 @@ const PASS_NAME: &str = "directional shadow render pass"; #[repr(C)] struct PassUniforms { view_projection: [[f32; 4]; 4], + view: [[f32; 4]; 4], + inverse_view: [[f32; 4]; 4], animation_timer: f32, padding: [u32; 3], } @@ -132,6 +135,13 @@ impl Prepare for DirectionalShadowRenderPassContext { fn prepare(&mut self, _device: &Device, instructions: &RenderInstruction) { self.uniforms_data = PassUniforms { view_projection: instructions.directional_light_with_shadow.view_projection_matrix.into(), + view: instructions.directional_light_with_shadow.view_matrix.into(), + inverse_view: instructions + .directional_light_with_shadow + .view_matrix + .invert() + .unwrap_or(Matrix4::identity()) + .into(), animation_timer: instructions.uniforms.animation_timer, padding: Default::default(), }; diff --git a/korangar/src/graphics/passes/directional_shadow/shader/entity.wgsl b/korangar/src/graphics/passes/directional_shadow/shader/entity.wgsl index 5f45ac89..58ff8f32 100644 --- a/korangar/src/graphics/passes/directional_shadow/shader/entity.wgsl +++ b/korangar/src/graphics/passes/directional_shadow/shader/entity.wgsl @@ -1,5 +1,7 @@ struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, animation_timer: f32, } @@ -20,18 +22,17 @@ struct InstanceData { struct Vertex { position: vec3, texture_coordinates: vec2, - depth_multiplier: f32, - curvature_multiplier: f32, } struct VertexOutput { @builtin(position) position: vec4, - @location(0) texture_coordinates: vec2, - @location(1) depth_offset: f32, - @location(2) curvature: f32, - @location(3) @interpolate(flat) original_depth_offset: f32, - @location(4) @interpolate(flat) original_curvature: f32, - @location(5) alpha: f32, + @location(0) world_position: vec4, + @location(1) texture_coordinates: vec2, + @location(2) depth_offset: f32, + @location(3) curvature: f32, + @location(4) @interpolate(flat) original_depth_offset: f32, + @location(5) @interpolate(flat) original_curvature: f32, + @location(6) alpha: f32, } @group(0) @binding(3) var texture_sampler: sampler; @@ -47,9 +48,11 @@ fn vs_main( let instance = instance_data[instance_index]; let vertex = vertex_data(vertex_index); let frame_part_vertex = instance.frame_part_transform * vec4(vertex.position, 1.0); + let world_position = instance.world * frame_part_vertex; var output: VertexOutput; - output.position = pass_uniforms.view_projection * instance.world * frame_part_vertex; + output.world_position = world_position; + output.position = pass_uniforms.view_projection * world_position; output.texture_coordinates = instance.texture_position + vertex.texture_coordinates * instance.texture_size; if (instance.mirror != 0u) { @@ -67,7 +70,7 @@ fn vs_main( // Because we have to transform the vertex of the frame part, we can't use the depth and curvature // directly and are using the fact, that y / depth and x / curvature correlate to each other. // An offset is also added for frame parts not stay at the same depth. - output.depth_offset = (frame_part_vertex.y - 1.0) * proportion_y + instance.extra_depth_offset; + output.depth_offset = frame_part_vertex.y * proportion_y + instance.extra_depth_offset; output.curvature = frame_part_vertex.x * proportion_x; output.original_depth_offset = instance.depth_offset; @@ -83,35 +86,30 @@ fn fs_main(input: VertexOutput) -> @builtin(frag_depth) f32 { discard; } - // TODO: This is only a temporary change, we will fix the depth_offset later. - // The idea for the temporary change is to create two parabolas to correct the plane inclination. - let sign_depth_offset = select(2.0, -2.0, input.depth_offset < 0.0); - let scaled_depth_offset = sign_depth_offset * pow(input.depth_offset, 2.0) * input.original_depth_offset; - let scaled_curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + // Adjust the sprite as if it was standing upright + let depth_offset = input.depth_offset * input.original_depth_offset; + let curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + let view_position = pass_uniforms.view * input.world_position; + let adjusted_view_position = view_position - vec4(0.0, 0.0, depth_offset + curvature_offset, 0.0); + let adjusted_world_position = pass_uniforms.inverse_view * adjusted_view_position; + let clip_position = pass_uniforms.view_projection * adjusted_world_position; + let depth = saturate(clip_position.z / clip_position.w); - // Shadows use orthographic projections, so values are linear, but "compressed". - let absolute_depth = decompress_depth_ortho(input.position.z); - let adjusted_linear_z: f32 = 3.5 + absolute_depth - scaled_depth_offset - scaled_curvature_offset; - let depth = compress_depth_ortho(adjusted_linear_z); - let clamped_depth = clamp(depth, 0.0, 1.0); - - return clamped_depth; + return depth; } // Optimized version of the following truth table: // -// vertex_index x y z u v d c -// 0 -1 2 1 0 0 1 -1 -// 1 -1 0 1 0 1 -1 -1 -// 2 1 2 1 1 0 1 1 -// 3 1 2 1 1 0 1 1 -// 4 -1 0 1 0 1 -1 -1 -// 5 1 0 1 1 1 -1 1 +// vertex_index x y z u v +// 0 -1 2 1 0 0 +// 1 -1 0 1 0 1 +// 2 1 2 1 1 0 +// 3 1 2 1 1 0 +// 4 -1 0 1 0 1 +// 5 1 0 1 1 1 // // (x,y,z) are the vertex position // (u,v) are the UV coordinates -// (depth) is the depth multiplier -// (curve) is the curvature multiplier fn vertex_data(vertex_index: u32) -> Vertex { let index = 1u << vertex_index; @@ -123,22 +121,6 @@ fn vertex_data(vertex_index: u32) -> Vertex { let z = 1.0; let u = f32(1 - case0); let v = f32(1 - case1); - let depth = y - 1.0; - let curve = x; - - return Vertex(vec3(x, y, z), vec2(u, v), depth, curve); -} - -fn decompress_depth_ortho(depth: f32) -> f32 { - let depth_scale = -pass_uniforms.view_projection[2][2]; - let depth_far = pass_uniforms.view_projection[3][2]; - let far = depth_far / depth_scale; - return far - depth / depth_scale; -} -fn compress_depth_ortho(depth: f32) -> f32 { - let depth_scale = -pass_uniforms.view_projection[2][2]; - let depth_far = pass_uniforms.view_projection[3][2]; - let far = depth_far / depth_scale; - return -depth * depth_scale + depth_far; + return Vertex(vec3(x, y, z), vec2(u, v)); } diff --git a/korangar/src/graphics/passes/directional_shadow/shader/entity_bindless.wgsl b/korangar/src/graphics/passes/directional_shadow/shader/entity_bindless.wgsl index 0be11b37..1ce74fe5 100644 --- a/korangar/src/graphics/passes/directional_shadow/shader/entity_bindless.wgsl +++ b/korangar/src/graphics/passes/directional_shadow/shader/entity_bindless.wgsl @@ -1,5 +1,7 @@ struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, animation_timer: f32, } @@ -20,19 +22,18 @@ struct InstanceData { struct Vertex { position: vec3, texture_coordinates: vec2, - depth_multiplier: f32, - curvature_multiplier: f32, } struct VertexOutput { @builtin(position) position: vec4, - @location(0) texture_coordinates: vec2, - @location(1) depth_offset: f32, - @location(2) curvature: f32, - @location(3) @interpolate(flat) original_depth_offset: f32, - @location(4) @interpolate(flat) original_curvature: f32, - @location(5) texture_index: i32, - @location(6) alpha: f32, + @location(0) world_position: vec4, + @location(1) texture_coordinates: vec2, + @location(2) depth_offset: f32, + @location(3) curvature: f32, + @location(4) @interpolate(flat) original_depth_offset: f32, + @location(5) @interpolate(flat) original_curvature: f32, + @location(6) texture_index: i32, + @location(7) alpha: f32, } @group(0) @binding(3) var texture_sampler: sampler; @@ -48,9 +49,11 @@ fn vs_main( let instance = instance_data[instance_index]; let vertex = vertex_data(vertex_index); let frame_part_vertex = instance.frame_part_transform * vec4(vertex.position, 1.0); + let world_position = instance.world * frame_part_vertex; var output: VertexOutput; - output.position = pass_uniforms.view_projection * instance.world * frame_part_vertex; + output.world_position = world_position; + output.position = pass_uniforms.view_projection * world_position; output.texture_coordinates = instance.texture_position + vertex.texture_coordinates * instance.texture_size; if (instance.mirror != 0u) { @@ -68,7 +71,7 @@ fn vs_main( // Because we have to transform the vertex of the frame part, we can't use the depth and curvature // directly and are using the fact, that y / depth and x / curvature correlate to each other. // An offset is also added for frame parts not stay at the same depth. - output.depth_offset = (frame_part_vertex.y - 1.0) * proportion_y + instance.extra_depth_offset; + output.depth_offset = frame_part_vertex.y * proportion_y + instance.extra_depth_offset; output.curvature = frame_part_vertex.x * proportion_x; output.original_depth_offset = instance.depth_offset; @@ -85,35 +88,30 @@ fn fs_main(input: VertexOutput) -> @builtin(frag_depth) f32 { discard; } - // TODO: This is only a temporary change, we will fix the depth_offset later. - // The idea for the temporary change is to create two parabolas to correct the plane inclination. - let sign_depth_offset = select(2.0, -2.0, input.depth_offset < 0.0); - let scaled_depth_offset = sign_depth_offset * pow(input.depth_offset, 2.0) * input.original_depth_offset; - let scaled_curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + // Adjust the sprite as if it was standing upright + let depth_offset = input.depth_offset * input.original_depth_offset; + let curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + let view_position = pass_uniforms.view * input.world_position; + let adjusted_view_position = view_position - vec4(0.0, 0.0, depth_offset + curvature_offset, 0.0); + let adjusted_world_position = pass_uniforms.inverse_view * adjusted_view_position; + let clip_position = pass_uniforms.view_projection * adjusted_world_position; + let depth = saturate(clip_position.z / clip_position.w); - // Shadows use orthographic projections, so values are linear, but "compressed". - let absolute_depth = decompress_depth_ortho(input.position.z); - let adjusted_linear_z: f32 = 3.5 + absolute_depth - scaled_depth_offset - scaled_curvature_offset; - let depth = compress_depth_ortho(adjusted_linear_z); - let clamped_depth = clamp(depth, 0.0, 1.0); - - return clamped_depth; + return depth; } // Optimized version of the following truth table: // -// vertex_index x y z u v d c -// 0 -1 2 1 0 0 1 -1 -// 1 -1 0 1 0 1 -1 -1 -// 2 1 2 1 1 0 1 1 -// 3 1 2 1 1 0 1 1 -// 4 -1 0 1 0 1 -1 -1 -// 5 1 0 1 1 1 -1 1 +// vertex_index x y z u v +// 0 -1 2 1 0 0 +// 1 -1 0 1 0 1 +// 2 1 2 1 1 0 +// 3 1 2 1 1 0 +// 4 -1 0 1 0 1 +// 5 1 0 1 1 1 // // (x,y,z) are the vertex position // (u,v) are the UV coordinates -// (depth) is the depth multiplier -// (curve) is the curvature multiplier fn vertex_data(vertex_index: u32) -> Vertex { let index = 1u << vertex_index; @@ -125,22 +123,6 @@ fn vertex_data(vertex_index: u32) -> Vertex { let z = 1.0; let u = f32(1 - case0); let v = f32(1 - case1); - let depth = y - 1.0; - let curve = x; - - return Vertex(vec3(x, y, z), vec2(u, v), depth, curve); -} - -fn decompress_depth_ortho(depth: f32) -> f32 { - let depth_scale = -pass_uniforms.view_projection[2][2]; - let depth_far = pass_uniforms.view_projection[3][2]; - let far = depth_far / depth_scale; - return far - depth / depth_scale; -} -fn compress_depth_ortho(depth: f32) -> f32 { - let depth_scale = -pass_uniforms.view_projection[2][2]; - let depth_far = pass_uniforms.view_projection[3][2]; - let far = depth_far / depth_scale; - return -depth * depth_scale + depth_far; + return Vertex(vec3(x, y, z), vec2(u, v)); } diff --git a/korangar/src/graphics/passes/directional_shadow/shader/indicator.wgsl b/korangar/src/graphics/passes/directional_shadow/shader/indicator.wgsl index 13d9fbd9..f1aa6ccc 100644 --- a/korangar/src/graphics/passes/directional_shadow/shader/indicator.wgsl +++ b/korangar/src/graphics/passes/directional_shadow/shader/indicator.wgsl @@ -20,6 +20,8 @@ struct GlobalUniforms { struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, animation_timer: f32, } diff --git a/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl b/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl index db1081e9..6d6e8242 100644 --- a/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl +++ b/korangar/src/graphics/passes/directional_shadow/shader/model.wgsl @@ -1,5 +1,7 @@ struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, animation_timer: f32, } diff --git a/korangar/src/graphics/passes/forward/shader/entity.wgsl b/korangar/src/graphics/passes/forward/shader/entity.wgsl index 0dd5d03d..56505808 100644 --- a/korangar/src/graphics/passes/forward/shader/entity.wgsl +++ b/korangar/src/graphics/passes/forward/shader/entity.wgsl @@ -52,8 +52,6 @@ struct TileLightIndices { struct Vertex { position: vec3, texture_coordinates: vec2, - depth_multiplier: f32, - curvature_multiplier: f32, } struct VertexOutput { @@ -122,7 +120,7 @@ fn vs_main( // Because we have to transform the vertex of the frame part, we can't use the depth and curvature // directly and are using the fact, that y / depth and x / curvature correlate to each other. // An offset is also added for frame parts not stay at the same depth. - output.depth_offset = (frame_part_vertex.y - 1.0) * proportion_y + instance.extra_depth_offset; + output.depth_offset = frame_part_vertex.y * proportion_y + instance.extra_depth_offset; output.curvature = frame_part_vertex.x * proportion_x; output.original_depth_offset = instance.depth_offset; @@ -153,21 +151,14 @@ fn fs_main(input: VertexOutput) -> FragmentOutput { let normal = normalize(input.normal); - // TODO: This is only a temporary change, we will fix the depth_offset later. - // The idea for the temporary change is to create two parabolas to correct the plane inclination. - let sign_depth_offset = select(2.0, -2.0, input.depth_offset < 0.0); - let scaled_depth_offset = sign_depth_offset * pow(input.depth_offset, 2.0) * input.original_depth_offset; - let scaled_curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; - - // Adjust world position in view space + // Adjust the sprite as if it was standing upright + let depth_offset = input.depth_offset * input.original_depth_offset; + let curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; let view_position = global_uniforms.view * input.world_position; - // Tha magic +2.0 is added, so that entities don't clip into the ground. - let adjusted_view_position = view_position - vec4(0.0, 0.0, scaled_depth_offset + scaled_curvature_offset + 2.0, 0.0); + let adjusted_view_position = view_position - vec4(0.0, 0.0, depth_offset + curvature_offset, 0.0); let adjusted_world_position = global_uniforms.inverse_view * adjusted_view_position; - - // Depth adjustment calculation let clip_position = global_uniforms.view_projection * adjusted_world_position; - let clamped_depth = clamp(clip_position.z / clip_position.w, 0.0, 1.0); + let depth = saturate(clip_position.z / clip_position.w); // Ambient light var ambient_light_contribution = global_uniforms.ambient_color.rgb; @@ -181,10 +172,9 @@ fn fs_main(input: VertexOutput) -> FragmentOutput { } // Shadow calculation - let shadow_position = directional_light.view_projection * input.world_position; + let shadow_position = directional_light.view_projection * adjusted_world_position; var shadow_coords = shadow_position.xyz / shadow_position.w; - let bias = 0.002; - let world_position = input.world_position.xyz / input.world_position.w; + let bias = 0.01; shadow_coords = vec3(clip_to_screen_space(shadow_coords.xy), shadow_coords.z + bias); var visibility: f32; @@ -245,7 +235,7 @@ fn fs_main(input: VertexOutput) -> FragmentOutput { var output: FragmentOutput; output.fragment_color = vec4(color, alpha_channel); - output.frag_depth = clamped_depth; + output.frag_depth = depth; return output; } @@ -280,18 +270,16 @@ fn clip_to_screen_space(ndc: vec2) -> vec2 { // Optimized version of the following truth table: // -// vertex_index x y z u v d c -// 0 -1 2 1 0 0 1 -1 -// 1 -1 0 1 0 1 -1 -1 -// 2 1 2 1 1 0 1 1 -// 3 1 2 1 1 0 1 1 -// 4 -1 0 1 0 1 -1 -1 -// 5 1 0 1 1 1 -1 1 +// vertex_index x y z u v +// 0 -1 2 1 0 0 +// 1 -1 0 1 0 1 +// 2 1 2 1 1 0 +// 3 1 2 1 1 0 +// 4 -1 0 1 0 1 +// 5 1 0 1 1 1 // // (x,y,z) are the vertex position // (u,v) are the UV coordinates -// (depth) is the depth multiplier -// (curve) is the curvature multiplier fn vertex_data(vertex_index: u32) -> Vertex { let index = 1u << vertex_index; @@ -303,10 +291,8 @@ fn vertex_data(vertex_index: u32) -> Vertex { let z = 1.0; let u = f32(1 - case0); let v = f32(1 - case1); - let depth = y - 1.0; - let curve = x; - return Vertex(vec3(x, y, z), vec2(u, v), depth, curve); + return Vertex(vec3(x, y, z), vec2(u, v)); } fn rotateY(direction: vec3, angle: f32) -> vec3 { diff --git a/korangar/src/graphics/passes/forward/shader/entity_bindless.wgsl b/korangar/src/graphics/passes/forward/shader/entity_bindless.wgsl index 520320e9..e27efdbe 100644 --- a/korangar/src/graphics/passes/forward/shader/entity_bindless.wgsl +++ b/korangar/src/graphics/passes/forward/shader/entity_bindless.wgsl @@ -52,8 +52,6 @@ struct TileLightIndices { struct Vertex { position: vec3, texture_coordinates: vec2, - depth_multiplier: f32, - curvature_multiplier: f32, } struct VertexOutput { @@ -123,7 +121,7 @@ fn vs_main( // Because we have to transform the vertex of the frame part, we can't use the depth and curvature // directly and are using the fact, that y / depth and x / curvature correlate to each other. // An offset is also added for frame parts not stay at the same depth. - output.depth_offset = (frame_part_vertex.y - 1.0) * proportion_y + instance.extra_depth_offset; + output.depth_offset = frame_part_vertex.y * proportion_y + instance.extra_depth_offset; output.curvature = frame_part_vertex.x * proportion_x; output.original_depth_offset = instance.depth_offset; @@ -155,21 +153,14 @@ fn fs_main(input: VertexOutput) -> FragmentOutput { let normal = normalize(input.normal); - // TODO: This is only a temporary change, we will fix the depth_offset later. - // The idea for the temporary change is to create two parabolas to correct the plane inclination. - let sign_depth_offset = select(2.0, -2.0, input.depth_offset < 0.0); - let scaled_depth_offset = sign_depth_offset * pow(input.depth_offset, 2.0) * input.original_depth_offset; - let scaled_curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; - - // Adjust world position in view space + // Adjust the sprite as if it was standing upright + let depth_offset = input.depth_offset * input.original_depth_offset; + let curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; let view_position = global_uniforms.view * input.world_position; - // Tha magic +2.0 is added, so that entities don't clip into the ground. - let adjusted_view_position = view_position - vec4(0.0, 0.0, scaled_depth_offset + scaled_curvature_offset + 2.0, 0.0); + let adjusted_view_position = view_position - vec4(0.0, 0.0, depth_offset + curvature_offset, 0.0); let adjusted_world_position = global_uniforms.inverse_view * adjusted_view_position; - - // Depth adjustment calculation let clip_position = global_uniforms.view_projection * adjusted_world_position; - let clamped_depth = clamp(clip_position.z / clip_position.w, 0.0, 1.0); + let depth = saturate(clip_position.z / clip_position.w); // Ambient light var ambient_light_contribution = global_uniforms.ambient_color.rgb; @@ -183,10 +174,9 @@ fn fs_main(input: VertexOutput) -> FragmentOutput { } // Shadow calculation - let shadow_position = directional_light.view_projection * input.world_position; + let shadow_position = directional_light.view_projection * adjusted_world_position; var shadow_coords = shadow_position.xyz / shadow_position.w; - let bias = 0.002; - let world_position = input.world_position.xyz / input.world_position.w; + let bias = 0.01; shadow_coords = vec3(clip_to_screen_space(shadow_coords.xy), shadow_coords.z + bias); var visibility: f32; @@ -247,7 +237,7 @@ fn fs_main(input: VertexOutput) -> FragmentOutput { var output: FragmentOutput; output.fragment_color = vec4(color, alpha_channel); - output.frag_depth = clamped_depth; + output.frag_depth = depth; return output; } @@ -282,18 +272,16 @@ fn clip_to_screen_space(ndc: vec2) -> vec2 { // Optimized version of the following truth table: // -// vertex_index x y z u v d c -// 0 -1 2 1 0 0 1 -1 -// 1 -1 0 1 0 1 -1 -1 -// 2 1 2 1 1 0 1 1 -// 3 1 2 1 1 0 1 1 -// 4 -1 0 1 0 1 -1 -1 -// 5 1 0 1 1 1 -1 1 +// vertex_index x y z u v +// 0 -1 2 1 0 0 +// 1 -1 0 1 0 1 +// 2 1 2 1 1 0 +// 3 1 2 1 1 0 +// 4 -1 0 1 0 1 +// 5 1 0 1 1 1 // // (x,y,z) are the vertex position // (u,v) are the UV coordinates -// (depth) is the depth multiplier -// (curve) is the curvature multiplier fn vertex_data(vertex_index: u32) -> Vertex { let index = 1u << vertex_index; @@ -305,10 +293,8 @@ fn vertex_data(vertex_index: u32) -> Vertex { let z = 1.0; let u = f32(1 - case0); let v = f32(1 - case1); - let depth = y - 1.0; - let curve = x; - return Vertex(vec3(x, y, z), vec2(u, v), depth, curve); + return Vertex(vec3(x, y, z), vec2(u, v)); } fn rotateY(direction: vec3, angle: f32) -> vec3 { diff --git a/korangar/src/graphics/passes/point_shadow/mod.rs b/korangar/src/graphics/passes/point_shadow/mod.rs index 36ce7baf..0902a9a8 100644 --- a/korangar/src/graphics/passes/point_shadow/mod.rs +++ b/korangar/src/graphics/passes/point_shadow/mod.rs @@ -5,6 +5,7 @@ mod model; use std::sync::OnceLock; use bytemuck::{Pod, Zeroable}; +use cgmath::{Matrix4, SquareMatrix}; pub(crate) use entity::PointShadowEntityDrawer; pub(crate) use indicator::PointShadowIndicatorDrawer; pub(crate) use model::PointShadowModelDrawer; @@ -27,6 +28,8 @@ const NUMBER_FACES: usize = 6; #[repr(C)] struct PassUniforms { view_projection: [[f32; 4]; 4], + view: [[f32; 4]; 4], + inverse_view: [[f32; 4]; 4], light_position: [f32; 4], animation_timer: f32, padding: [u32; 3], @@ -148,6 +151,8 @@ impl Prepare for PointShadowRenderPassContext { let uniforms = instructions.point_light_shadow_caster.iter().flat_map(|caster| { (0..NUMBER_FACES).map(|face_index| PassUniforms { view_projection: caster.view_projection_matrices[face_index].into(), + view: caster.view_matrices[face_index].into(), + inverse_view: caster.view_matrices[face_index].invert().unwrap_or(Matrix4::identity()).into(), light_position: caster.position.to_homogeneous().into(), animation_timer: instructions.uniforms.animation_timer, padding: Default::default(), diff --git a/korangar/src/graphics/passes/point_shadow/shader/entity.wgsl b/korangar/src/graphics/passes/point_shadow/shader/entity.wgsl index 13115fa3..80fb5b1d 100644 --- a/korangar/src/graphics/passes/point_shadow/shader/entity.wgsl +++ b/korangar/src/graphics/passes/point_shadow/shader/entity.wgsl @@ -1,5 +1,7 @@ struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, light_position: vec4, animation_timer: f32 } @@ -21,8 +23,6 @@ struct InstanceData { struct Vertex { position: vec3, texture_coordinates: vec2, - depth_multiplier: f32, - curvature_multiplier: f32, } struct VertexOutput { @@ -70,7 +70,7 @@ fn vs_main( // Because we have to transform the vertex of the frame part, we can't use the depth and curvature // directly and are using the fact, that y / depth and x / curvature correlate to each other. // An offset is also added for frame parts not stay at the same depth. - output.depth_offset = (frame_part_vertex.y - 1.0) * proportion_y + instance.extra_depth_offset; + output.depth_offset = frame_part_vertex.y * proportion_y + instance.extra_depth_offset; output.curvature = frame_part_vertex.x * proportion_x; output.original_depth_offset = instance.depth_offset; @@ -86,39 +86,29 @@ fn fs_main(input: VertexOutput) -> @builtin(frag_depth) f32 { discard; } - // TODO: This is only a temporary change, we will fix the depth_offset later. - // The idea for the temporary change is to create two parabolas to correct the plane inclination. - let sign_depth_offset = select(2.0, -2.0, input.depth_offset < 0.0); - let scaled_depth_offset = sign_depth_offset * pow(input.depth_offset, 2.0) * input.original_depth_offset; - let scaled_curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + // Adjust the sprite as if it was standing upright + let depth_offset = input.depth_offset * input.original_depth_offset; + let curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + let view_position = pass_uniforms.view * input.world_position; + let adjusted_view_position = view_position - vec4(0.0, 0.0, depth_offset + curvature_offset, 0.0); + let adjusted_world_position = pass_uniforms.inverse_view * adjusted_view_position; + let light_distance = length(adjusted_world_position.xyz - pass_uniforms.light_position.xyz); - // Point shadows use a projection matrix, so depth is non-linear. - let absolute_depth = nonLinearToLinear(input.position.z); - let adjusted_linear_z: f32 = 3.5 + absolute_depth - scaled_depth_offset - scaled_curvature_offset; - - // TODO: NHA We need to save the distance from this fragment position (with the adjusted depth value) to the light. - return 0.0; -} - -fn nonLinearToLinear(nonlinear_depth: f32) -> f32 { - const NEAR_PLANE = 0.1; - return NEAR_PLANE * (nonlinear_depth + 1e-7); + return linearToNonLinear(light_distance); } // Optimized version of the following truth table: // -// vertex_index x y z u v d c -// 0 -1 2 1 0 0 1 -1 -// 1 -1 0 1 0 1 -1 -1 -// 2 1 2 1 1 0 1 1 -// 3 1 2 1 1 0 1 1 -// 4 -1 0 1 0 1 -1 -1 -// 5 1 0 1 1 1 -1 1 +// vertex_index x y z u v +// 0 -1 2 1 0 0 +// 1 -1 0 1 0 1 +// 2 1 2 1 1 0 +// 3 1 2 1 1 0 +// 4 -1 0 1 0 1 +// 5 1 0 1 1 1 // // (x,y,z) are the vertex position // (u,v) are the UV coordinates -// (depth) is the depth multiplier -// (curve) is the curvature multiplier fn vertex_data(vertex_index: u32) -> Vertex { let index = 1u << vertex_index; @@ -130,8 +120,11 @@ fn vertex_data(vertex_index: u32) -> Vertex { let z = 1.0; let u = f32(1 - case0); let v = f32(1 - case1); - let depth = y - 1.0; - let curve = x; - return Vertex(vec3(x, y, z), vec2(u, v), depth, curve); + return Vertex(vec3(x, y, z), vec2(u, v)); +} + +fn linearToNonLinear(linear_depth: f32) -> f32 { + const NEAR_PLANE = 0.1; + return NEAR_PLANE / (linear_depth + 1e-7); } diff --git a/korangar/src/graphics/passes/point_shadow/shader/entity_bindless.wgsl b/korangar/src/graphics/passes/point_shadow/shader/entity_bindless.wgsl index 56b97e57..4d0db2c4 100644 --- a/korangar/src/graphics/passes/point_shadow/shader/entity_bindless.wgsl +++ b/korangar/src/graphics/passes/point_shadow/shader/entity_bindless.wgsl @@ -1,5 +1,7 @@ struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, light_position: vec4, animation_timer: f32 } @@ -21,8 +23,6 @@ struct InstanceData { struct Vertex { position: vec3, texture_coordinates: vec2, - depth_multiplier: f32, - curvature_multiplier: f32, } struct VertexOutput { @@ -71,7 +71,7 @@ fn vs_main( // Because we have to transform the vertex of the frame part, we can't use the depth and curvature // directly and are using the fact, that y / depth and x / curvature correlate to each other. // An offset is also added for frame parts not stay at the same depth. - output.depth_offset = (frame_part_vertex.y - 1.0) * proportion_y + instance.extra_depth_offset; + output.depth_offset = frame_part_vertex.y * proportion_y + instance.extra_depth_offset; output.curvature = frame_part_vertex.x * proportion_x; output.original_depth_offset = instance.depth_offset; @@ -88,39 +88,29 @@ fn fs_main(input: VertexOutput) -> @builtin(frag_depth) f32 { discard; } - // TODO: This is only a temporary change, we will fix the depth_offset later. - // The idea for the temporary change is to create two parabolas to correct the plane inclination. - let sign_depth_offset = select(2.0, -2.0, input.depth_offset < 0.0); - let scaled_depth_offset = sign_depth_offset * pow(input.depth_offset, 2.0) * input.original_depth_offset; - let scaled_curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + // Adjust the sprite as if it was standing upright + let depth_offset = input.depth_offset * input.original_depth_offset; + let curvature_offset = (0.5 - pow(input.curvature, 2.0)) * input.original_curvature; + let view_position = pass_uniforms.view * input.world_position; + let adjusted_view_position = view_position - vec4(0.0, 0.0, depth_offset + curvature_offset, 0.0); + let adjusted_world_position = pass_uniforms.inverse_view * adjusted_view_position; + let light_distance = length(adjusted_world_position.xyz - pass_uniforms.light_position.xyz); - // Point shadows use a projection matrix, so depth is non-linear. - let absolute_depth = nonLinearToLinear(input.position.z); - let adjusted_linear_z: f32 = 3.5 + absolute_depth - scaled_depth_offset - scaled_curvature_offset; - - // TODO: NHA We need to save the distance from this fragment position (with the adjusted depth value) to the light. - return 0.0; -} - -fn nonLinearToLinear(nonlinear_depth: f32) -> f32 { - const NEAR_PLANE = 0.1; - return NEAR_PLANE * (nonlinear_depth + 1e-7); + return linearToNonLinear(light_distance); } // Optimized version of the following truth table: // -// vertex_index x y z u v d c -// 0 -1 2 1 0 0 1 -1 -// 1 -1 0 1 0 1 -1 -1 -// 2 1 2 1 1 0 1 1 -// 3 1 2 1 1 0 1 1 -// 4 -1 0 1 0 1 -1 -1 -// 5 1 0 1 1 1 -1 1 +// vertex_index x y z u v +// 0 -1 2 1 0 0 +// 1 -1 0 1 0 1 +// 2 1 2 1 1 0 +// 3 1 2 1 1 0 +// 4 -1 0 1 0 1 +// 5 1 0 1 1 1 // // (x,y,z) are the vertex position // (u,v) are the UV coordinates -// (depth) is the depth multiplier -// (curve) is the curvature multiplier fn vertex_data(vertex_index: u32) -> Vertex { let index = 1u << vertex_index; @@ -132,8 +122,11 @@ fn vertex_data(vertex_index: u32) -> Vertex { let z = 1.0; let u = f32(1 - case0); let v = f32(1 - case1); - let depth = y - 1.0; - let curve = x; - return Vertex(vec3(x, y, z), vec2(u, v), depth, curve); + return Vertex(vec3(x, y, z), vec2(u, v)); +} + +fn linearToNonLinear(linear_depth: f32) -> f32 { + const NEAR_PLANE = 0.1; + return NEAR_PLANE / (linear_depth + 1e-7); } diff --git a/korangar/src/graphics/passes/point_shadow/shader/indicator.wgsl b/korangar/src/graphics/passes/point_shadow/shader/indicator.wgsl index 9b8cc8ad..89ccdbdb 100644 --- a/korangar/src/graphics/passes/point_shadow/shader/indicator.wgsl +++ b/korangar/src/graphics/passes/point_shadow/shader/indicator.wgsl @@ -20,6 +20,8 @@ struct GlobalUniforms { struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, light_position: vec4, animation_timer: f32 } diff --git a/korangar/src/graphics/passes/point_shadow/shader/model.wgsl b/korangar/src/graphics/passes/point_shadow/shader/model.wgsl index 64c1f668..0c4abf0d 100644 --- a/korangar/src/graphics/passes/point_shadow/shader/model.wgsl +++ b/korangar/src/graphics/passes/point_shadow/shader/model.wgsl @@ -1,5 +1,7 @@ struct PassUniforms { view_projection: mat4x4, + view: mat4x4, + inverse_view: mat4x4, light_position: vec4, animation_timer: f32 } diff --git a/korangar/src/main.rs b/korangar/src/main.rs index 42f400f8..8951d550 100644 --- a/korangar/src/main.rs +++ b/korangar/src/main.rs @@ -1882,7 +1882,7 @@ impl Client { let (directional_light_view_matrix, directional_light_projection_matrix) = self.directional_shadow_camera.view_projection_matrices(); - let directional_light_matrix = directional_light_projection_matrix * directional_light_view_matrix; + let directional_light_view_projection_matrix = directional_light_projection_matrix * directional_light_view_matrix; #[cfg(feature = "debug")] matrices_measurement.stop(); @@ -2252,7 +2252,8 @@ impl Client { middle_layer_rectangles: middle_layer_instructions.as_slice(), top_layer_rectangles: top_layer_instructions.as_slice(), directional_light_with_shadow: DirectionalShadowCasterInstruction { - view_projection_matrix: directional_light_matrix, + view_projection_matrix: directional_light_view_projection_matrix, + view_matrix: directional_light_view_matrix, direction: directional_light_direction, color: directional_light_color, }, diff --git a/korangar/src/world/cameras/directional_shadow.rs b/korangar/src/world/cameras/directional_shadow.rs index 96f502ce..0cc38709 100644 --- a/korangar/src/world/cameras/directional_shadow.rs +++ b/korangar/src/world/cameras/directional_shadow.rs @@ -1,6 +1,4 @@ -use std::f32::consts::FRAC_PI_2; - -use cgmath::{EuclideanSpace, InnerSpace, Matrix4, Point3, Vector2, Vector3, Vector4, Zero}; +use cgmath::{InnerSpace, Matrix4, Point3, Vector2, Vector3, Zero}; use super::Camera; use crate::graphics::orthographic_reverse_lh; @@ -93,38 +91,4 @@ impl Camera for DirectionalShadowCamera { fn view_direction(&self) -> Vector3 { self.view_direction } - - fn calculate_depth_offset_and_curvature(&self, world_matrix: &Matrix4, sprite_height: f32, sprite_width: f32) -> (f32, f32) { - const OFFSET_FACTOR: f32 = 10.0; - const CURVATURE_FACTOR: f32 = 8.0; - - let sprite_height = -2.0 * sprite_height; - - let sprite_position = world_matrix * Vector4::new(0.0, 0.0, 0.0, 1.0); - let camera_position = self.camera_position().to_vec().extend(1.0); - let view_direction = self.view_direction().extend(0.0); - - // Calculate angle from the camera to the sprite in against the x/z plane. - let camera_to_sprite = (sprite_position - camera_position).normalize(); - let vertical_axis = Vector4::unit_y(); - let sprite_angle = camera_to_sprite.angle(vertical_axis).0; - - // Adjust the angle to make 0.0 degrees the horizon. - let sprite_angle = (sprite_angle - FRAC_PI_2).to_degrees(); - let angle_progress = sprite_angle / -90.0; - - // Calculate offset point in the opposite view direction. - let offset_magnitude = OFFSET_FACTOR * sprite_height * angle_progress; - let offset_point = sprite_position - view_direction * offset_magnitude; - - // Calculate linear depth offset in view space. - let (view_matrix, _) = self.view_projection_matrices(); - let sprite_view = view_matrix * sprite_position; - let offset_view = view_matrix * offset_point; - let depth_offset = offset_view.z - sprite_view.z; - - let curvature = CURVATURE_FACTOR * sprite_width; - - (depth_offset, curvature) - } } diff --git a/korangar/src/world/light/mod.rs b/korangar/src/world/light/mod.rs index 081aae92..68abace0 100644 --- a/korangar/src/world/light/mod.rs +++ b/korangar/src/world/light/mod.rs @@ -66,6 +66,7 @@ impl PointLight { &self, instructions: &mut Vec, view_projection_matrices: [Matrix4; 6], + view_matrices: [Matrix4; 6], model_texture: Arc, model_vertex_buffer: Arc>, entity_offset: [usize; 6], @@ -75,6 +76,7 @@ impl PointLight { ) { instructions.push(PointShadowCasterInstruction { view_projection_matrices, + view_matrices, position: self.position, color: self.color, range: self.range, @@ -228,6 +230,8 @@ impl PointLightSet<'_> { point_shadow_camera.set_camera_position(point_light.position); let mut view_projection_matrices = [Matrix4::identity(); NUMBER_OF_POINT_LIGHTS_WITH_SHADOWS]; + let mut view_matrices = [Matrix4::identity(); NUMBER_OF_POINT_LIGHTS_WITH_SHADOWS]; + let entity_offsets = [0; NUMBER_OF_POINT_LIGHTS_WITH_SHADOWS]; let entity_counts = [0; NUMBER_OF_POINT_LIGHTS_WITH_SHADOWS]; let mut model_offsets = [0; NUMBER_OF_POINT_LIGHTS_WITH_SHADOWS]; @@ -246,6 +250,7 @@ impl PointLightSet<'_> { point_shadow_camera.generate_view_projection(Vector2::zero()); view_projection_matrices[face_index as usize] = point_shadow_camera.view_projection_matrix(); + (view_matrices[face_index as usize], _) = point_shadow_camera.view_projection_matrices(); let model_offset = point_shadow_model_instructions.len(); @@ -262,6 +267,7 @@ impl PointLightSet<'_> { point_light.render_with_shadows( point_light_with_shadow_instructions, view_projection_matrices, + view_matrices, map.get_texture().clone(), map.get_model_vertex_buffer().clone(), entity_offsets,