rustial-renderer-wgpu 0.0.1

Pure WGPU renderer for the rustial 2.5D map engine
Documentation
// Shadow sampling prelude for receiver shaders.
//
// Included in fill-extrusion and model fragment shaders to sample the
// cascaded shadow map and compute shadow occlusion.
//
// Expects the following to be defined in the including shader:
//   - `shadow_params` uniform with light matrices and parameters
//   - `shadow_map_0`, `shadow_map_1` depth textures
//   - `shadow_sampler` comparison sampler

struct ShadowParams {
    light_matrix_0: mat4x4<f32>,
    light_matrix_1: mat4x4<f32>,
    // [intensity, texel_size, normal_offset, cascade_split]
    shadow_config:  vec4<f32>,
    // [dir.x, dir.y, dir.z, 0]
    shadow_dir:     vec4<f32>,
};

/// Transform a world-space position into shadow-map UV + depth for a cascade.
fn shadow_coords(light_matrix: mat4x4<f32>, world_pos: vec3<f32>) -> vec3<f32> {
    let light_clip = light_matrix * vec4<f32>(world_pos, 1.0);
    // Perspective divide (orthographic, but still needed for mat4 correctness).
    let ndc = light_clip.xyz / light_clip.w;
    // Map from NDC [-1,1] XY → [0,1] UV.  Z is already [0,1] in WGPU.
    return vec3<f32>(ndc.x * 0.5 + 0.5, -ndc.y * 0.5 + 0.5, ndc.z);
}

/// Sample shadow occlusion for a world-space position.
///
/// Returns a value in [0, 1] where 0 = fully lit and 1 = fully shadowed.
fn shadow_occlusion(
    world_pos: vec3<f32>,
    normal: vec3<f32>,
    params: ShadowParams,
    map_0: texture_depth_2d,
    map_1: texture_depth_2d,
    samp: sampler_comparison,
) -> f32 {
    let intensity = params.shadow_config.x;
    if intensity < 0.001 {
        return 0.0;
    }

    let texel_size = params.shadow_config.y;
    let normal_offset_scale = params.shadow_config.z;
    let cascade_split = params.shadow_config.w;

    // Normal offset to reduce shadow acne.
    let sun_dir = normalize(params.shadow_dir.xyz);
    let ndotl = max(dot(normal, sun_dir), 0.0);
    let offset_scale = normal_offset_scale * (1.0 - ndotl);
    let offset_pos = world_pos + normal * offset_scale;

    // Try cascade 0 first.
    let coords0 = shadow_coords(params.light_matrix_0, offset_pos);
    if coords0.x >= 0.0 && coords0.x <= 1.0 && coords0.y >= 0.0 && coords0.y <= 1.0
        && coords0.z >= 0.0 && coords0.z <= 1.0 {
        let shadow = textureSampleCompare(map_0, samp, coords0.xy, coords0.z);
        return (1.0 - shadow) * intensity;
    }

    // Fallback to cascade 1.
    let coords1 = shadow_coords(params.light_matrix_1, offset_pos);
    if coords1.x >= 0.0 && coords1.x <= 1.0 && coords1.y >= 0.0 && coords1.y <= 1.0
        && coords1.z >= 0.0 && coords1.z <= 1.0 {
        // Fade out at the far edge.
        let fade_start = 0.75;
        let edge = max(
            max(abs(coords1.x * 2.0 - 1.0), abs(coords1.y * 2.0 - 1.0)),
            0.0
        );
        let fade = smoothstep(fade_start, 1.0, edge);
        let shadow = textureSampleCompare(map_1, samp, coords1.xy, coords1.z);
        return (1.0 - shadow) * intensity * (1.0 - fade);
    }

    // Outside all cascades — no shadow.
    return 0.0;
}