fix: spot — corrige le signe du test de cône (cube noir)
Le test du cône comparait l (surface->lumière) à dir_angle.xyz (lumière->scène), deux directions opposées, donc cone ≈ -1 et facteur spot = 0 : les spots n'éclairaient rien (cube noir). On teste désormais -l (lumière->point), aligné avec l'axe du cône. Ajout d'un test Rust qui verrouille l'invariant (alignement +1 sur l'axe). Build/test/WGSL/fmt OK.
This commit is contained in:
@@ -177,4 +177,29 @@ mod tests {
|
||||
fn capacity_bounded_by_max_lights() {
|
||||
assert!(MAX_LIGHTS >= 1);
|
||||
}
|
||||
|
||||
/// Locks the spot sign convention used by the shader: for a surface point that lies on the
|
||||
/// cone axis, the alignment between the "light -> point" direction (`-l`, where `l` points
|
||||
/// from the surface toward the light) and the stored cone axis (`dir_angle.xyz`, from the
|
||||
/// light toward the scene) must be **+1** (full cone), not −1. A regression to the wrong sign
|
||||
/// would make every spot light contribute zero (black cube). Mirrors the WGSL spot loop.
|
||||
#[test]
|
||||
fn spot_cone_axis_alignment_is_positive() {
|
||||
// Spot at (0,0,3), cone axis pointing toward the origin (light -> scene).
|
||||
let light_pos = Vec3::new(0.0, 0.0, 3.0);
|
||||
let surface_point = Vec3::ZERO;
|
||||
let cone_axis = (surface_point - light_pos).normalize(); // (0,0,-1)
|
||||
|
||||
// Shader math: l points surface -> light; the cone test uses -l (light -> point).
|
||||
let l = (light_pos - surface_point).normalize(); // (0,0,1)
|
||||
let to_point = -l; // (0,0,-1)
|
||||
let cone = to_point.dot(cone_axis);
|
||||
|
||||
assert!(
|
||||
(cone - 1.0).abs() < 1e-6,
|
||||
"on-axis point must align with the cone axis (got {cone}); if it is ~-1 the spot sign is wrong"
|
||||
);
|
||||
// Sanity: the buggy expression (dot of l with the axis) would be ~ -1.
|
||||
assert!((l.dot(cone_axis) + 1.0).abs() < 1e-6);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,15 +169,19 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
}
|
||||
|
||||
// Lumières spot (indices num_directional + num_point..num_directional + num_point +
|
||||
// num_spot). Cône orienté : pénombre lissée entre le demi-angle intérieur (dir_angle.w) et
|
||||
// un liseré extérieur (demi-angle − 0.1 rad), plus atténuation linéaire par rayon.
|
||||
// num_spot). Cône orienté : on teste l'alignement de la direction **de la lumière vers le
|
||||
// point** de la surface (-l, car l pointe de la surface vers la lumière) avec l'axe du cône
|
||||
// (dir_angle.xyz, de la lumière vers la scène). Pénombre lissée entre le demi-angle intérieur
|
||||
// (dir_angle.w) et un liseré extérieur (demi-angle − 0.1 rad), plus atténuation linéaire.
|
||||
let spot_base = frame.num_directional + frame.num_point;
|
||||
for (var i = spot_base; i < spot_base + frame.num_spot; i++) {
|
||||
let to_light = frame.lights[i].position_dir.xyz - in.world_pos;
|
||||
let dist = length(to_light);
|
||||
let l = to_light / max(dist, 1e-4);
|
||||
let l = to_light / max(dist, 1e-4); // surface -> lumière
|
||||
let ndotl = max(dot(n, l), 0.0);
|
||||
let cone = dot(l, normalize(frame.lights[i].dir_angle.xyz));
|
||||
// direction lumière -> point de la surface = -l ; alignée avec l'axe du cône (dir_angle.xyz).
|
||||
let to_point = -l;
|
||||
let cone = dot(to_point, normalize(frame.lights[i].dir_angle.xyz));
|
||||
let cos_inner = frame.lights[i].dir_angle.w;
|
||||
let cos_outer = cos_inner - 0.1;
|
||||
let spot_factor = clamp((cone - cos_outer) / max(cos_inner - cos_outer, 1e-4), 0.0, 1.0);
|
||||
|
||||
Reference in New Issue
Block a user