Constant GLASS_WGSL
Source pub const GLASS_WGSL: &str = r#"
struct Scene {
projection: mat4x4f,
world0: vec4f,
world1: vec4f,
world2: vec4f,
extent: vec4f,
caustic_rect: vec4f,
caustic_plane: vec4f,
caustic_info: vec4f,
sky: array<vec4f, 9>,
}
struct Object {
model_view: mat4x4f,
mvp: mat4x4f,
normal0: vec4f,
normal1: vec4f,
normal2: vec4f,
base_roughness: vec4f,
tint_absorption: vec4f,
optics: vec4f,
film: vec4f,
liquid: vec4f,
tinted_base: vec4f,
tinted_subsurface: vec4f,
pick: vec4u,
clip0: vec4f,
clip1: vec4f,
}
@group(0) @binding(0) var<uniform> scene: Scene;
@group(0) @binding(1) var<storage, read> objects: array<Object>;
@group(0) @binding(2) var fluid_tex: texture_2d<f32>;
@group(0) @binding(3) var ripple_tex: texture_2d<f32>;
@group(1) @binding(0) var front_id_tex: texture_2d<u32>;
@group(2) @binding(0) var front_tex: texture_2d<f32>;
@group(2) @binding(1) var back_tex: texture_2d<f32>;
@group(2) @binding(2) var front_depth_tex: texture_depth_2d;
@group(2) @binding(3) var opaque_tex: texture_2d<f32>;
@group(2) @binding(4) var reflection_tex: texture_cube<f32>;
@group(2) @binding(5) var caustic_tex: texture_2d<f32>;
@group(2) @binding(6) var opaque_depth_tex: texture_depth_2d;
@group(2) @binding(7) var linear_sampler: sampler;
@group(2) @binding(8) var front_look_tex: texture_2d<f32>;
struct SurfaceVertex {
@location(0) position: vec4f,
@location(1) normal: vec4f,
@location(2) uv: vec4f,
@location(3) look: vec4f,
}
struct SurfaceOut {
@builtin(position) position: vec4f,
@location(0) view_normal: vec3f,
@location(1) view_depth: f32,
@location(2) @interpolate(flat) object_id: u32,
@location(3) look: vec4f,
@location(4) world: vec3f,
}
struct FrontOut {
@location(0) normal_depth: vec4f,
@location(1) id: u32,
@location(2) look: vec4f,
}
struct ComposeOut {
@location(0) color: vec4f,
@location(1) reactive: f32,
}
fn height_at(uv: vec2f, object: Object) -> f32 {
let fluid_size = vec2f(textureDimensions(fluid_tex));
let ripple_size = vec2f(textureDimensions(ripple_tex));
let fluid_xy = vec2i(clamp(uv * fluid_size, vec2f(0.0), fluid_size - vec2f(1.0)));
let ripple_xy = vec2i(clamp(uv * ripple_size, vec2f(0.0), ripple_size - vec2f(1.0)));
return textureLoad(fluid_tex, fluid_xy, 0).r * object.liquid.y + textureLoad(ripple_tex, ripple_xy, 0).r * object.liquid.z;
}
fn to_world(view_point: vec3f) -> vec3f {
return mat3x3f(scene.world0.xyz, scene.world1.xyz, scene.world2.xyz) * view_point + vec3f(scene.world0.w, scene.world1.w, scene.world2.w);
}
fn clip_plane(plane: vec4f, world: vec3f) -> bool {
return any(plane.xyz != vec3f(0.0)) && dot(plane.xyz, world) > plane.w;
}
fn clipped(object: Object, world: vec3f) -> bool {
return clip_plane(object.clip0, world) || clip_plane(object.clip1, world);
}
fn cap(plane: vec4f, world: vec3f, t: f32) -> f32 {
if (!clip_plane(plane, world)) { return t; }
let eye = vec3f(scene.world0.w, scene.world1.w, scene.world2.w);
let along = dot(plane.xyz, world - eye);
if (abs(along) < 1e-8) { return 0.0; }
return min(t, (plane.w - dot(plane.xyz, eye)) / along);
}
@vertex fn surface_vertex(v: SurfaceVertex, @builtin(instance_index) index: u32) -> SurfaceOut {
let object = objects[index];
var p = v.position;
var n = v.normal.xyz;
if (object.liquid.x > 0.5 && n.z > 0.5) {
let step = 1.0 / vec2f(textureDimensions(fluid_tex));
let dx = height_at(v.uv.xy + vec2f(step.x, 0.0), object) - height_at(v.uv.xy - vec2f(step.x, 0.0), object);
let dy = height_at(v.uv.xy + vec2f(0.0, step.y), object) - height_at(v.uv.xy - vec2f(0.0, step.y), object);
p.z += height_at(v.uv.xy, object);
n = normalize(vec3f(-dx / (2.0 * step.x), -dy / (2.0 * step.y), 1.0));
}
let view = object.model_view * p;
var out: SurfaceOut;
out.position = object.mvp * p;
out.view_normal = normalize(mat3x3f(object.normal0.xyz, object.normal1.xyz, object.normal2.xyz) * n);
out.view_depth = -view.z;
out.object_id = index + 1u;
out.look = v.look;
out.world = to_world(view.xyz);
return out;
}
@fragment fn front_fragment(input: SurfaceOut) -> FrontOut {
if (input.look.z < 0.0) { discard; }
if (clipped(objects[input.object_id - 1u], input.world)) { discard; }
var out: FrontOut;
out.normal_depth = vec4f(input.view_normal, input.view_depth);
out.id = input.object_id;
out.look = vec4f(input.look.xy, select(0.0, 1.0, input.look.w > 0.5), 0.0);
return out;
}
@fragment fn back_fragment(input: SurfaceOut) -> @location(0) vec4f {
let pixel = vec2i(input.position.xy);
if (textureLoad(front_id_tex, pixel, 0).r != input.object_id) { discard; }
let object = objects[input.object_id - 1u];
if (!clipped(object, input.world)) {
return vec4f(input.view_normal, input.view_depth);
}
let first = cap(object.clip0, input.world, 1.0);
let t = cap(object.clip1, input.world, first);
if (t <= 0.0) { discard; }
var plane = object.clip1.xyz;
if (t == first) {
plane = object.clip0.xyz;
}
let normal = normalize(transpose(mat3x3f(scene.world0.xyz, scene.world1.xyz, scene.world2.xyz)) * plane);
return vec4f(normal, input.view_depth * t);
}
@vertex fn fullscreen_vertex(@builtin(vertex_index) vertex: u32) -> @builtin(position) vec4f {
let xy = vec2f(f32((vertex << 1u) & 2u), f32(vertex & 2u));
return vec4f(xy * 2.0 - vec2f(1.0), 0.0, 1.0);
}
fn refract_ray(incident: vec3f, normal: vec3f, eta: f32) -> vec3f {
let cosine = -dot(incident, normal);
let k = 1.0 - eta * eta * (1.0 - cosine * cosine);
if (k < 0.0) { return reflect(incident, normal); }
return eta * incident + (eta * cosine - sqrt(k)) * normal;
}
fn landing(uv: vec2f, front_depth: f32, back_depth: f32, front_normal: vec3f, back_normal: vec3f, ior: f32, receiver_depth: f32) -> vec4f {
let ndc = vec2f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0);
let entry = vec3f(ndc * front_depth / vec2f(scene.projection[0].x, scene.projection[1].y), -front_depth);
let incident = normalize(entry);
let inside = refract_ray(incident, front_normal, 1.0 / ior);
let travel = max(back_depth - front_depth, 0.0) / max(-inside.z, 0.1);
let exit = entry + inside * travel;
let outgoing = refract_ray(inside, -back_normal, ior);
let reach = max(receiver_depth - back_depth, 0.0) / max(-outgoing.z, 0.1);
let hit = exit + outgoing * reach;
return vec4f(hit, max(receiver_depth, back_depth));
}
fn sample_uv(uv: vec2f, front_depth: f32, back_depth: f32, front_normal: vec3f, back_normal: vec3f, ior: f32, receiver_depth: f32) -> vec2f {
let land = landing(uv, front_depth, back_depth, front_normal, back_normal, ior, receiver_depth);
return vec2f(0.5 + land.x * scene.projection[0].x / (2.0 * land.w), 0.5 - land.y * scene.projection[1].y / (2.0 * land.w));
}
fn caustic_light(view_point: vec3f, strength: f32) -> vec3f {
if (scene.caustic_info.z < 0.5 || strength == 0.0) {
return vec3f(0.0);
}
let world = to_world(view_point);
let toward = scene.caustic_plane.xyz;
let along = (scene.caustic_plane.w - world.y) / max(toward.y, 1e-4);
let at = (world.xz + toward.xz * along - scene.caustic_rect.xy) / scene.caustic_rect.zw;
if (any(at < vec2f(0.0)) || any(at > vec2f(1.0))) {
return vec3f(0.0);
}
return textureSampleLevel(caustic_tex, linear_sampler, at, 0.0).rgb * strength;
}
fn sky_irradiance(n: vec3f) -> vec3f {
let d = normalize(n);
let basis0 = vec3f(0.282095, 0.488603 * d.y, 0.488603 * d.z);
let basis1 = vec3f(0.488603 * d.x, 1.092548 * d.x * d.y, 1.092548 * d.y * d.z);
let basis2 = vec3f(0.315392 * (3.0 * d.z * d.z - 1.0), 1.092548 * d.x * d.z, 0.546274 * (d.x * d.x - d.y * d.y));
let sh = scene.sky;
let value = sh[0].xyz * basis0.x + sh[1].xyz * basis0.y + sh[2].xyz * basis0.z
+ sh[3].xyz * basis1.x + sh[4].xyz * basis1.y + sh[5].xyz * basis1.z
+ sh[6].xyz * basis2.x + sh[7].xyz * basis2.y + sh[8].xyz * basis2.z;
return max(value, vec3f(0.0));
}
@fragment fn compose_fragment(@builtin(position) position: vec4f) -> ComposeOut {
let pixel = vec2i(position.xy);
let id = textureLoad(front_id_tex, pixel, 0).r;
if (id == 0u) { discard; }
let front_z = textureLoad(front_depth_tex, pixel, 0);
let opaque_z = textureLoad(opaque_depth_tex, pixel, 0);
if (opaque_z < front_z) { discard; }
let front = textureLoad(front_tex, pixel, 0);
let back = textureLoad(back_tex, pixel, 0);
if (back.w <= front.w) { discard; }
let object = objects[id - 1u];
let look = textureLoad(front_look_tex, pixel, 0);
var base = object.base_roughness.xyz;
var scatter_tint = object.tint_absorption.xyz;
var subsurface = object.optics.w;
var film_amount = object.film.z;
if (look.z > 0.5) {
let t = clamp(look.x, 0.0, 1.0);
base = mix(base, object.tinted_base.xyz, t);
scatter_tint = mix(scatter_tint, object.tinted_subsurface.xyz, t);
film_amount = mix(film_amount, object.tinted_base.w, t);
subsurface = mix(subsurface, object.tinted_subsurface.w, t) * max(look.y, 0.0);
}
let uv = position.xy / scene.extent.xy;
let ray = normalize(vec3f(vec2f(uv.x * 2.0 - 1.0, 1.0 - uv.y * 2.0) / vec2f(scene.projection[0].x, scene.projection[1].y), -1.0));
let normal = normalize(front.xyz);
let inner = refract_ray(ray, normal, 1.0 / max(object.optics.x, 1.01));
let distance = (back.w - front.w) / max(-inner.z, 0.1);
var background_depth = back.w + max(back.w - front.w, 0.01) * 2.0;
if (opaque_z < 0.999) {
let linear = scene.projection[3].z / (opaque_z + scene.projection[2].z);
background_depth = max(linear, back.w);
}
let max_lod = max(scene.extent.z - 1.0, 0.0);
let lod = clamp(object.base_roughness.w * object.base_roughness.w * max_lod, 0.0, max_lod);
let ior = vec3f(object.optics.x - 0.016 * object.optics.y, object.optics.x, object.optics.x + 0.022 * object.optics.y);
var refracted = vec3f(0.0);
for (var channel = 0u; channel < 3u; channel++) {
let sample_at = sample_uv(uv, front.w, back.w, normal, normalize(back.xyz), ior[channel], background_depth);
let fit = scene.extent.xy / vec2f(textureDimensions(opaque_tex));
let sample_color = textureSampleLevel(opaque_tex, linear_sampler, clamp(sample_at, vec2f(0.0), vec2f(1.0)) * fit, lod).rgb;
refracted[channel] = sample_color[channel];
}
let absorption = exp(-max(object.tint_absorption.w, 0.0) * distance * (vec3f(1.0) - clamp(scatter_tint, vec3f(0.0), vec3f(1.0))));
refracted *= absorption * base;
var scattered = clamp(subsurface * (1.0 - exp(-distance)), 0.0, 1.0);
if (look.z > 0.5) {
scattered = 1.0 - exp(-subsurface * distance);
}
let world = mat3x3f(scene.world0.xyz, scene.world1.xyz, scene.world2.xyz);
let lit_scatter = scatter_tint * sky_irradiance(world * normal) / 3.14159265;
refracted = mix(refracted, lit_scatter, scattered);
let cosine = clamp(-dot(ray, normal), 0.0, 1.0);
let f0 = pow((object.optics.x - 1.0) / (object.optics.x + 1.0), 2.0);
var fresnel = vec3f(f0 + (1.0 - f0) * pow(1.0 - cosine, 5.0));
if (film_amount > 0.0 && object.film.x > 0.0) {
fresnel = mix(fresnel, film_dielectric(cosine, object.film.x, object.film.y, object.optics.x), clamp(film_amount, 0.0, 1.0));
}
let reflected_world = normalize(world * reflect(ray, normal));
let reflected = textureSampleLevel(reflection_tex, linear_sampler, reflected_world, lod).rgb;
let caustic = caustic_light(landing(uv, front.w, back.w, normal, normalize(back.xyz), ior.y, background_depth).xyz, object.film.w);
let color = mix(reflected, refracted + caustic, (vec3f(1.0) - fresnel) * clamp(object.optics.z, 0.0, 1.0));
var out: ComposeOut;
out.color = vec4f(color, 1.0);
out.reactive = 1.0;
return out;
}
@fragment fn pick_fragment(@builtin(position) position: vec4f) -> @location(0) u32 {
let pixel = vec2i(position.xy);
let id = textureLoad(front_id_tex, pixel, 0).r;
if (id == 0u) { discard; }
let front_z = textureLoad(front_depth_tex, pixel, 0);
let opaque_z = textureLoad(opaque_depth_tex, pixel, 0);
if (opaque_z < front_z) { discard; }
let front = textureLoad(front_tex, pixel, 0);
let back = textureLoad(back_tex, pixel, 0);
if (back.w <= front.w) { discard; }
let pick = objects[id - 1u].pick.x;
if (pick == 0u) { discard; }
return pick;
}
"#;