Skip to main content

LIGHTS_WGSL

Constant LIGHTS_WGSL 

Source
pub const LIGHTS_WGSL: &str = r#"
const FIRST_TILE_LAYER: u32 = 3u;
struct LocalLight {
    position_radius: vec4f,
    colour_range: vec4f,
    shadow: vec4f,
    bias: vec4f,
}
struct LocalLights {
    info: vec4u,
    lights: array<LocalLight, 4>,
    faces: array<mat4x4f, 24>,
}
@group(1) @binding(4) var shadow_transmission: texture_2d_array<u32>;
@group(1) @binding(5) var<uniform> local_lights: LocalLights;
@group(1) @binding(6) var light_faces: texture_depth_2d_array;
@group(1) @binding(7) var light_sampler: sampler_comparison;
@group(1) @binding(8) var<storage, read> caster_tints: array<vec4f>;

fn caster_skips(material: u32) -> bool {
    return caster.mode.x > 0.5 && caster_tints[material].w > 0.5;
}

fn light_face(d: vec3f) -> u32 {
    let a = abs(d);
    if (a.x >= a.y && a.x >= a.z) {
        return select(1u, 0u, d.x > 0.0);
    }
    if (a.y >= a.z) {
        return select(3u, 2u, d.y > 0.0);
    }
    return select(5u, 4u, d.z > 0.0);
}

fn light_shadow(light: LocalLight, world: vec3f, normal: vec3f, toward: vec3f) -> f32 {
    if (light.shadow.x < 0.0) {
        return 1.0;
    }
    let offset = world - light.position_radius.xyz;
    let reach = max(abs(offset.x), max(abs(offset.y), abs(offset.z)));
    let texel = reach * light.shadow.y;
    let shifted = world + normal * texel * light.bias.x + toward * texel * light.bias.y;
    let layer = u32(light.shadow.x) + light_face(shifted - light.position_radius.xyz);
    let clip = local_lights.faces[layer] * vec4f(shifted, 1.0);
    if (clip.w <= 0.0) {
        return 1.0;
    }
    let ndc = clip.xyz / clip.w;
    let uv = vec2f(ndc.x, -ndc.y) * 0.5 + vec2f(0.5);
    let step = light.shadow.w / vec2f(textureDimensions(light_faces));
    var lit = 0.0;
    for (var y = -1; y <= 1; y++) {
        for (var x = -1; x <= 1; x++) {
            lit += textureSampleCompareLevel(light_faces, light_sampler, uv + vec2f(f32(x), f32(y)) * step, layer, ndc.z);
        }
    }
    return lit / 9.0;
}

fn light_tint(light: LocalLight, world: vec3f, normal: vec3f, toward: vec3f) -> vec3f {
    let tile = local_lights.info.z;
    if (tile == 0u || light.shadow.x < 0.0) {
        return vec3f(1.0);
    }
    let offset = world - light.position_radius.xyz;
    let reach = max(abs(offset.x), max(abs(offset.y), abs(offset.z)));
    let texel = reach * light.shadow.y;
    let shifted = world + normal * texel * light.bias.x + toward * texel * light.bias.y;
    let face = u32(light.shadow.x) + light_face(shifted - light.position_radius.xyz);
    let clip = local_lights.faces[face] * vec4f(shifted, 1.0);
    if (clip.w <= 0.0) {
        return vec3f(1.0);
    }
    let ndc = clip.xyz / clip.w;
    let uv = clamp(vec2f(ndc.x, -ndc.y) * 0.5 + vec2f(0.5), vec2f(0.0), vec2f(1.0));
    let columns = max(local_lights.info.w, 1u);
    let per = columns * columns;
    let slot = face % per;
    let corner = vec2i(vec2u(slot % columns, slot / columns) * tile);
    let at = corner + min(vec2i(uv * f32(tile)), vec2i(i32(tile) - 1));
    let stored = textureLoad(shadow_transmission, at, FIRST_TILE_LAYER + face / per, 0);
    return select(vec3f(1.0), unpack4x8unorm(stored.x).xyz, ndc.z > bitcast<f32>(stored.y));
}

