feat: lumières spot (cône + angle) (Étape 13, Phase 4.2)
- Light étendu à 4×Vec4 (64 o) : dir_angle (axe du cône + cos demi-angle) - FrameUniforms 576→704 o : compteur num_spot, _pad[1] - Lights + spot: Vec<Light> ; into_frame_array renvoie (arr, n_dir, n_point, n_spot) - Scene::add_spot_light(pos, dir, color, intensity, radius, half_angle) ; clear_lights inclut spot - standard_shader.wgsl : 3e boucle d'accumulation (pénombre lissée ±0.1 rad + atténuation linéaire) - exemple cube : lumière spot verte pointée vers le cube - Docs : shaders/resources README (704 B), README roadmap (item 11), ROADMAP (item coché) - Build/test/fmt OK (5 tests + validation WGSL + doctests) ; non-régression par défaut
This commit is contained in:
@@ -14,7 +14,7 @@ unlit** de `standard` (décision actée dans le DRAFT : « 2D ⊂ 3D »).
|
||||
|
||||
| File | Purpose |
|
||||
|------|---------|
|
||||
| **standard_shader.wgsl** | Standard (Phong) vertex/fragment shader — ambient + multi-light (directional + point) diffuse with an explicit unlit mode. Carries the full uniform contract (frame @group(0) + object @group(1) + texture @group(2)). |
|
||||
| **standard_shader.wgsl** | Standard (Phong) vertex/fragment shader — ambient + multi-light (directional + point + spot) diffuse with an explicit unlit mode. Carries the full uniform contract (frame @group(0) + object @group(1) + texture @group(2)). |
|
||||
|
||||
## Shader Contract (standard_shader.wgsl)
|
||||
|
||||
@@ -34,14 +34,16 @@ par tout matériau (Étape 3 : un seul layout pour tous).
|
||||
|
||||
| Group / Binding | Struct | Contenu |
|
||||
|-----------------|--------|---------|
|
||||
| `@group(0) @binding(0)` | `FrameUniforms` (576 B) | `view`, `proj`, `cam_pos`, `ambient`, `lights[8]`, `num_directional`, `num_point`, `options` (.x = unlit flag) |
|
||||
| `@group(0) @binding(0)` | `FrameUniforms` (704 B) | `view`, `proj`, `cam_pos`, `ambient`, `lights[8]`, `num_directional`, `num_point`, `num_spot`, `options` (.x = unlit flag) |
|
||||
| `@group(1) @binding(0)` | `ObjectUniform` (64 B) | `model` (matrice modèle de l'entité) |
|
||||
|
||||
`FrameUniforms` porte une **liste de lumières globales** (Étape 12, Phase 4.2) : `lights[0..num_directional]`
|
||||
sont des lumières **directionnelles** (`position_dir.xyz` = direction de la surface vers la lumière), et
|
||||
`lights[num_directional..]` des lumières **ponctuelles** (`position_dir.xyz` = position monde, `radius.x` =
|
||||
rayon d'atténuation linéaire). L'index disambiguise le type — pas de drapeau. `ambient` est la couleur du
|
||||
terme ambiant hémisphérique.
|
||||
`FrameUniforms` porte une **liste de lumières globales** (Étapes 12–13, Phase 4.2) : `lights[0..num_directional]`
|
||||
sont des lumières **directionnelles** (`position_dir.xyz` = direction de la surface vers la lumière),
|
||||
`lights[num_directional..num_directional + num_point]` des lumières **ponctuelles**
|
||||
(`position_dir.xyz` = position monde, `radius.x` = rayon d'atténuation linéaire), et
|
||||
`lights[num_directional + num_point..]` des lumières **spot** (`position_dir.xyz` = position monde,
|
||||
`dir_angle.xyz` = axe du cône de la lumière vers la scène, `dir_angle.w` = cos du demi-angle).
|
||||
L'index disambiguise le type — pas de drapeau. `ambient` est la couleur du terme ambiant hémisphérique.
|
||||
|
||||
### Mode unlit
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//!
|
||||
//! ## Uniform Contract
|
||||
//! Three bind groups, shared by every material (one single pipeline layout — voir Étape 3) :
|
||||
//! - `@group(0) @binding(0)` : `FrameUniforms` (per-frame, camera + lights) [192 + 48·MAX_LIGHTS bytes]
|
||||
//! - `@group(0) @binding(0)` : `FrameUniforms` (per-frame, camera + lights) [704 bytes]
|
||||
//! - `@group(1) @binding(0)` : `ObjectUniform` (per-entity model matrix) [64 bytes]
|
||||
//! - `@group(2) @binding(0)` : `texture_sampler` (sampler) — diffuse (Étape 10)
|
||||
//! - `@group(2) @binding(1)` : `diffuse_texture` (texture_2d<f32>) (Étape 10)
|
||||
@@ -21,14 +21,18 @@
|
||||
//! | 128 | cam_pos | vec4<f32> | Camera world position (.xyz) |
|
||||
//! | 144 | ambient | vec4<f32> | Ambient hemisphere color (.rgb) |
|
||||
//! | 160 | lights[0..MAX] | array<Light> | Global light list |
|
||||
//! | 160 + 48·MAX_LIGHTS | num_directional| u32 | # directional (indices 0..n) |
|
||||
//! | 160 + 64·MAX_LIGHTS | num_directional| u32 | # directional (indices 0..n) |
|
||||
//! | | num_point | u32 | # point (indices n..) |
|
||||
//! | | num_spot | u32 | # spot (indices after point) |
|
||||
//! | | options | vec4<u32> | x = unlit flag (1 => flat color) |
|
||||
//!
|
||||
//! `MAX_LIGHTS = 8`. `struct Light` is 48 bytes (3 × vec4). Directional lights occupy
|
||||
//! `MAX_LIGHTS = 8`. `struct Light` is 64 bytes (4 × vec4). Directional lights occupy
|
||||
//! `lights[0..num_directional]` (`position_dir.xyz` = direction **from the surface toward the
|
||||
//! light**); point lights occupy `lights[num_directional..]` (`position_dir.xyz` = world position,
|
||||
//! `radius.x` = linear attenuation radius). No type flag — the index disambiguates (Étape 12).
|
||||
//! light**); point lights occupy `lights[num_directional..num_directional + num_point]`
|
||||
//! (`position_dir.xyz` = world position, `radius.x` = linear attenuation radius); spot lights
|
||||
//! occupy `lights[num_directional + num_point..]` (`position_dir.xyz` = world position,
|
||||
//! `dir_angle.xyz` = cone axis from the light toward the scene, `dir_angle.w` = cos of the
|
||||
//! half-angle). No type flag — the index disambiguates (Étapes 12–13).
|
||||
//!
|
||||
//! ## Texturing (Étape 10, D2)
|
||||
//! The fragment samples `diffuse_texture` **unconditionally**. A texture-less `Material` binds the
|
||||
@@ -59,13 +63,16 @@ struct VertexInput {
|
||||
// `wsg_lib::resources::MAX_LIGHTS`.
|
||||
const MAX_LIGHTS: u32 = 8u;
|
||||
|
||||
// A single light (48 bytes = 3 × vec4). Directional: `position_dir.xyz` = direction from the
|
||||
// A single light (64 bytes = 4 × vec4). Directional: `position_dir.xyz` = direction from the
|
||||
// surface toward the light. Point: `position_dir.xyz` = world position, `radius.x` = linear
|
||||
// attenuation radius. The array index disambiguates the type (no flag stored).
|
||||
// attenuation radius. Spot: `position_dir.xyz` = world position, `dir_angle.xyz` = cone axis
|
||||
// (from the light toward the scene), `dir_angle.w` = cos of the half-angle. The array index
|
||||
// disambiguates the type (no flag stored).
|
||||
struct Light {
|
||||
position_dir: vec4<f32>,
|
||||
color: vec4<f32>, // rgb = color; a = intensity
|
||||
radius: vec4<f32>, // x = point-light attenuation radius
|
||||
radius: vec4<f32>, // x = point/spot attenuation radius
|
||||
dir_angle: vec4<f32>, // spot: xyz = cone axis, w = cos(half-angle)
|
||||
};
|
||||
|
||||
struct FrameUniforms {
|
||||
@@ -73,9 +80,10 @@ struct FrameUniforms {
|
||||
proj: mat4x4<f32>,
|
||||
cam_pos: vec4<f32>,
|
||||
ambient: vec4<f32>, // .rgb = ambient hemisphere color
|
||||
lights: array<Light, MAX_LIGHTS>, // [0..num_directional] directional, then point
|
||||
lights: array<Light, MAX_LIGHTS>, // directional, then point, then spot
|
||||
num_directional: u32,
|
||||
num_point: u32,
|
||||
num_spot: u32,
|
||||
options: vec4<u32>, // .x : unlit flag (1 = flat color, no lighting)
|
||||
};
|
||||
|
||||
@@ -160,6 +168,23 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
diffuse += frame.lights[i].color.rgb * frame.lights[i].color.a * ndotl * falloff;
|
||||
}
|
||||
|
||||
// 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.
|
||||
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 ndotl = max(dot(n, l), 0.0);
|
||||
let cone = dot(l, 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);
|
||||
let falloff = clamp(1.0 - dist / max(frame.lights[i].radius.x, 1e-4), 0.0, 1.0);
|
||||
diffuse += frame.lights[i].color.rgb * frame.lights[i].color.a * ndotl * falloff * spot_factor;
|
||||
}
|
||||
|
||||
let lit = base * (ambient + diffuse);
|
||||
return vec4<f32>(lit, in.color.a);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user