Constant VOLUME_WGSL
Source pub const VOLUME_WGSL: &str = r#"
struct Frame {
view_proj: mat4x4f, eye: vec4f, right: vec4f, up: vec4f, forward: vec4f,
lens: vec4f, sun: vec4f, sun_color: vec4f, extent: vec4f,
volume_lo: vec4f, volume_hi: vec4f, volume_color: vec4f, volume_control: vec4f,
room_light: vec4f, room_shape: vec4f, room_lo: vec4f, room_hi: vec4f,
plume: array<vec4f, 8>,
}
struct Shadow {
splits: vec4f, filter_params: vec4f, sun: vec4f, texels: vec4f,
view_proj_0: mat4x4f, view_proj_1: mat4x4f, view_proj_2: mat4x4f,
}
override STEPS: u32 = 20u;
override WARP_OCTAVES: i32 = 4;
override OCTAVES: i32 = 4;
override EARLY_OUT: bool = false;
override SPREAD_BASE: f32 = 1.0;
override SPREAD_GROWTH: f32 = 2.0;
override DRIFT_X_QUAD: f32 = 0.0;
override DRIFT_X_LIN: f32 = 0.0;
override DRIFT_Z_QUAD: f32 = 0.0;
override DRIFT_Z_LIN: f32 = 0.0;
override SWAY_X_WAVE: f32 = 10.0;
override SWAY_X_RATE: f32 = 1.0;
override SWAY_X_AMP: f32 = 0.005;
override SWAY_X_BASE: f32 = 0.5;
override SWAY_X_GROWTH: f32 = 2.0;
override SWAY_Z_WAVE: f32 = 10.0;
override SWAY_Z_RATE: f32 = 1.0;
override SWAY_Z_AMP: f32 = 0.005;
override SWAY_Z_BASE: f32 = 0.5;
override SWAY_Z_GROWTH: f32 = 2.0;
override FADE_IN: f32 = 0.01;
override FADE_OUT_START: f32 = 0.05;
override FADE_OUT_END: f32 = 0.15;
override DEPTH_PER_WIDTH: f32 = 6.0;
override GRAIN_H: f32 = 60.0;
override GRAIN_V: f32 = 40.0;
override LIFT_RATE: f32 = 0.05;
override WARP_AMP: f32 = 2.0;
override WARP_RATE: f32 = 0.2;
override CHURN: f32 = 0.6;
override THRESHOLD_LOW: f32 = 0.4;
override THRESHOLD_HIGH: f32 = 0.85;
@group(0) @binding(0) var<uniform> frame: Frame;
@group(0) @binding(1) var scene_depth: texture_2d<f32>;
@group(0) @binding(2) var shadow_atlas: texture_depth_2d_array;
@group(0) @binding(3) var<uniform> shadow: Shadow;
@group(0) @binding(4) var room_field: texture_3d<f32>;
@group(0) @binding(5) var room_sampler: sampler;
struct Out { @builtin(position) clip: vec4f, @location(0) uv: vec2f, }
@vertex fn vs_main(@builtin(vertex_index) vertex: u32) -> Out {
let xy = array<vec2f, 3>(vec2f(-1.0,-1.0),vec2f(3.0,-1.0),vec2f(-1.0,3.0));
var out: Out;
out.clip = vec4f(xy[vertex], 0.0, 1.0);
out.uv = xy[vertex] * vec2f(0.5,-0.5) + vec2f(0.5);
return out;
}
fn pcg(value: u32) -> u32 {
let state = value * 747796405u + 2891336453u;
let mixed = ((state >> ((state >> 28u) + 4u)) ^ state) * 277803737u;
return (mixed >> 22u) ^ mixed;
}
fn hash3(p: vec3i) -> f32 {
let seed = select(0u, bitcast<u32>(frame.extent.w), frame.volume_control.w > 0.5);
let h = pcg(u32(p.x) * 73856093u ^ u32(p.y) * 19349663u ^ u32(p.z) * 83492791u ^ seed);
return f32(h >> 8u) / 16777216.0;
}
fn noise3(p: vec3f) -> f32 {
let i = vec3i(floor(p));
let f = fract(p);
let t = f * f * (3.0 - 2.0 * f);
let a = mix(mix(hash3(i), hash3(i + vec3i(1,0,0)), t.x), mix(hash3(i + vec3i(0,1,0)), hash3(i + vec3i(1,1,0)), t.x), t.y);
let b = mix(mix(hash3(i + vec3i(0,0,1)), hash3(i + vec3i(1,0,1)), t.x), mix(hash3(i + vec3i(0,1,1)), hash3(i + vec3i(1,1,1)), t.x), t.y);
return mix(a, b, t.z);
}
fn fbm(p: vec3f, octaves: i32) -> f32 {
var sum = 0.0;
var amplitude = 0.55;
var q = p;
for (var i = 0; i < octaves; i++) {
sum += noise3(q) * amplitude;
q = q * 2.07 + vec3f(1.7, 9.2, 3.1);
amplitude *= 0.5;
}
return sum * (1.03125 / (1.1 * (1.0 - pow(0.5, f32(octaves)))));
}
fn density(p: vec3f) -> f32 {
let rise = p.y - frame.volume_lo.y;
let lift = p.y - frame.plume[0].y;
let smoke = frame.volume_control.w > 0.5;
let width = select(max(frame.volume_hi.z - frame.volume_lo.z, 0.000001), max(frame.volume_hi.x - frame.volume_lo.x, 0.000001), smoke);
let origin = frame.plume[0].xyz;
let drift = vec3f(DRIFT_X_QUAD * lift * lift + DRIFT_X_LIN * lift, 0.0, DRIFT_Z_QUAD * lift * lift + DRIFT_Z_LIN * lift);
let sway = vec3f(sin(lift * SWAY_X_WAVE - frame.extent.z * SWAY_X_RATE) * SWAY_X_AMP * (SWAY_X_BASE + lift * SWAY_X_GROWTH), 0.0, cos(lift * SWAY_Z_WAVE - frame.extent.z * SWAY_Z_RATE) * SWAY_Z_AMP * (SWAY_Z_BASE + lift * SWAY_Z_GROWTH));
let steam_centre = origin + vec3f(0.0, lift, 0.0) + drift + sway;
let smoke_rise = clamp(rise / max(frame.volume_hi.y - frame.volume_lo.y, 0.001), 0.0, 1.0);
let smoke_centre = (frame.volume_lo.xyz + frame.volume_hi.xyz) * 0.5 + vec3f(sin(smoke_rise * 9.0 - frame.extent.z * 1.3) * width * 0.29 * smoke_rise, 0.0, cos(smoke_rise * 7.0 - frame.extent.z) * width * 0.21 * smoke_rise);
let centre = select(steam_centre, smoke_centre, smoke);
let radius = select(frame.plume[0].w * (SPREAD_BASE + lift * SPREAD_GROWTH), width * (0.4 + smoke_rise * 0.25), smoke);
let core = exp(-dot((p - centre).xz, (p - centre).xz) / max(radius * radius, 0.00000001));
let steam_fade = smoothstep(0.0, FADE_IN, lift) * (1.0 - smoothstep(FADE_OUT_START, FADE_OUT_END, lift));
if (EARLY_OUT && !smoke && core * steam_fade < 0.002) { return 0.0; }
let q = vec3f(p.x * GRAIN_H, (p.y - frame.extent.z * LIFT_RATE) * GRAIN_V, p.z * GRAIN_H);
let warp = vec3f(fbm(q * 0.5 + vec3f(frame.extent.z * WARP_RATE, 0.0, 0.0), WARP_OCTAVES), 0.0, fbm(q * 0.5 + vec3f(3.1, 0.0, frame.extent.z * WARP_RATE), WARP_OCTAVES)) * WARP_AMP;
let steam_turbulence = smoothstep(THRESHOLD_LOW, THRESHOLD_HIGH, fbm(q + warp + vec3f(0.0, -frame.extent.z * CHURN, 0.0), OCTAVES));
let smoke_q = p * 18.0 + vec3f(0.0, -frame.extent.z * 0.7, 0.0);
let smoke_turbulence = smoothstep(0.3, 0.72, noise3(smoke_q) * 0.65 + noise3(smoke_q * 2.07 + vec3f(1.7,9.2,3.1)) * 0.35);
let turbulence = select(steam_turbulence, 0.25 + 0.75 * smoke_turbulence, smoke);
let smoke_fade = smoothstep(0.0, 0.1, smoke_rise) * (1.0 - smoothstep(0.85, 1.0, smoke_rise));
let fade = select(steam_fade, smoke_fade, smoke);
if (!smoke && core * fade < 0.002) { return 0.0; }
return core * turbulence * fade * frame.volume_control.x;
}
fn phase(cosine: f32, g: f32) -> f32 {
let g2 = g * g;
return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * cosine, 0.001), 1.5);
}
struct VolumeOut { @location(0) radiance: vec4f, @location(1) distance: f32, }
fn room_density(p: vec3f) -> f32 {
let drift = vec3f(0.011, 0.0035, -0.004) * frame.extent.z * 1.6;
let uvw = (p + drift - frame.room_lo.xyz) / max(frame.room_hi.xyz - frame.room_lo.xyz, vec3f(0.0001));
var field = vec4f(0.4375, 0.0, 0.46875, 0.4375);
if (all(uvw >= vec3f(0.0)) && all(uvw <= vec3f(1.0))) {
field = textureSampleLevel(room_field, room_sampler, uvw, 0.0);
}
let behind = smoothstep(0.45, 0.02, p.z);
let top = smoothstep(0.2, 0.46, p.y) * (0.35 + 0.9 * field.z);
let low = exp(-max(p.y, 0.0) / 0.035) * (0.3 + 0.9 * field.w);
return frame.volume_control.x * (0.5 + field.x) + behind * (frame.volume_hi.w * field.y + frame.volume_color.w * top) + frame.volume_lo.w * low;
}
fn room_sunlight(world: vec3f, distance: f32) -> f32 {
if (shadow.splits.w <= 0.0) { return 1.0; }
var layer = 0;
var matrix = shadow.view_proj_0;
if (distance > shadow.splits.y) { layer = 1; matrix = shadow.view_proj_1; }
if (distance > shadow.splits.z) { layer = 2; matrix = shadow.view_proj_2; }
let clip = matrix * vec4f(world, 1.0);
let point = clip.xyz / clip.w;
let uv = point.xy * vec2f(0.5, -0.5) + vec2f(0.5);
if (any(uv < vec2f(0.0)) || any(uv > vec2f(1.0)) || point.z < 0.0 || point.z > 1.0) { return frame.room_shape.y; }
let size = textureDimensions(shadow_atlas);
let at = clamp(vec2i(uv * vec2f(size)), vec2i(0), vec2i(size) - vec2i(1));
return select(0.0, 1.0, point.z - shadow.sun.w <= textureLoad(shadow_atlas, at, layer, 0));
}
fn room_phase(cosine: f32, g: f32) -> f32 {
let d = 1.0 + g * g - 2.0 * g * cosine;
return (1.0 - g * g) / (12.566371 * d * sqrt(d));
}
fn room(low: vec2f, limit: f32, ray: vec3f) -> VolumeOut {
var out: VolumeOut;
out.distance = limit;
let along = max(dot(ray, normalize(frame.forward.xyz)), 0.001);
let span = min(limit / along, frame.room_shape.x);
if (span <= 0.0) {
out.radiance = vec4f(0.0);
return out;
}
let steps = 6u;
let step_length = span / f32(steps);
let turn = f32(pcg(bitcast<u32>(frame.extent.z) ^ bitcast<u32>(frame.extent.w)) >> 8u) / 16777216.0;
let jitter = fract(fract(52.9829189 * fract(dot(floor(low), vec2f(0.06711056, 0.00583715)))) + turn);
let sun = normalize(frame.sun.xyz);
let cosine = dot(ray, sun);
let phase = mix(room_phase(cosine, frame.volume_control.y), room_phase(cosine, -0.55), frame.room_light.w);
let direct = frame.sun_color.rgb * frame.sun_color.w * frame.room_light.rgb * phase;
let fog = frame.volume_control.x;
var light = vec3f(0.0);
for (var i = 0u; i < steps; i++) {
let distance = (f32(i) + jitter) * step_length;
let p = frame.eye.xyz + ray * distance;
let density = room_density(p);
if (density <= 0.0) { continue; }
light += density * exp(-fog * distance) * (direct * room_sunlight(p, distance * along) + frame.volume_color.rgb) * step_length;
}
out.radiance = vec4f(light, 1.0 - exp(-fog * span));
return out;
}
@fragment fn fs_main(in: Out) -> VolumeOut {
let screen = vec2i(clamp(in.uv * frame.extent.xy, vec2f(0.0), frame.extent.xy - vec2f(1.0)));
let scene_limit = textureLoad(scene_depth, screen, 0).x;
let limit = select(1e30, scene_limit, frame.volume_control.w > 0.5);
let centre_clip = frame.view_proj * vec4f(frame.eye.xyz + frame.forward.xyz, 1.0);
let centre = centre_clip.xy / centre_clip.w;
let ray = normalize(frame.forward.xyz + frame.right.xyz * ((in.uv.x * 2.0 - 1.0 - centre.x) * frame.lens.x) + frame.up.xyz * ((1.0 - in.uv.y * 2.0 - centre.y) * frame.lens.y));
if (frame.volume_control.w > 1.5) { return room(in.clip.xy, scene_limit, ray); }
let safe = select(ray, select(vec3f(-0.00001), vec3f(0.00001), ray >= vec3f(0.0)), abs(ray) < vec3f(0.00001));
let t0 = (frame.volume_lo.xyz - frame.eye.xyz) / safe;
let t1 = (frame.volume_hi.xyz - frame.eye.xyz) / safe;
let enter = max(max(min(t0.x,t1.x),min(t0.y,t1.y)),max(min(t0.z,t1.z),0.0));
let leave = min(min(max(t0.x,t1.x),max(t0.y,t1.y)),min(max(t0.z,t1.z),limit));
var out: VolumeOut;
out.radiance = vec4f(0.0);
out.distance = scene_limit;
if (leave <= enter) { return out; }
let steps = STEPS;
let step_length = (leave - enter) / f32(steps);
let sun = normalize(frame.sun.xyz);
let scatter = frame.volume_color.rgb * (vec3f(0.32) + frame.sun_color.rgb * frame.sun_color.w * phase(dot(ray,sun), frame.volume_control.y)) * 0.85;
var transmittance = 1.0;
var light = vec3f(0.0);
for (var i = 0u; i < steps; i++) {
let distance = enter + (f32(i) + 0.5) * step_length;
let p = frame.eye.xyz + ray * distance;
let width = select(max(frame.volume_hi.z - frame.volume_lo.z, 0.000001), max(frame.volume_hi.x - frame.volume_lo.x, 0.000001), frame.volume_control.w > 0.5);
let scale = select(DEPTH_PER_WIDTH, 3.0, frame.volume_control.w > 0.5);
let absorbed = 1.0 - exp(-density(p) * step_length * scale / width);
let weight = transmittance * absorbed;
light += weight * scatter;
transmittance *= 1.0 - absorbed;
}
out.radiance = vec4f(light, 1.0 - transmittance);
return out;
}
"#;