fn local_light_radiance(surface: Shaded, wo: vec3f, t: vec3f, b: vec3f, n: vec3f, world: vec3f, geometric: vec3f, film: vec3f) -> vec3f {
    var total = vec3f(0.0);
    let count = min(local_lights.info.x, 4u);
    for (var index = 0u; index < count; index++) {
        let light = local_lights.lights[index];
        let to_light = light.position_radius.xyz - world;
        let distance = length(to_light);
        let range = light.colour_range.w;
        if (distance >= range || distance <= 1e-6) {
            continue;
        }
        let l = to_light / distance;
        let ndl = dot(n, l);
        if (ndl <= 0.0 || dot(geometric, l) <= 0.0) {
            continue;
        }
        let ratio = distance / range;
        let window = clamp(1.0 - ratio * ratio * ratio * ratio, 0.0, 1.0);
        let reach = max(distance, light.position_radius.w);
        var lit = surface;
        lit.roughness = max(surface.roughness, sqrt(clamp(light.position_radius.w / (2.0 * distance), 0.0, 1.0)));
        let wi = vec3f(dot(l, t), dot(l, b), dot(l, n));
        let radiance = light.colour_range.xyz * window * window / (reach * reach);
        total += eval_filmed(lit, wo, wi, film) * radiance * ndl * light_shadow(light, world, geometric, l) * light_tint(light, world, geometric, l);
    }
    return total;
}

fn shadow_tint_one(world: vec3f, normal: vec3f, instance: u32, cascade: i32) -> vec3f {
    var view_proj = shadow_setting.view_proj_0;
    var texel = shadow_setting.texels.x;
    if (cascade == 1) {
        view_proj = shadow_setting.view_proj_1;
        texel = shadow_setting.texels.y;
    } else if (cascade == 2) {
        view_proj = shadow_setting.view_proj_2;
        texel = shadow_setting.texels.z;
    }
    let shifted = shadow_offset(world, normal, shadow_setting.sun.xyz, shadow_setting.filter_params, texel, -1.0);
    let projected = shadow_project(view_proj, shifted);
    if (any(projected < vec3f(0.0)) || any(projected > vec3f(1.0))) {
        return vec3f(1.0);
    }
    let size = vec2i(textureDimensions(shadow_transmission));
    let at = clamp(vec2i(projected.xy * vec2f(size)), vec2i(0), size - vec2i(1));
    let stored = textureLoad(shadow_transmission, at, cascade, 0);
    let behind = projected.z - shadow_setting.sun.w > bitcast<f32>(stored.y) && stored.z != instance + 1u;
    return select(vec3f(1.0), unpack4x8unorm(stored.x).xyz, behind);
}

fn shadow_tint(world: vec3f, normal: vec3f, instance: u32) -> vec3f {
    if (local_lights.info.y == 0u || shadow_setting.texels.w > 0.5) {
        return vec3f(1.0);
    }
    let distance = length(world - frame.camera_position.xyz);
    let choice = shadow_select(distance, shadow_setting.splits, shadow_setting.filter_params.w);
    let tint = shadow_tint_one(world, normal, instance, i32(choice.x));
    if (choice.z <= 0.0) {
        return tint;
    }
    return mix(tint, shadow_tint_one(world, normal, instance, i32(choice.y)), choice.z);
}

struct TransmitOutput {
    @builtin(position) position: vec4f,
    @location(0) world_position: vec3f,
    @location(1) @interpolate(flat) instance: u32,
    @location(2) @interpolate(flat) tint: vec4f,
}

@vertex fn transmit_vertex(input: VertexInput) -> TransmitOutput {
    let instance = instances[input.instance_index];
    var output: TransmitOutput;
    output.tint = caster_tints[instance.fields.x];
    output.instance = input.instance_index;
    if (output.tint.w < 0.5) {
        output.position = vec4f(2.0, 2.0, 2.0, 1.0);
        return output;
    }
    let position = caster_position(input, instance);
    let world = instance.model * vec4f(position, 1.0);
    output.position = caster.view_proj * world;
    output.world_position = world.xyz;
    return output;
}

@fragment fn transmit_fragment(input: TransmitOutput) -> @location(0) vec4u {
    if (clipped(input.instance, input.world_position)) {
        discard;
    }
    return vec4u(pack4x8unorm(vec4f(input.tint.xyz, 1.0)), bitcast<u32>(input.position.z), input.instance + 1u, 0u);
}
"#;