viewport-lib 0.16.0

3D viewport rendering library
Documentation
// Shared scene-lighting evaluation for non-mesh pipelines.
//
// Defines the canonical `SingleLight` and `Lights` struct layouts (matching
// `src/resources/types.rs::LightsUniform`) and one helper:
//
//   apply_scene_lighting(N, base_colour, two_sided, world_pos, lights) -> vec3<f32>
//
// The helper returns `base_colour * (hemisphere_ambient + light_sum)` where
// hemisphere_ambient is mix(ground_colour, sky_colour, N.z*0.5+0.5) * intensity,
// and light_sum accumulates all active light types:
//   - Directional: Lambert weight on N·L (or abs(N·L) when two_sided)
//   - Point: distance falloff, no angular term
//   - Spot: distance falloff * inner/outer cone interpolation
//
// Consumers must:
//   - Bind a `Lights` uniform at `@group(0) @binding(3)` (every existing scivis
//     pipeline already does).
//   - Remove their local `SingleLight` and `Lights` struct definitions and
//     `// #include "scene_lighting.wgsl"` near the top of the file instead.

struct SingleLight {
    light_view_proj: mat4x4<f32>,
    pos_or_dir:      vec3<f32>,
    light_type:      u32,
    colour:           vec3<f32>,
    intensity:       f32,
    range:           f32,
    inner_angle:     f32,
    outer_angle:     f32,
    spot_direction:  vec3<f32>,
    _pad:            vec2<f32>,
};

struct Lights {
    count:                u32,
    shadow_bias:          f32,
    shadows_enabled:      u32,
    debug_vis_mode:       u32,
    sky_colour:            vec3<f32>,
    hemisphere_intensity: f32,
    ground_colour:         vec3<f32>,
    debug_vis_scale:      f32,
    lights:               array<SingleLight, 8>,
    ibl_enabled:          u32,
    ibl_intensity:        f32,
    ibl_rotation:         f32,
    show_skybox:          u32,
    debug_vis_split_x:    f32,
    _pad_dbg_a:           u32,
    _pad_dbg_b:           u32,
    _pad_dbg_c:           u32,
};

fn apply_scene_lighting(
    normal: vec3<f32>,
    base_colour: vec3<f32>,
    two_sided: bool,
    world_pos: vec3<f32>,
    lights: Lights,
) -> vec3<f32> {
    // Hemisphere ambient. Z-up world: N.z=+1 reads pure sky, N.z=-1 reads pure
    // ground. Scaled by `hemisphere_intensity` so `hemisphere_intensity = 0`
    // disables the term and items rely entirely on direct lights.
    let up_weight = clamp(normal.z * 0.5 + 0.5, 0.0, 1.0);
    let ambient = mix(lights.ground_colour, lights.sky_colour, up_weight)
                  * lights.hemisphere_intensity;

    var direct = vec3<f32>(0.0);
    let n_lights = min(lights.count, 8u);
    for (var i: u32 = 0u; i < n_lights; i = i + 1u) {
        let l = lights.lights[i];
        var L: vec3<f32>;
        var radiance: vec3<f32>;

        if l.light_type == 0u {
            // Directional: pos_or_dir is the surface-to-light direction.
            L = normalize(l.pos_or_dir);
            radiance = l.colour * l.intensity;
        } else if l.light_type == 1u {
            // Point: inverse-square-ish falloff clamped at range.
            let to_light = l.pos_or_dir - world_pos;
            let dist = length(to_light);
            L = to_light / max(dist, 0.0001);
            let falloff = clamp(1.0 - dist / l.range, 0.0, 1.0);
            radiance = l.colour * l.intensity * falloff * falloff;
        } else {
            // Spot: distance falloff * inner/outer cone attenuation.
            let to_light = l.pos_or_dir - world_pos;
            let dist = length(to_light);
            L = to_light / max(dist, 0.0001);
            let dist_falloff = clamp(1.0 - dist / l.range, 0.0, 1.0);
            let spot_dir = normalize(l.spot_direction);
            let cos_angle = dot(-L, spot_dir);
            let cos_outer = cos(l.outer_angle);
            let cos_inner = cos(l.inner_angle);
            let cone_att = clamp(
                (cos_angle - cos_outer) / max(cos_inner - cos_outer, 0.0001),
                0.0, 1.0,
            );
            radiance = l.colour * l.intensity * dist_falloff * dist_falloff * cone_att;
        }

        let raw = dot(normal, L);
        let n_dot_l = select(max(raw, 0.0), abs(raw), two_sided);
        direct = direct + radiance * n_dot_l;
    }

    return base_colour * (ambient + direct);
}