PRISMACORE_003 — Retina Banquet
GLSL shader by merrypranxter · created 2026-02-21 · 10s loop · 2 passes
Eight-fold polar symmetry with two counter-rotating folds fighting each other for control of the geometry. Triple-pass domain warp where each layer drifts at a different speed so space never settles. R, G, and B channels warped independently through separate time offsets — chromatic aberration as temporal desync, not just spatial shift. Razor-thin sin-threshold ridges blow out additively on top of per-channel thin-film iridescence. Strobe pulse on exposure. Glitch tears wide enough to lose your footing. CRT grime cranked. ACES tonemapper fought valiantly and lost. Math that wants to hurt you, but make it Lisa Frank
This page opens the shader in the ShaderKit browser GLSL editor: edit it live, fork it, or render it to video, GIF or images up to 8K. Also available as a full-screen view and an embeddable player.
Shader source (GLSL)
Common
#define pi acos(-1.)
#define deg pi/180. //1 degree
#define time iTime*2.*pi/10. //sin(time) loops 10 seconds
#define R iResolution.xy //shorthand
#define ar R.x/R.y //aspect ratio
#define M iMouse //shorthand
#define xm (M.xy/R) //normalized mouse
#define nm ((xm.xy-0.5)*vec2(ar,1.)+0.5) //aspect ratio correction
vec3 cs = vec3(1.,2.,3.);
mat2 r2d(float a) {
return mat2(cos(a),sin(a),-sin(a),cos(a));
}
Buffer A (iChannel0)
// PRISMACORE_003 — Retina Banquet
// 8-fold polar war + razor ridges + triple warp + strobe bloom + glitch tears
// Shadertoy: iTime, iResolution
#define PI 3.14159265
#define TAU 6.28318530
// ════════════════════════════════
// KNOBS
// ════════════════════════════════
#define FOLD_N 8.0 // symmetry arms
#define WARP_SCALE 3.2 // domain warp violence
#define RIDGE_FREQ 18.0 // razor line density (higher = more cuts)
#define RIDGE_SHARP 0.04 // ridge width (lower = thinner = more intense)
#define GLITCH_AMT 0.55 // tear probability
#define DAMAGE 0.7 // CRT grime level
#define IRIDESCENCE 2.8 // color cycle density
#define STROBE 0.18 // exposure pulse amplitude
#define FLOW_SPEED 0.6 // animation pace
// ════════════════════════════════
float hash(vec2 p) {
p = fract(p * vec2(127.1, 311.7));
p += dot(p, p + 19.19);
return fract(p.x * p.y);
}
float hash1(float x) { return fract(sin(x * 127.1) * 43758.5); }
vec2 hash2(vec2 p) { return vec2(hash(p), hash(p + 17.3)); }
float vnoise(vec2 p) {
vec2 i = floor(p), f = fract(p);
vec2 u = f*f*(3.0-2.0*f);
return mix(mix(hash(i), hash(i+vec2(1,0)), u.x),
mix(hash(i+vec2(0,1)), hash(i+vec2(1,1)), u.x), u.y);
}
float fbm(vec2 p, int oct) {
float v = 0.0, a = 0.5;
for (int i = 0; i < 6; i++) {
if (i >= oct) break;
v += a * vnoise(p);
p = p * 2.1 + vec2(1.7, 9.2);
a *= 0.5;
}
return v;
}
// ---- POLAR FOLD (dual rotation) ----
vec2 polarFold(vec2 p, float t) {
// two counter-rotating layers
float a1 = atan(p.y, p.x) + t * FLOW_SPEED * 0.07;
float a2 = atan(p.y, p.x) - t * FLOW_SPEED * 0.11;
float r = length(p);
float seg = TAU / FOLD_N;
float fa1 = mod(a1, seg); fa1 = min(fa1, seg - fa1);
float fa2 = mod(a2, seg); fa2 = min(fa2, seg - fa2);
// blend between the two folds — they phase in/out against each other
float blend = sin(t * FLOW_SPEED * 0.3) * 0.5 + 0.5;
float fa = mix(fa1, fa2, blend);
return vec2(cos(fa), sin(fa)) * r;
}
// ---- TRIPLE WARP ----
vec2 warp3(vec2 p, float t) {
float s = FLOW_SPEED;
// pass 1
vec2 q1 = vec2(fbm(p + vec2(t*s*0.7, 1.3), 4),
fbm(p + vec2(2.8, t*s*0.5), 4));
vec2 w1 = p + q1 * (WARP_SCALE * 0.35);
// pass 2 — offset time
vec2 q2 = vec2(fbm(w1 + vec2(t*s*1.1 + 4.2, 0.9), 4),
fbm(w1 + vec2(1.3, t*s*0.8 + 3.1), 4));
vec2 w2 = w1 + q2 * (WARP_SCALE * 0.25);
// pass 3 — fastest, smallest, adds micro-chaos
vec2 q3 = vec2(fbm(w2 * 2.0 + t*s*1.7, 3),
fbm(w2 * 2.0 + t*s*1.3 + 5.5, 3));
return w2 + q3 * (WARP_SCALE * 0.12);
}
// ---- RAZOR RIDGES ----
// sin thresholded to knife-thin bright lines
float ridges(float field) {
float s = sin(field * PI * RIDGE_FREQ);
return smoothstep(1.0 - RIDGE_SHARP, 1.0, abs(s));
}
// ---- THIN FILM ----
vec3 thinFilm(float phase, float strength) {
return vec3(
sin(phase * TAU * strength) * 0.5 + 0.5,
sin(phase * TAU * strength + TAU/3.0) * 0.5 + 0.5,
sin(phase * TAU * strength + 2.0*TAU/3.0) * 0.5 + 0.5
);
}
// ---- GLITCH TEARS ----
vec2 glitch(vec2 uv, float t) {
float band = floor(uv.y * 24.0 + t * 9.1);
float trigger = hash1(band + floor(t * 4.0));
float shift = (trigger > 1.0 - GLITCH_AMT)
? (hash1(band) - 0.5) * 0.18 : 0.0;
// occasional full-row color inversion band
if (hash1(floor(t * 17.0) + band * 0.13) > 0.96)
shift += (hash1(band * 5.1) - 0.5) * 0.08;
uv.x += shift;
return uv;
}
// ---- CRT DAMAGE ----
float crt(vec2 uv, float t) {
float scan = sin(uv.y * iResolution.y * 1.5) * 0.06 * DAMAGE;
vec2 vig = uv * 2.0 - 1.0;
float v = 1.0 - dot(vig, vig) * 0.45 * DAMAGE;
float grain = (hash(uv * iResolution.xy + t * 1337.0) - 0.5) * 0.10 * DAMAGE;
return v + scan + grain;
}
// ════════════════════════════════
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec2 uv = fragCoord / iResolution.xy;
float t = iTime;
float asp = iResolution.x / iResolution.y;
// glitch first
vec2 guv = glitch(uv, t);
vec2 p = (guv * 2.0 - 1.0) * vec2(asp, 1.0);
// dual-rotation polar fold
vec2 fp = polarFold(p, t);
// triple warp
vec2 wp = warp3(fp * 0.9 + 1.5, t);
// base field
float field = fbm(wp * 1.6, 5);
// CHROMATIC SPLIT — each channel warped separately with time offsets
float tR = t * 1.00;
float tG = t * 1.05;
float tB = t * 0.93;
vec2 wpR = warp3(fp * 0.9 + 1.5, tR);
vec2 wpG = warp3(fp * 0.9 + 1.7, tG);
vec2 wpB = warp3(fp * 0.9 + 1.3, tB);
float fR = fbm(wpR * 1.6, 5);
float fG = fbm(wpG * 1.6, 5);
float fB = fbm(wpB * 1.6, 5);
// razor ridges on each channel independently
float ridR = ridges(fR);
float ridG = ridges(fG + 0.07); // phase offset so they don't stack perfectly
float ridB = ridges(fB + 0.14);
// thin film per channel
vec3 filmR = thinFilm(fR, IRIDESCENCE);
vec3 filmG = thinFilm(fG, IRIDESCENCE);
vec3 filmB = thinFilm(fB, IRIDESCENCE);
const vec3 PINK = vec3(1.00, 0.07, 0.57);
const vec3 CYAN = vec3(0.00, 0.95, 1.00);
const vec3 LIME = vec3(0.60, 1.00, 0.00);
const vec3 VIOLET = vec3(0.55, 0.00, 1.00);
const vec3 WHITE = vec3(1.00, 0.95, 1.00);
// build color from per-channel film
vec3 col;
col.r = filmR.r * PINK.r + filmR.g * LIME.r + filmR.b * CYAN.r;
col.g = filmG.r * PINK.g + filmG.g * LIME.g + filmG.b * CYAN.g;
col.b = filmB.r * PINK.b + filmB.g * LIME.b + filmB.b * CYAN.b;
// violet bleeds into depth
float depth = 1.0 - smoothstep(0.1, 0.5, field);
col = mix(col, VIOLET * 0.9, depth * 0.65);
// smash razor ridges on top — additive, so they blow out
col += vec3(ridR, ridG, ridB) * vec3(PINK.r, LIME.g, CYAN.b) * 2.5;
// additive bloom: sample field neighbors and pile on
float blm = 0.0;
float br = 0.012;
blm += ridges(fbm(warp3((fp+vec2(br,0))*0.9+1.5,t)*1.6, 4));
blm += ridges(fbm(warp3((fp-vec2(br,0))*0.9+1.5,t)*1.6, 4));
blm += ridges(fbm(warp3((fp+vec2(0,br))*0.9+1.5,t)*1.6, 4));
blm += ridges(fbm(warp3((fp-vec2(0,br))*0.9+1.5,t)*1.6, 4));
col += col * (blm / 4.0) * 1.2;
// strobe pulse on exposure
float strobe = 1.0 + sin(t * 13.0) * STROBE * 0.5
+ sin(t * 7.3) * STROBE * 0.3;
col *= strobe;
// CRT damage
col *= crt(uv, t);
// gamma + exposure push — intentionally hot
col = pow(max(col, 0.0), vec3(0.75)) * 1.45;
// ACES (it will try its best, bless its heart)
col = col*(2.51*col+0.03) / (col*(2.43*col+0.59)+0.14);
fragColor = vec4(clamp(col, 0.0, 1.0), 1.0);
}
Image
Not used
More shaders by merrypranxter
- PRISMACORE_002 — After Work Sky
- PRISMACORE_001 — Liquid Crystal Fever Dream
- Hopf Vibratiok in pink
- Void Geode
- Project_2026-02-14_04-53-13
- Chromatic Plexus
- Signal Death
- Deep fried math
- Candy Interference
- Project_2026-02-12_21-16-32
- Project_2026-02-12_17-00-28
- Project_2026-02-12_16-17-50
Browse all public shaders · All shaders by merrypranxter · ShaderKit home
Unread comments
×Projects with new comments. Click to open and read.