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);
}
"#;