Constant IMPOSTOR_WGSL
Source pub const IMPOSTOR_WGSL: &str = r#"
struct Params {
view_projection: mat4x4f,
camera_position: vec4f,
camera_right: vec4f,
camera_up: vec4f,
settings: vec4f,
source: vec4f,
}
struct Instance {
center_radius: vec4f,
options: vec4u,
}
struct VertexOut {
@builtin(position) clip: vec4f,
@location(0) local: vec2f,
@location(1) @interpolate(flat) instance_id: u32,
}
struct FragmentOut {
@location(0) color: vec4f,
@builtin(frag_depth) depth: f32,
}
struct Shade {
color: vec4f,
depth: f32,
}
struct Sample {
color: vec4f,
position: vec3f,
weight: f32,
}
@group(0) @binding(0) var<uniform> params: Params;
@group(0) @binding(1) var<storage, read> instances: array<Instance>;
@group(0) @binding(2) var radiance: texture_2d_array<f32>;
@group(0) @binding(3) var geometry: texture_2d<f32>;
@group(0) @binding(4) var linear_sampler: sampler;
fn oct_decode(p: vec2f) -> vec3f {
let y = 1.0 - abs(p.x) - abs(p.y);
if y < 0.0 {
return normalize(vec3f((1.0 - abs(p.y)) * select(-1.0, 1.0, p.x >= 0.0), y, (1.0 - abs(p.x)) * select(-1.0, 1.0, p.y >= 0.0)));
}
return normalize(vec3f(p.x, y, p.y));
}
fn oct_encode(d: vec3f) -> vec2f {
let p = d.xz / max(abs(d.x) + abs(d.y) + abs(d.z), 1e-20);
if d.y < 0.0 {
return vec2f((1.0 - abs(p.y)) * select(-1.0, 1.0, p.x >= 0.0), (1.0 - abs(p.x)) * select(-1.0, 1.0, p.y >= 0.0));
}
return p;
}
fn frame_basis(direction: vec3f) -> mat3x3f {
let forward = -direction;
var nominal_up = vec3f(0.0, 1.0, 0.0);
if abs(direction.y) > 0.95 { nominal_up = vec3f(0.0, 0.0, 1.0); }
let right = normalize(cross(forward, nominal_up));
let up = normalize(cross(right, forward));
return mat3x3f(right, up, forward);
}
fn hash(pixel: vec2u, object_id: u32) -> f32 {
var value = pixel.x * 1664525u + pixel.y * 1013904223u + object_id * 747796405u;
value = (value ^ (value >> 16u)) * 2246822519u;
value = (value ^ (value >> 13u)) * 3266489917u;
return f32(value ^ (value >> 16u)) * (1.0 / 4294967296.0);
}
fn geometry_sample(uv: vec2f) -> vec4f {
let size = i32(params.settings.x * params.settings.y);
return textureLoad(geometry, clamp(vec2i(uv * f32(size)), vec2i(0), vec2i(size - 1)), 0);
}
fn frame_sample(cell: vec2i, local: vec2f, inst: Instance, low: i32, high: i32, anchor_mix: f32) -> Sample {
let n = i32(params.settings.x);
let bounded = clamp(cell, vec2i(0), vec2i(n - 1));
let encoded = (vec2f(bounded) + vec2f(0.5)) * (2.0 / f32(n)) - vec2f(1.0);
var direction = oct_decode(encoded);
if params.settings.w > 0.5 { direction.y = abs(direction.y); }
let basis = frame_basis(direction);
let radius = inst.center_radius.w;
let depth_scale = radius / params.source.x;
let eye = inst.center_radius.xyz + direction * radius * 3.0;
let scale = 1.0 / 3.0;
var uv = vec2f(local.x * 0.5 + 0.5, 0.5 - local.y * 0.5);
let cell_size = 1.0 / f32(n);
var atlas_uv = (vec2f(bounded) + uv) * cell_size;
var feature = geometry_sample(atlas_uv);
for (var step = 0; step < 1; step++) {
if feature.w <= 0.0 || feature.w * depth_scale > radius * 6.0 { break; }
let ray = normalize(basis[2] + basis[0] * ((uv.x * 2.0 - 1.0) * scale) + basis[1] * ((1.0 - uv.y * 2.0) * scale));
let point = eye + ray * feature.w * depth_scale;
let card_point = inst.center_radius.xyz + params.camera_right.xyz * local.x * radius + params.camera_up.xyz * local.y * radius;
let shift = vec2f(dot(point - card_point, params.camera_right.xyz), -dot(point - card_point, params.camera_up.xyz)) / max(radius, 1e-5);
uv = clamp(uv - shift * 0.5, vec2f(0.5 / params.settings.y), vec2f(1.0 - 0.5 / params.settings.y));
atlas_uv = (vec2f(bounded) + uv) * cell_size;
feature = geometry_sample(atlas_uv);
}
let ray = normalize(basis[2] + basis[0] * ((uv.x * 2.0 - 1.0) * scale) + basis[1] * ((1.0 - uv.y * 2.0) * scale));
let position = eye + ray * feature.w * depth_scale;
let a = textureSampleLevel(radiance, linear_sampler, atlas_uv, low, 0.0);
var color = a;
if high != low { color = mix(a, textureSampleLevel(radiance, linear_sampler, atlas_uv, high, 0.0), anchor_mix); }
return Sample(color, position, feature.w * depth_scale);
}
@vertex fn vertex(@builtin(vertex_index) vertex_id: u32, @builtin(instance_index) instance_id: u32) -> VertexOut {
let corners = array<vec2f, 6>(vec2f(-1.0, -1.0), vec2f(1.0, -1.0), vec2f(-1.0, 1.0), vec2f(-1.0, 1.0), vec2f(1.0, -1.0), vec2f(1.0, 1.0));
let local = corners[vertex_id];
let inst = instances[instance_id];
let world = inst.center_radius.xyz + (params.camera_right.xyz * local.x + params.camera_up.xyz * local.y) * inst.center_radius.w;
var output: VertexOut;
output.clip = params.view_projection * vec4f(world, 1.0);
output.local = local;
output.instance_id = instance_id;
return output;
}
fn shade(local: vec2f, instance_id: u32, pixel: vec4f) -> Shade {
let inst = instances[instance_id];
var toward = params.camera_position.xyz - inst.center_radius.xyz;
if params.source.y > 0.5 { toward = params.camera_position.xyz; }
toward = normalize(toward);
var uv = oct_encode(toward);
if params.settings.w > 0.5 { uv = oct_encode(vec3f(toward.x, abs(toward.y), toward.z)); }
let grid = (uv * 0.5 + vec2f(0.5)) * params.settings.x - vec2f(0.5);
let base = vec2i(floor(grid));
let t = fract(grid);
var cells: array<vec2i, 3>;
var weights: vec3f;
if t.x + t.y <= 1.0 {
cells = array<vec2i, 3>(base, base + vec2i(1, 0), base + vec2i(0, 1));
weights = vec3f(1.0 - t.x - t.y, t.x, t.y);
} else {
cells = array<vec2i, 3>(base + vec2i(1, 1), base + vec2i(0, 1), base + vec2i(1, 0));
weights = vec3f(t.x + t.y - 1.0, 1.0 - t.x, 1.0 - t.y);
}
let low = i32(params.settings.z);
let high = min(low + 1, i32(textureNumLayers(radiance)) - 1);
let anchor_mix = fract(params.settings.z);
var color = vec4f(0.0);
var position = vec3f(0.0);
var weight = 0.0;
for (var i = 0; i < 3; i++) {
let sample = frame_sample(cells[i], local, inst, low, high, anchor_mix);
let amount = weights[i] * max(sample.color.a, 0.0);
color += sample.color * weights[i];
if sample.weight > 0.0 && sample.weight < inst.center_radius.w * 6.0 {
position += sample.position * amount;
weight += amount;
}
}
if color.a <= 0.5 || hash(vec2u(pixel.xy), inst.options.x) < bitcast<f32>(inst.options.y) || weight <= 1e-5 { discard; }
let clip = params.view_projection * vec4f(position / weight, 1.0);
var output: Shade;
output.color = vec4f(color.rgb / max(color.a, 1e-5), 1.0);
output.depth = clamp(clip.z / clip.w, 0.0, 1.0);
return output;
}
@fragment fn fragment(input: VertexOut) -> FragmentOut {
let value = shade(input.local, input.instance_id, input.clip);
return FragmentOut(value.color, value.depth);
}
@fragment fn shadow_fragment(input: VertexOut) -> @builtin(frag_depth) f32 {
return shade(input.local, input.instance_id, input.clip).depth;
}
"#;