feat: multi-lumières directionnelles + ponctuelles (Étape 12, Phase 4.2)
- Light (48 o) + MAX_LIGHTS=8 ; FrameUniforms étendu (ambient, lights[8], compteurs, _pad) - resources/lights.rs : Lights (1 dir +Z par défaut, non-régression) + into_frame_array - Scene : API déclarative add_directional_light / add_point_light / set_ambient / clear_lights - Renderer::write_frame_uniforms upload les lumières/ambiant de la scène - standard_shader.wgsl : struct Light + boucles d'accumulation (dir + ponctuelles, atténuation linéaire) - exemple cube : 1 lumière ponctuelle chaude - Docs : shaders/resources README, README roadmap, ROADMAP (item coché + Spot notée futur) - Build/test/fmt OK ; non-régression par défaut (1 dir +Z + ambiant blanc)
This commit is contained in:
@@ -8,24 +8,27 @@
|
||||
//!
|
||||
//! ## 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 bytes]
|
||||
//! - `@group(0) @binding(0)` : `FrameUniforms` (per-frame, camera + lights) [192 + 48·MAX_LIGHTS 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)
|
||||
//!
|
||||
//! `FrameUniforms` layout (std140 — each element 16-byte aligned, no padding) :
|
||||
//! | Offset | Field | Type | Meaning |
|
||||
//! |--------|--------------|-----------|-----------------------------------|
|
||||
//! | 0 | view | mat4x4<f32> | Camera view matrix |
|
||||
//! | 64 | proj | mat4x4<f32> | Camera projection matrix |
|
||||
//! | 128 | cam_pos | vec4<f32> | Camera world position (.xyz) |
|
||||
//! | 144 | light_dir | vec4<f32> | Light direction (see below) |
|
||||
//! | 160 | light_color | vec4<f32> | Light color (.rgb) |
|
||||
//! | 176 | options | vec4<u32> | x = unlit flag (1 => flat color) |
|
||||
//! | 192 | total | | |
|
||||
//! `FrameUniforms` layout (std140 — each element 16-byte aligned) :
|
||||
//! | Offset | Field | Type | Meaning |
|
||||
//! |-----------------------|----------------|---------------|----------------------------------|
|
||||
//! | 0 | view | mat4x4<f32> | Camera view matrix |
|
||||
//! | 64 | proj | mat4x4<f32> | Camera projection matrix |
|
||||
//! | 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) |
|
||||
//! | | num_point | u32 | # point (indices n..) |
|
||||
//! | | options | vec4<u32> | x = unlit flag (1 => flat color) |
|
||||
//!
|
||||
//! `light_dir` convention : vector pointing **from the surface toward the light**.
|
||||
//! The fragment shader negates it to obtain the light direction for the N·L term.
|
||||
//! `MAX_LIGHTS = 8`. `struct Light` is 48 bytes (3 × 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).
|
||||
//!
|
||||
//! ## Texturing (Étape 10, D2)
|
||||
//! The fragment samples `diffuse_texture` **unconditionally**. A texture-less `Material` binds the
|
||||
@@ -52,13 +55,28 @@ struct VertexInput {
|
||||
@location(3) color: vec4<f32>,
|
||||
};
|
||||
|
||||
// Étape 12 (Phase 4.2) : maximum number of lights in the per-frame array. Must match
|
||||
// `wsg_lib::resources::MAX_LIGHTS`.
|
||||
const MAX_LIGHTS: u32 = 8u;
|
||||
|
||||
// A single light (48 bytes = 3 × 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).
|
||||
struct Light {
|
||||
position_dir: vec4<f32>,
|
||||
color: vec4<f32>, // rgb = color; a = intensity
|
||||
radius: vec4<f32>, // x = point-light attenuation radius
|
||||
};
|
||||
|
||||
struct FrameUniforms {
|
||||
view: mat4x4<f32>,
|
||||
proj: mat4x4<f32>,
|
||||
cam_pos: vec4<f32>,
|
||||
light_dir: vec4<f32>,
|
||||
light_color: vec4<f32>,
|
||||
options: vec4<u32>, // .x : unlit flag (1 = flat color, no directional lighting)
|
||||
ambient: vec4<f32>, // .rgb = ambient hemisphere color
|
||||
lights: array<Light, MAX_LIGHTS>, // [0..num_directional] directional, then point
|
||||
num_directional: u32,
|
||||
num_point: u32,
|
||||
options: vec4<u32>, // .x : unlit flag (1 = flat color, no lighting)
|
||||
};
|
||||
|
||||
struct ObjectUniform {
|
||||
@@ -115,16 +133,32 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> {
|
||||
}
|
||||
|
||||
let n = normalize(in.normal);
|
||||
// light_dir pointe de la surface vers la lumière ; on inverse pour le terme N·L.
|
||||
let l = normalize(-frame.light_dir.xyz);
|
||||
let ndotl = max(dot(n, l), 0.0);
|
||||
|
||||
// Ambient hémisphérique : dépend de la composante verticale de la normale.
|
||||
// Ambient hémisphérique : dépend de la composante verticale de la normale (couleur venue
|
||||
// de frame.ambient, Étape 12 — était codée en dur via la couleur de la lumière avant).
|
||||
let sky = max(n.y, 0.0);
|
||||
let ambient = frame.light_color.rgb * (0.3 + 0.4 * sky);
|
||||
let ambient = frame.ambient.rgb * (0.3 + 0.4 * sky);
|
||||
|
||||
// Diffuse directionnel classique.
|
||||
let diffuse = frame.light_color.rgb * ndotl;
|
||||
var diffuse = vec3<f32>(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.
|
||||
for (var i = 0u; i < frame.num_directional; i++) {
|
||||
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;
|
||||
}
|
||||
|
||||
// Lumières ponctuelles (indices num_directional..num_directional + num_point). Atténuation
|
||||
// linéaire dans le rayon (zéro au-delà).
|
||||
for (var i = frame.num_directional; i < frame.num_directional + frame.num_point; 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 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;
|
||||
}
|
||||
|
||||
let lit = base * (ambient + diffuse);
|
||||
return vec4<f32>(lit, in.color.a);
|
||||
|
||||
Reference in New Issue
Block a user