Files
wsg/lib/src/shaders/shadow_shader.wgsl
T
Jérôme Bousquié c2cbd7fadb Étape 14: add shadow mapping (directional light, Phase 4.2)
Implement shadow mapping for directional lights:
- Scene::set_shadow_caster(Option<usize>) selects the shadow-casting light
  by packed frame-array index (None disables; point lights rejected at render).
- Lights::get(index) resolves a packed index across the directional/point/spot lists.
- Renderer allocates a shadow depth map, comparison sampler, group-3 bind groups,
  shadow uniform buffer and shadow pipeline; render_scene does a depth-only
  shadow pass before the main pass; compute_shadow_light_view_proj builds an
  orthographic light-space frustum from the scene radius.
- standard_shader: shadow_light_index/light_view_proj/shadow_params uniforms,
  @group(3) depth map + comparison sampler, 3x3 PCF compute_shadow().
- shadow_shader: path/vertex shader with attribute layout matching the shared
  vertex buffer (only position consumed).
- shadow_test example: directional shadow caster casts a PCF-softened shadow
  onto a ground slab; documented in examples README.
2026-09-19 09:48:17 +02:00

49 lines
1.9 KiB
WebGPU Shading Language

//! # Shadow Shader (Étape 14, Phase 4.2 — depth-only pass)
//!
//! Minimal vertex shader used for the **shadow map pass** (DRAFT Étape 14, D4). It transforms each
//! vertex into the light's clip space and lets the depth write happen — no fragment stage, no color
//! output, no lighting : the rasterizer only records the depth (D2).
//!
//! Only the `position` attribute (location 0) is consumed, so this pipeline needs no normal/uv/color
//! buffers and is as cheap as possible.
//!
//! ## Uniform Contract (this pipeline's own layout — independent of the main pipeline)
//! - `@group(0) @binding(0)` : `ShadowUniform` — the light's `view_proj` matrix (world → light clip).
//! - `@group(1) @binding(0)` : `ObjectUniform` — the entity's per-entity model matrix (shared with
//! the main pipeline, so the Renderer reuses its per-entity object bind groups).
//!
//! The light VP is passed as a group-0 uniform rather than reusing the camera `FrameUniforms`
//! because the shadow pass is rendered from the light's point of view, not the camera's.
struct ShadowUniform {
view_proj: mat4x4<f32>,
};
struct ObjectUniform {
model: mat4x4<f32>,
};
@group(0) @binding(0) var<uniform> shadow: ShadowUniform;
@group(1) @binding(0) var<uniform> object: ObjectUniform;
struct VertexInput {
@location(0) position: vec3<f32>,
@location(1) normal: vec3<f32>,
@location(2) uv: vec2<f32>,
@location(3) color: vec4<f32>,
};
// Output carries only the clip position; any attribute interpolated without a fragment stage is
// still fine (it is simply discarded). Keeping just the position minimizes the vertex output size.
struct VertexOutput {
@builtin(position) clip_position: vec4<f32>,
};
@vertex
fn vs_main(input: VertexInput) -> VertexOutput {
var out: VertexOutput;
let world = object.model * vec4<f32>(input.position, 1.0);
out.clip_position = shadow.view_proj * world;
return out;
}