YrXtals/shaders/visualizer.wgsl

260 lines
7.0 KiB
WebGPU Shading Language

// synthesizes triangle and line vertices per-bin from a storage buffer, with mirrors as an instance axis.
struct Globals {
bounds: vec2<f32>, // full canvas in pixels
base: vec2<f32>, // building rect in pixels, 0.55w by 0.5h when mirrored
num_bins: u32,
num_channels: u32,
flags: u32, // 1=glass, 2=mirrored, 4=inverted, 8=stereo
fade_bins: u32, // count of tail bins fading toward zero alpha when mirrored
hue_param: f32,
contrast: f32,
brightness: f32,
_pad0: f32,
unified_hue: f32,
unified_sat: f32,
unified_val: f32,
_pad1: f32,
};
struct Bin {
log_x: f32, // 0..1 along the log-frequency axis
visual_norm: f32, // smoothed dB on a 0..1 scale
primary_norm: f32, // primary dB on a 0..1 scale
bright_mod: f32,
alpha_mod: f32,
hue: f32,
sat: f32,
val: f32,
splash: f32, // 0..1 suppressed-energy color splash
};
// 5/12 hue-wheel rotation per full splash, cycling all 12 wheel segments.
const SPLASH_HUE_OFFSET: f32 = 0.4166667;
@group(0) @binding(0) var<uniform> globals: Globals;
@group(0) @binding(1) var<storage, read> bins: array<Bin>;
struct VertexOut {
@builtin(position) clip_position: vec4<f32>,
@location(0) color: vec4<f32>,
};
fn flag(bit: u32) -> bool {
return (globals.flags & bit) != 0u;
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> vec3<f32> {
let hh = fract(fract(h) + 1.0) * 6.0;
let i = floor(hh);
let f = hh - i;
let p = v * (1.0 - s);
let q = v * (1.0 - s * f);
let t = v * (1.0 - s * (1.0 - f));
let ii = i32(i) % 6;
if (ii == 0) { return vec3<f32>(v, t, p); }
if (ii == 1) { return vec3<f32>(q, v, p); }
if (ii == 2) { return vec3<f32>(p, v, t); }
if (ii == 3) { return vec3<f32>(p, q, v); }
if (ii == 4) { return vec3<f32>(t, p, v); }
return vec3<f32>(v, p, q);
}
fn final_brightness(b: Bin) -> f32 {
let base_b = sqrt(b.primary_norm);
let bm = b.bright_mod;
var b_mult: f32;
if (bm >= 0.0) {
b_mult = 1.0 + bm;
} else {
b_mult = 1.0 / (1.0 - bm * 2.0);
}
return clamp(base_b * b_mult * globals.brightness, 0.0, 1.0);
}
fn alpha_for(b: Bin, fade: f32) -> f32 {
let am = b.alpha_mod;
var a_mult: f32;
if (am >= 0.0) {
a_mult = 1.0 + am * 0.5;
} else {
a_mult = 1.0 + am;
}
a_mult = max(a_mult, 0.1);
var a = 0.4 + (b.primary_norm - 0.5) * globals.contrast;
a = clamp(a * a_mult, 0.0, 1.0);
return a * fade;
}
fn dyn_rgb(b: Bin) -> vec3<f32> {
let fb = final_brightness(b);
let hue = fract(b.hue + SPLASH_HUE_OFFSET * b.splash);
let s = clamp((b.sat + 0.4 * b.splash) * globals.hue_param, 0.0, 1.0);
let v = clamp((b.val + 0.4 * b.splash) * fb, 0.0, 1.0);
return hsv_to_rgb(hue, s, v);
}
fn fill_rgb(b: Bin) -> vec3<f32> {
if (flag(1u)) {
let fb = final_brightness(b);
let hue = fract(globals.unified_hue + SPLASH_HUE_OFFSET * b.splash);
let s = clamp(globals.unified_sat + 0.4 * b.splash, 0.0, 1.0);
let v = clamp((globals.unified_val + 0.4 * b.splash) * fb, 0.0, 1.0);
return hsv_to_rgb(hue, s, v);
}
return dyn_rgb(b);
}
fn channel_offset(rgb: vec3<f32>, ch: u32) -> vec3<f32> {
if (ch == 1u && flag(8u)) {
let off = 40.0 / 255.0;
return vec3<f32>(
max(rgb.x - off, 0.0),
max(rgb.y - off, 0.0),
min(rgb.z + off, 1.0),
);
}
return rgb;
}
fn fade_factor(seg: u32) -> f32 {
if (!flag(2u)) { return 1.0; }
let from_end = i32(globals.num_bins) - 2 - i32(seg);
if (from_end < i32(globals.fade_bins)) {
var f = f32(from_end + 1) / f32(globals.fade_bins + 1u);
f = f * f;
return max(f, 0.0);
}
return 1.0;
}
fn pixel_to_clip(p: vec2<f32>) -> vec4<f32> {
let nx = (p.x / max(globals.bounds.x, 1.0)) * 2.0 - 1.0;
let ny = 1.0 - (p.y / max(globals.bounds.y, 1.0)) * 2.0;
return vec4<f32>(nx, ny, 0.0, 1.0);
}
fn mirror_xform(iid: u32, p: vec2<f32>) -> vec2<f32> {
var sx: f32 = 1.0;
var sy: f32 = 1.0;
var tx: f32 = 0.0;
var ty: f32 = 0.0;
if (iid == 1u) {
sx = -1.0; tx = globals.bounds.x;
} else if (iid == 2u) {
sy = -1.0; ty = globals.bounds.y;
} else if (iid == 3u) {
sx = -1.0; sy = -1.0;
tx = globals.bounds.x; ty = globals.bounds.y;
}
return vec2<f32>(p.x * sx + tx, p.y * sy + ty);
}
@vertex
fn vs_fill(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VertexOut {
let nb = globals.num_bins;
let segs = max(nb, 1u) - 1u;
let per_ch = segs * 6u;
let ch = vid / per_ch;
let in_ch = vid % per_ch;
let seg = in_ch / 6u;
let corner = in_ch % 6u;
var i = seg;
var j = seg + 1u;
if (flag(4u)) {
i = nb - 1u - seg;
j = nb - 2u - seg;
}
let base = ch * nb;
let bi = bins[base + i];
let bj = bins[base + j];
let w = globals.base.x;
let h = globals.base.y;
let x1 = bi.log_x * w;
let x2 = bj.log_x * w;
let y1 = h - bi.visual_norm * h;
let y2 = h - bj.visual_norm * h;
let anchor_y = h;
var p: vec2<f32>;
switch corner {
case 0u: { p = vec2<f32>(x1, anchor_y); }
case 1u: { p = vec2<f32>(x1, y1); }
case 2u: { p = vec2<f32>(x2, y2); }
case 3u: { p = vec2<f32>(x1, anchor_y); }
case 4u: { p = vec2<f32>(x2, y2); }
default: { p = vec2<f32>(x2, anchor_y); }
}
let pw = mirror_xform(iid, p);
var rgb = fill_rgb(bi);
rgb = channel_offset(rgb, ch);
let a = alpha_for(bi, fade_factor(seg));
var out: VertexOut;
out.clip_position = pixel_to_clip(pw);
out.color = vec4<f32>(rgb, a);
return out;
}
@vertex
fn vs_line(@builtin(vertex_index) vid: u32, @builtin(instance_index) iid: u32) -> VertexOut {
let nb = globals.num_bins;
let per_ch = nb * 2u;
let ch = vid / per_ch;
let in_ch = vid % per_ch;
let seg = in_ch / 2u;
let endpoint = in_ch % 2u;
var i = seg;
if (flag(4u)) {
i = nb - 1u - seg;
}
let bi = bins[ch * nb + i];
let w = globals.base.x;
let h = globals.base.y;
let x = bi.log_x * w;
let y_top = h - bi.visual_norm * h;
let anchor_y = h;
var p: vec2<f32>;
if (endpoint == 0u) {
p = vec2<f32>(x, anchor_y);
} else {
p = vec2<f32>(x, y_top);
}
let pw = mirror_xform(iid, p);
var rgb = dyn_rgb(bi);
rgb = channel_offset(rgb, ch);
var a = alpha_for(bi, fade_factor(seg));
a = min(a, 0.9);
var out: VertexOut;
out.clip_position = pixel_to_clip(pw);
out.color = vec4<f32>(rgb, a);
return out;
}
// cepstrum line strip in pixel space.
struct CepIn {
@location(0) position: vec2<f32>,
@location(1) color: vec4<f32>,
};
@vertex
fn vs_cep(in: CepIn) -> VertexOut {
var out: VertexOut;
out.clip_position = pixel_to_clip(in.position);
out.color = in.color;
return out;
}
@fragment
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
return in.color;
}