Constant SHADOW_WGSL
Source pub const SHADOW_WGSL: &str = r#"
override use_soft_search: bool = true;
override use_soft_filter: bool = true;
override use_cascade_blend: bool = true;
struct ShadowSample {
splits: vec4f,
filter_params: vec4f,
sun: vec4f,
texels: vec4f,
view_proj_0: mat4x4f,
view_proj_1: mat4x4f,
view_proj_2: mat4x4f,
soft: vec4f,
soft_taps: vec4f,
}
fn shadow_select(distance: f32, splits: vec4f, blend: f32) -> vec3f {
var cascade = 0.0;
if (distance > splits.y) {
cascade = 1.0;
}
if (distance > splits.z) {
cascade = 2.0;
}
var next = cascade;
var t = 0.0;
if (cascade < 2.0) {
let start = select(splits.y, splits.x, cascade < 0.5);
let end = select(splits.z, splits.y, cascade < 0.5);
let band = (end - start) * blend;
if (band > 0.0 && distance > end - band) {
next = cascade + 1.0;
t = clamp((distance - (end - band)) / band, 0.0, 1.0);
}
}
return vec3f(cascade, next, t);
}
fn shadow_blend(closer: f32, farther: f32, t: f32) -> f32 {
return mix(closer, farther, t);
}
fn shadow_project(view_proj: mat4x4f, world: vec3f) -> vec3f {
let clip = view_proj * vec4f(world, 1.0);
let ndc = clip.xyz / clip.w;
let uv = vec2f(ndc.x, -ndc.y) * 0.5 + vec2f(0.5, 0.5);
return vec3f(uv, ndc.z);
}
fn shadow_pcf(
map: texture_depth_2d_array,
samp: sampler_comparison,
uv: vec2f,
layer: i32,
depth: f32,
radius: f32,
) -> f32 {
let size = textureDimensions(map);
let texel = vec2f(1.0 / f32(size.x), 1.0 / f32(size.y));
var lit = 0.0;
for (var y = -1; y <= 1; y++) {
for (var x = -1; x <= 1; x++) {
let at = uv + vec2f(f32(x), f32(y)) * texel * radius;
lit += textureSampleCompareLevel(map, samp, at, layer, depth);
}
}
return lit / 9.0;
}
fn shadow_disc(index: u32, count: u32, turn: f32) -> vec2f {
let radius = sqrt((f32(index) + 0.5) / f32(count));
let angle = f32(index) * 2.39996323 + turn;
return vec2f(cos(angle), sin(angle)) * radius;
}
fn shadow_turn(world: vec3f) -> f32 {
let p = fract(world * vec3f(443.897, 441.423, 437.195));
let q = p + dot(p, p.yzx + 19.19);
return fract((q.x + q.y) * q.z) * 6.2831853;
}
fn shadow_slope(view_proj: mat4x4f, normal: vec3f, size: vec2f, scales: vec2f, limit: f32) -> vec2f {
let a0 = view_proj[0].xyz;
let a1 = view_proj[1].xyz;
let a2 = view_proj[2].xyz;
let len = length(normal);
if (len < 1e-6) {
return vec2f(0.0);
}
let n = normal / len;
let m = n.x * cross(a1, a2) + n.y * cross(a2, a0) + n.z * cross(a0, a1);
let denom = select(-1e-6, m.z, abs(m.z) >= 1e-6);
let g = -m.xy / denom;
var per_texel = vec2f(g.x * 2.0 / size.x, -g.y * 2.0 / size.y);
let tangent = length(per_texel) * scales.x * scales.y;
if (tangent > limit) {
per_texel = per_texel * (limit / tangent);
}
return per_texel;
}
fn shadow_soft(
map: texture_depth_2d_array,
samp: sampler_comparison,
gather: sampler,
view_proj: mat4x4f,
projected: vec3f,
layer: i32,
depth: f32,
radius: f32,
soft: vec4f,
taps: vec4f,
world: vec3f,
normal: vec3f,
) -> f32 {
let size = vec2f(textureDimensions(map));
let per_depth = 1.0 / max(length(vec3f(view_proj[0].z, view_proj[1].z, view_proj[2].z)), 1e-8);
let per_world = 0.5 * length(vec3f(view_proj[0].x, view_proj[1].x, view_proj[2].x)) * size.x;
let slope = shadow_slope(view_proj, normal, size, vec2f(per_depth, per_world), soft.w);
let turn = shadow_turn(world);
let reach = min(depth * per_depth * soft.x * per_world, soft.y);
let count = u32(soft.z);
let filter_count = u32(taps.y);
let search = max(reach, radius);
if (!use_soft_search) {
return shadow_soft_filter(map, samp, projected, layer, depth, search, filter_count, turn, slope);
}
let centre = projected.xy * size;
let gathers = u32(taps.x);
var blockers = 0.0;
var total = 0.0;
var near = vec3f(-1e30, 0.0, 0.0);
var far = false;
var inner = false;
var tested = count;
if (gathers > 0u) {
tested = gathers * 4u;
for (var i = 0u; i < gathers; i++) {
let at = round(centre + shadow_disc(i, gathers, turn) * search);
let stored = textureGather(map, gather, at / size, layer);
let corner = at - vec2f(0.5) - centre;
let offsets = array<vec2f, 4>(corner + vec2f(0.0, 1.0), corner + vec2f(1.0, 1.0), corner + vec2f(1.0, 0.0), corner);
for (var k = 0u; k < 4u; k++) {
let shift = dot(slope, offsets[k]);
if (stored[k] < depth + shift) {
let level = stored[k] - shift;
far = far || stored[k] <= 0.0;
inner = inner || stored[k] > 0.0;
blockers += 1.0;
total += level;
near = shadow_near(near, level, depth);
}
}
}
} else {
for (var i = 0u; i < count; i++) {
let at = centre + shadow_disc(i, count, turn) * search;
let texel = clamp(vec2i(floor(at)), vec2i(0), vec2i(size) - vec2i(1));
let stored = textureLoad(map, texel, layer, 0);
let here = depth + dot(slope, vec2f(texel) + vec2f(0.5) - centre);
if (stored < here) {
let level = stored - dot(slope, vec2f(texel) + vec2f(0.5) - centre);
far = far || stored <= 0.0;
inner = inner || stored > 0.0;
blockers += 1.0;
total += level;
near = shadow_near(near, level, depth);
}
}
}
let flags = u32(taps.z);
if (blockers < 0.5 && (flags & 8u) == 0u) {
return 1.0;
}
if (!use_soft_filter) {
return 1.0 - blockers / f32(tested);
}
if ((flags & 2u) != 0u && blockers > f32(tested) - 0.5) {
return 0.0;
}
let clustered = (flags & 1u) != 0u && near.z * 2.0 >= blockers;
let blocker = select(select(total / blockers, near.y / near.z, clustered), depth, blockers < 0.5);
let width = clamp((depth - blocker) * per_depth * soft.x * per_world, radius, soft.y);
var lit = 1.0;
if ((flags & 16u) == 0u || clustered || near.z < 0.5) {
lit = shadow_soft_filter(map, samp, projected, layer, depth, width, filter_count, turn, slope);
} else {
let above = textureLoad(map, clamp(vec2i(floor(centre)), vec2i(0), vec2i(size) - vec2i(1)), layer, 0);
let seen = above < depth && above > 0.0;
let top = select(near.x, above, seen);
let level = select(near.y / near.z, above, seen);
let close = clamp((depth - level) * per_depth * soft.x * per_world, radius, soft.y);
if (close >= width * 0.5) {
lit = shadow_soft_filter(map, samp, projected, layer, depth, width, filter_count, turn, slope);
} else {
let band = top - 0.5 * max(depth - top, 0.0);
lit = shadow_layered_filter(map, samp, projected, layer, depth, band, width, close, filter_count, turn, slope);
}
}
if ((flags & 64u) == 0u || !far || lit <= 0.0) {
return lit;
}
let hidden = clamp(select(0.0, (depth - near.y / near.z) * per_depth * soft.x * per_world, inner), radius, soft.y);
return min(lit, shadow_soft_filter(map, samp, projected, 1, depth, hidden, filter_count, turn, slope));
}
fn shadow_layered_filter(
map: texture_depth_2d_array,
samp: sampler_comparison,
projected: vec3f,
layer: i32,
depth: f32,
band: f32,
width: f32,
close: f32,
count: u32,
turn: f32,
slope: vec2f,
) -> f32 {
let size = vec2f(textureDimensions(map));
var lit = 0.0;
for (var i = 0u; i < count; i++) {
let disc = shadow_disc(i, count, turn + 1.7);
let far = disc * width;
let far_lit = textureSampleCompareLevel(map, samp, projected.xy + far / size, layer, band + dot(slope, far));
let near = disc * close;
let at = projected.xy + near / size;
let shift = dot(slope, near);
let near_blocked = textureSampleCompareLevel(map, samp, at, layer, band + shift) - textureSampleCompareLevel(map, samp, at, layer, depth + shift);
lit += far_lit * (1.0 - clamp(near_blocked, 0.0, 1.0));
}
return lit / f32(count);
}
fn shadow_near(near: vec3f, level: f32, depth: f32) -> vec3f {
if (level > near.x + 0.5 * max(depth - level, 0.0)) {
return vec3f(level, level, 1.0);
}
if (level >= near.x - 0.5 * max(depth - near.x, 0.0)) {
return vec3f(max(near.x, level), near.y + level, near.z + 1.0);
}
return near;
}
fn shadow_soft_filter(
map: texture_depth_2d_array,
samp: sampler_comparison,
projected: vec3f,
layer: i32,
depth: f32,
width: f32,
count: u32,
turn: f32,
slope: vec2f,
) -> f32 {
let size = vec2f(textureDimensions(map));
var lit = 0.0;
for (var i = 0u; i < count; i++) {
let step = shadow_disc(i, count, turn + 1.7) * width;
let at = projected.xy + step / size;
lit += textureSampleCompareLevel(map, samp, at, layer, depth + dot(slope, step));
}
return lit / f32(count);
}
fn shadow_offset(world: vec3f, normal: vec3f, sun: vec3f, filter_params: vec4f, texel: f32, soft: f32) -> vec3f {
let len = length(normal);
let n = select(vec3f(0.0, 1.0, 0.0), normal / max(len, 1e-8), len > 1e-6);
let slen = length(sun);
let toward = select(vec3f(0.0, 1.0, 0.0), sun / max(slen, 1e-8), slen > 1e-6);
let ndotl = clamp(dot(n, toward), 0.0, 1.0);
let slope = sqrt(max(1.0 - ndotl * ndotl, 0.0));
let scale = select(1.0, soft, soft >= 0.0 && ndotl * ndotl * 17.0 >= 1.0);
return world + n * texel * (filter_params.y + filter_params.z * slope) * scale;
}
fn shadow_one(
map: texture_depth_2d_array,
samp: sampler_comparison,
gather: sampler,
data: ShadowSample,
world: vec3f,
normal: vec3f,
cascade: i32,
) -> f32 {
var view_proj = data.view_proj_0;
var texel = data.texels.x;
if (cascade == 1) {
view_proj = data.view_proj_1;
texel = data.texels.y;
} else if (cascade == 2) {
view_proj = data.view_proj_2;
texel = data.texels.z;
}
var layer = cascade;
let soft = select(-1.0, data.soft_taps.w, (use_soft_search || use_soft_filter) && data.soft.x > 0.0);
var projected = shadow_project(view_proj, shadow_offset(world, normal, data.sun.xyz, data.filter_params, texel, soft));
if ((any(projected < vec3f(0.0)) || any(projected > vec3f(1.0))) && (u32(data.soft_taps.z) & 32u) != 0u && cascade != 2) {
view_proj = data.view_proj_2;
texel = data.texels.z;
layer = 2;
projected = shadow_project(view_proj, shadow_offset(world, normal, data.sun.xyz, data.filter_params, texel, soft));
}
if (any(projected < vec3f(0.0)) || any(projected > vec3f(1.0))) {
return 1.0;
}
if ((use_soft_search || use_soft_filter) && data.soft.x > 0.0) {
return shadow_soft(
map,
samp,
gather,
view_proj,
projected,
layer,
projected.z - data.sun.w,
data.filter_params.x,
data.soft,
data.soft_taps,
world,
normal,
);
}
return shadow_pcf(
map,
samp,
projected.xy,
layer,
projected.z - data.sun.w,
data.filter_params.x,
);
}
fn shadow_sample(
map: texture_depth_2d_array,
samp: sampler_comparison,
gather: sampler,
data: ShadowSample,
world: vec3f,
normal: vec3f,
view_distance: f32,
) -> f32 {
if ((u32(data.soft_taps.z) & 32u) != 0u) {
return shadow_one(map, samp, gather, data, world, normal, 0);
}
let choice = shadow_select(view_distance, data.splits, data.filter_params.w);
if (use_cascade_blend && choice.z > 0.0 && (u32(data.soft_taps.z) & 4u) != 0u) {
let dither = fract(shadow_turn(world.zxy) * 0.15915494);
return shadow_one(map, samp, gather, data, world, normal, i32(select(choice.x, choice.y, dither < choice.z)));
}
let lit = shadow_one(map, samp, gather, data, world, normal, i32(choice.x));
if (!use_cascade_blend || choice.z <= 0.0) {
return lit;
}
let next = shadow_one(map, samp, gather, data, world, normal, i32(choice.y));
return shadow_blend(lit, next, choice.z);
}
"#;