71 lines
3.3 KiB
JavaScript
71 lines
3.3 KiB
JavaScript
// Glitch family: vertical transposition. The frame is cut into vertical bands,
|
|
// each of which shifts up or down by a small amount. That alone is a tear; what
|
|
// makes it churn is that the offset is re-rolled on the quantised grid and the
|
|
// previous frame's transposed image is smeared underneath — so slices translate
|
|
// to a new fixed pose each step instead of crawling, and the pitch reads as an
|
|
// edit rather than a broken renderer.
|
|
|
|
export const pitchShatter = {
|
|
name: 'Pitch Shatter',
|
|
family: 'glitch',
|
|
kind: 'fragment',
|
|
// Personality: see look/Personality.js.
|
|
traits: ['camera', 'style'],
|
|
|
|
params: {
|
|
slices: { type: 'float', range: [4, 40], default: 18, uniform: 'u_slices', bias: 'density' },
|
|
amp: { type: 'float', range: [0, 0.35], default: 0.1, uniform: 'u_amp', bias: 'energy' },
|
|
quantize: { type: 'float', range: [1, 8], default: 4, uniform: 'u_quantize' },
|
|
bleed: { type: 'float', range: [0, 0.9], default: 0.4, uniform: 'u_bleed' },
|
|
glow: { type: 'float', range: [0, 1], default: 0.3, uniform: 'u_glow', bias: 'energy' },
|
|
speed: { type: 'float', range: [0.05, 0.8], default: 0.25, uniform: 'u_speed', bias: 'motion', rate: true },
|
|
palette: { type: 'palette', count: 5 },
|
|
},
|
|
|
|
reactive: {
|
|
amp: { feature: 'beat', amount: 0.4, response: 'spike' },
|
|
bleed: { feature: 'flux', amount: 0.25, response: 'smooth' },
|
|
glow: { feature: 'bandHigh', amount: 0.2 },
|
|
},
|
|
|
|
shader: `
|
|
vec4 scene(vec2 uv, vec2 p) {
|
|
float t = u_time * u_speed + u_seed;
|
|
p = sigCamera(p);
|
|
|
|
// One shared grid for every discontinuity below: slices re-roll on the
|
|
// quantised step rather than moving continuously frame-to-frame.
|
|
float q = max(u_quantize, 1.0);
|
|
float step_ = floor(u_barPhase * q) + floor(t * 6.0) * q;
|
|
|
|
// A vertical slice is a column of pixels; each gets one fixed offset per
|
|
// step, decided by a hash of (slice, step) so it jumps cleanly on the grid.
|
|
float slice = floor(uv.x * u_slices);
|
|
float sliceRand = hash12(vec2(slice, step_ * 31.0));
|
|
float pitch = (sliceRand - 0.5) * 2.0 * u_amp;
|
|
|
|
// The base image is always sampled from the un-pitched field so the scene
|
|
// has real content behind the displacement, even on its first frame.
|
|
vec2 baseUv = vec2(uv.x, fract(uv.y - pitch));
|
|
float field = fbm(vec2(baseUv.x * 2.5, baseUv.y * 4.0) + t * 0.4, 4);
|
|
float ramp = fract(field * 2.0 + baseUv.y * 2.0 - t * 0.4);
|
|
vec3 col = palRamp(ramp * 0.7 + slice * 0.02);
|
|
col *= 0.4 + 0.6 * smoothstep(0.1, 0.9, field);
|
|
|
|
// The smear: pull the previous frame across the pitch so slices leave a
|
|
// transient ghost as they land, and the corruption feels sludgy.
|
|
vec3 ghost = prev(vec2(uv.x, fract(uv.y + pitch * 0.55)));
|
|
col = mix(col, ghost, u_bleed * (0.4 + sliceRand));
|
|
|
|
// A thin scan beam rooting the hard edge of each step, kept local and beat
|
|
// -pinned so the frame never swings in whole-frame luminance.
|
|
float beamY = fract(u_beatPhase) * 2.0 - 1.0;
|
|
float beam = exp(-abs(p.y - beamY * 0.6) * 6.0);
|
|
col += pal(3) * beam * u_slices * 0.001 * (0.5 + u_beat);
|
|
|
|
col += pal(int(mod(slice, 5.0))) * (1.0 - sliceRand) * u_glow * 0.3 * sliceRand;
|
|
col += sigGrain(uv);
|
|
return vec4(col, 1.0);
|
|
}
|
|
`,
|
|
}; |