Three new scene shaders to widen the library, now that the main families are settled. Each is a shader plus a params block, registered in the single source of truth (scenes/registry.js), so casting, UI, arc and checks pick them up automatically. - Block Mosh (glitch): a datamosh. Blocks pull the feedback buffer along per-block strokes on a bar-quantized grid so the corruption steps like an edit rather than crawling, and spills on onsets. Fills the most iconic gap in the glitch family. - Neon City (structural): a receding skyline with instanced lit windows, distinct from the rolling ridgelines and the road grid it sits alongside. - Flora (organic): an abstract plant of swaying stalks, teardrop petals and a bloom corona, all stamped in the track's signature shape. Also adds HOWTO-visualizers.md, a quick reference for building the next one.
119 lines
5.0 KiB
JavaScript
119 lines
5.0 KiB
JavaScript
// Organic family: an abstract plant — a plume of swaying stalks with teardrop
|
|
// leaves and a glowing bloom at each crown.
|
|
//
|
|
// Abstract on purpose: no soil, no foreground, just the silhouette of growth.
|
|
// Every petal and the crown are stamped in the track's signature form, so a
|
|
// hexagonal track grows hexagonal flora. The stalk bends on bass and the bloom
|
|
// pulses on the beat — the plant dances to the track rather than the camera
|
|
// shaking.
|
|
|
|
export const flora = {
|
|
name: 'Flora',
|
|
family: 'organic',
|
|
kind: 'fragment',
|
|
// Personality: see look/Personality.js.
|
|
traits: ['shape', 'camera', 'style'],
|
|
|
|
params: {
|
|
count: { type: 'float', range: [1, 5], default: 3, uniform: 'u_count', bias: 'density' },
|
|
height: { type: 'float', range: [0.6, 2.0], default: 1.1, uniform: 'u_height', bias: 'energy' },
|
|
stem: { type: 'float', range: [0.008, 0.05], default: 0.02, uniform: 'u_stem' },
|
|
sway: { type: 'float', range: [0.05, 0.5], default: 0.2, uniform: 'u_sway', bias: 'motion' },
|
|
swayRate:{ type: 'float', range: [0.1, 1.2], default: 0.6, uniform: 'u_swayRate', rate: true },
|
|
leaf: { type: 'float', range: [0.12, 0.5], default: 0.26, uniform: 'u_leaf' },
|
|
bud: { type: 'float', range: [0, 1], default: 0.55, uniform: 'u_bud', bias: 'energy' },
|
|
palette: { type: 'palette', count: 4 },
|
|
},
|
|
|
|
reactive: {
|
|
sway: { feature: 'bandLow', amount: 0.25, response: 'smooth' },
|
|
bud: { feature: 'beat', amount: 0.3, response: 'smooth' },
|
|
},
|
|
|
|
shader: `
|
|
// Horizontal lean of a spine at parametric height h (0 base .. 1 tip), seeded
|
|
// per plant and swaying more toward the crown like a real stem. Returns a value
|
|
// scaled by u_sway later, so the reactive bend applies to every part at once.
|
|
float swayAt(float h, float phase, float t) {
|
|
return sin(t * (0.35 + fract(phase * 1.7)) + phase)
|
|
+ h * sin(t * (0.35 + fract(phase * 0.6)) + phase * 2.1);
|
|
}
|
|
|
|
// A petal: an elongated blossom drawn in the track's signature form, growing
|
|
// forward along dir from a node and clipped to a blade shape.
|
|
float petal(vec2 p, vec2 node, vec2 dir, float len, float width) {
|
|
vec2 off = p - node;
|
|
float along = dot(off, dir);
|
|
vec2 perp = vec2(-dir.y, dir.x);
|
|
float across = dot(off, perp);
|
|
float taper = 0.6 + 0.4 * sat(along / max(len, 1e-3));
|
|
vec2 q = vec2(along / max(len, 1e-3), across / max(width * taper, 1e-3));
|
|
float form = sigForm(q * 0.5, vec2(0.0), 0.5);
|
|
float clip = smoothstep(0.0, -0.1, along) * smoothstep(len + 0.12, len * 0.72, along);
|
|
return form * clip;
|
|
}
|
|
|
|
vec4 scene(vec2 uv, vec2 p) {
|
|
float t = u_time * u_swayRate + u_seed;
|
|
float beat = u_beat;
|
|
p = sigCamera(p);
|
|
|
|
// Soft vertical wash behind the plants.
|
|
vec3 col = mix(pal(0) * 0.03, pal(1) * 0.06, sat((p.y + 1.0) * 0.5));
|
|
|
|
for (int i = 0; i < 5; i++) {
|
|
if (i >= int(u_count)) break;
|
|
float fi = float(i);
|
|
float s = u_seed + fi * 43.7;
|
|
float baseX = (hash11(s * 1.7) - 0.5) * 1.25;
|
|
float height = u_height * (0.55 + 0.45 * hash11(s * 3.1));
|
|
float baseY = -0.92;
|
|
|
|
// Stem, sampled so the bend follows the sway curve rather than a chord.
|
|
float stemMask = 0.0;
|
|
for (int j = 0; j < 12; j++) {
|
|
float hj = (float(j) + 0.5) / 12.0;
|
|
float cx = baseX + swayAt(hj, s, t) * u_sway;
|
|
float cy = baseY + hj * height;
|
|
float d = length(p - vec2(cx, cy));
|
|
float taper = max(u_stem * (1.0 - hj * 0.8) + 0.002, 0.003);
|
|
stemMask = max(stemMask, smoothstep(taper + 0.004, taper - 0.004, d));
|
|
}
|
|
col = mix(col, mix(pal(1), pal(3), 0.3), stemMask);
|
|
|
|
// Stalk tip, reused for the petal rows and the bloom.
|
|
float tipX = baseX + swayAt(1.0, s, t) * u_sway;
|
|
float tipY = baseY + height;
|
|
|
|
// Petals up the upper half, alternating sides.
|
|
for (int l = 0; l < 6; l++) {
|
|
float fll = float(l);
|
|
float node = 0.45 + floor(fll * 0.5) * 0.12;
|
|
float side = fract(fll * 0.5) < 0.5 ? -1.0 : 1.0;
|
|
float nx = baseX + swayAt(node, s, t) * u_sway;
|
|
float ny = baseY + node * height;
|
|
vec2 dir = normalize(vec2(side, 0.55));
|
|
float len = u_leaf * 2.0;
|
|
float m = petal(p, vec2(nx, ny), dir, len, u_leaf);
|
|
vec3 leafCol = mix(pal(2), pal(3), fract(s * 0.4));
|
|
col = mix(col, leafCol, m);
|
|
col += pal(3) * m * (0.2 + 0.3 * beat);
|
|
}
|
|
|
|
// Bloom: a small corona of signature forms around the crown.
|
|
float bloom = 0.0;
|
|
for (int k = 0; k < 6; k++) {
|
|
float a = 6.2831853 * (float(k) + 0.5) / 6.0 + s;
|
|
vec2 off = vec2(cos(a), sin(a)) * u_leaf * (0.30 + 0.2 * beat);
|
|
bloom += sigForm(p - vec2(tipX, tipY), off, u_leaf * 0.5);
|
|
}
|
|
col = mix(col, pal(2), sat(bloom) * u_bud * (0.6 + 0.4 * beat));
|
|
}
|
|
|
|
col += sigGrain(uv);
|
|
return vec4(col, 1.0);
|
|
}
|
|
`,
|
|
};
|
|
|
|
export default flora; |