diff --git a/lib/src/core/renderer.rs b/lib/src/core/renderer.rs index ca084d8..bf31642 100644 --- a/lib/src/core/renderer.rs +++ b/lib/src/core/renderer.rs @@ -161,11 +161,14 @@ impl Renderer { mag_filter: wgpu::FilterMode::Linear, min_filter: wgpu::FilterMode::Linear, mipmap_filter: wgpu::MipmapFilterMode::Nearest, - // Comparison sampler : `textureSampleCompare` returns 1 when the sampled depth passes - // this test against the reference, 0 otherwise (D5). LessEqual = lit when the fragment - // is no farther from the light than the surface recorded in the shadow map (the map is - // cleared to 1.0 = far, so open un-blocked texels pass and surfaces behind a blocker fail). - compare: Some(wgpu::CompareFunction::LessEqual), + // The shadow map uses WebGPU `[0,1]` clip depth: the light's orthographic + // projection is built with glam's `directx` (WebGPU) module so NDC z is already in + // [0,1] and matches the `current_depth` computed in the main-pass shader. The + // comparison sampler compares the recorded depth against the reference: a surface is + // LIT when it is no farther from the light than the depth recorded in the map, i.e. + // `stored_depth >= reference` (GreaterEqual). The map is cleared to 1.0 (far), so + // un-blocked texels pass and surfaces behind a blocker fail. + compare: Some(wgpu::CompareFunction::GreaterEqual), ..Default::default() }); let shadow_map_layout = create_shadow_map_bind_group_layout(&device); @@ -362,10 +365,14 @@ impl Renderer { // Avoid a degenerate basis when the light points straight down/up (parallel up vector). let up = if dir.y.abs() > 0.99 { Vec3::Z } else { Vec3::Y }; let view = glam::camera::rh::view::look_at_mat4(eye, target, up); - // Orthographic box of half-size r, near 0, far r (D1/D3), in the same OpenGL NDC convention - // as the camera projection (wgpu maps NDC z ∈ [-1,1] to depth [0,1], see standard_shader). + // Orthographic box of half-size r, near 0, far 2·r (D1/D3), in the WebGPU `[0,1]` NDC + // convention (glam `directx` module), which matches the depth range wgpu writes to the + // shadow map and the `current_depth` computed by the main-pass shader. The eye sits one + // scene-radius behind the target, so the box [−r, r] around the target spans a depth range + // of [0, 2r] from the eye: `far = 2·r` covers the whole box (and the shadows cast behind + // it), whereas `far = r` would clip the far half. let proj = - glam::camera::rh::proj::opengl::orthographic(-r, r, -r, r, 0.0, r); + glam::camera::rh::proj::directx::orthographic(-r, r, -r, r, 0.0, 2.0 * r); Some((index, proj * view)) } diff --git a/lib/src/resources/camera.rs b/lib/src/resources/camera.rs index c668833..73a2e2f 100644 --- a/lib/src/resources/camera.rs +++ b/lib/src/resources/camera.rs @@ -91,6 +91,10 @@ impl Camera { /// # Returns /// A `Mat4` representing the projection transformation matrix (view → clip space) pub fn projection_matrix(&self, aspect: f32) -> Mat4 { - glam::camera::rh::proj::opengl::perspective(self.fov, aspect, self.near, self.far) + // WebGPU expects NDC clip depth in [0,1]; glam's `opengl` module remaps to [-1,1], which + // would clip roughly the front half of the frustum in wgpu. The `directx` (WebGPU) module + // produces Y-up right-handed projections with depth already in [0,1], matching the shadow + // projections and the depth wgpu writes. + glam::camera::rh::proj::directx::perspective(self.fov, aspect, self.near, self.far) } } diff --git a/lib/src/shaders/standard_shader.wgsl b/lib/src/shaders/standard_shader.wgsl index 24c2625..d33f03e 100644 --- a/lib/src/shaders/standard_shader.wgsl +++ b/lib/src/shaders/standard_shader.wgsl @@ -162,9 +162,10 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4 { var diffuse = vec3(0.0); // Lumières directionnelles (indices 0..num_directional). `position_dir` pointe de la surface - // vers la lumière ; on l'inverse pour le terme N·L. + // vers la lumière, donc on l'utilise tel quel pour le terme N·L (dot(n, direction vers la + // lumière) > 0 = face éclairée). for (var i = 0u; i < frame.num_directional; i++) { - let l = normalize(-frame.lights[i].position_dir.xyz); + let l = normalize(frame.lights[i].position_dir.xyz); let ndotl = max(dot(n, l), 0.0); diffuse += frame.lights[i].color.rgb * frame.lights[i].color.a * ndotl; } @@ -220,7 +221,9 @@ fn compute_shadow(world_pos: vec3) -> f32 { let shadow_ndc = light_clip.xyz / max(light_clip.w, 1e-6); var shadow_uv = shadow_ndc.xy * 0.5 + 0.5; shadow_uv = vec2(shadow_uv.x, 1.0 - shadow_uv.y); // flip V for texture coordinates - let current_depth = shadow_ndc.z * 0.5 + 0.5; + // The light projection is built with the WebGPU `[0,1]` clip-depth convention (glam + // directx/WebGPU module), so NDC z is already in [0,1]: no extra remap is needed. + let current_depth = shadow_ndc.z; let bias = frame.shadow_params.y; let texel = 1.0 / max(frame.shadow_params.x, 1.0);