c2cbd7fadb
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.
49 lines
1.9 KiB
WebGPU Shading Language
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;
|
|
}
|