Retrogrid
Three layers stack on top of each other:
- a perspective ground plane raymarched from a
rayDirvector — the classic outrun grid, colored per cell from the four-color palette; - a slowly-drifting epicycle system — nine nested rotating circles whose combined tip traces a Spirograph curve, painted with a heavy phosphor bloom;
- an Oort cloud — three concentric rings of 24 tiny particles at slightly different radii, each twinkling on its own hashed clock, meant to read as a distant shell of icy bodies.
The panel exposes pitch, zoom, and fisheye as sliders. pitch
tilts the camera about the horizon (0 = looking straight ahead, 90 =
straight down). fisheye blends between a perspective projection
(atan(r, 1)) and a barrel-distorted one (r * PI * 0.8), so at 1
the ground plane bulges into a hemisphere.
[glsl]: shader source
// Perspective grid + phosphor epicycle + Oort cloud shell.
// @slider tempo 0.1 3.0 1.0 0.05
// @slider pitch 0.0 90.0 65.0 1.0
// @slider zoom 0.5 2.0 1.0 0.05
// @slider fisheye 0.0 1.0 0.5 0.05
uniform float tempo;
uniform float pitch;
uniform float zoom;
uniform float fisheye;
#define PI 3.14159265
#define TAU 6.28318530
float hash(float p){
p = fract(p * 0.1031);
p *= p + 33.33;
p *= p + p;
return fract(p);
}
// nine nested rotating circles: sum of orbits at hash-derived periods
vec2 epicyclePos(float t, float seed){
vec2 pos = vec2(0.0);
for(int i = 0; i < 9; i++){
float fi = float(i);
float radius = 0.12 / (1.0 + fi * 0.55);
float period = 1.0 + floor(hash(seed + fi * 7.13) * 7.0);
float phase = hash(seed + fi * 3.71 + 10.0) * TAU;
float dir = hash(seed + fi * 5.37 + 20.0) > 0.5 ? 1.0 : -1.0;
pos += vec2(cos(dir * t * period + phase),
sin(dir * t * period + phase)) * radius;
}
return pos;
}
// phosphor bloom: hot white core + colored inner glow + wide halo
vec3 phosphor(float d, vec3 color){
float core = smoothstep(0.005, 0.001, d);
float inner = exp(-d * 220.0);
float outer = exp(-d * 60.0);
return vec3(1.0) * core * 1.5 + color * inner * 1.8 + color * outer * 0.5;
}
// draw one epicycle system with a phosphor-persistence trail
vec3 drawEpicycle(vec2 uv, float time, vec2 center, float seed, vec3 color){
vec2 local = uv - center;
vec3 result = vec3(0.0);
float speed = 0.06 + hash(seed + 50.0) * 0.04;
float t0 = time * speed;
// orbit ring visualisation: node at every pivot, ring around every orbit
vec2 pivot = vec2(0.0);
for(int i = 0; i < 9; i++){
float fi = float(i);
float radius = 0.12 / (1.0 + fi * 0.55);
float period = 1.0 + floor(hash(seed + fi * 7.13) * 7.0);
float phase = hash(seed + fi * 3.71 + 10.0) * TAU;
float dir = hash(seed + fi * 5.37 + 20.0) > 0.5 ? 1.0 : -1.0;
float angle = dir * t0 * period + phase;
float nodeDist = length(local - pivot);
float dimmer = 1.0 / (1.0 + fi * 0.3);
result += phosphor(nodeDist, color) * 0.3 * dimmer;
float ringDist = abs(length(local - pivot) - radius);
float ringLine = smoothstep(0.003, 0.0005, ringDist);
float ringBloom = exp(-ringDist * 120.0);
result += color * ringLine * 0.2 * dimmer + color * ringBloom * 0.06 * dimmer;
pivot += vec2(cos(angle), sin(angle)) * radius;
}
// phosphor-persistence trail: 100 backwards steps of exponentially decayed glow
float trailSpan = TAU * 4.0;
for(int i = 0; i < 100; i++){
float fi = float(i);
float tt = t0 - fi / 100.0 * trailSpan;
vec2 pos = epicyclePos(tt, seed);
float d = length(local - pos);
float fade = exp(-fi * 0.025);
float core = smoothstep(0.004, 0.0008, d);
float glow = exp(-d * 180.0);
result += color * (core * 0.6 + glow * 0.3) * fade;
}
// tip: full phosphor bloom
vec2 tipPos = epicyclePos(t0, seed);
float d = length(local - tipPos);
result += phosphor(d, color);
return result;
}
// three concentric rings of icy specks with orbital drift + twinkle
vec3 drawOortCloud(vec2 uv, float time){
vec3 result = vec3(0.0);
for(int layer = 0; layer < 3; layer++){
float fl = float(layer);
float ringRadius = 0.33 + fl * 0.055;
for(int i = 0; i < 24; i++){
float fi = float(i);
float seed = fi + fl * 100.0;
float baseAngle = fi / 24.0 * TAU + hash(seed) * 0.4;
float orbitSpeed = 0.012 + hash(seed + 1.0) * 0.02;
float dir = hash(seed + 2.0) > 0.5 ? 1.0 : -1.0;
float a = baseAngle + time * orbitSpeed * dir;
float r = ringRadius + (hash(seed + 3.0) - 0.5) * 0.045;
vec2 pos = vec2(cos(a), sin(a)) * r;
float d = length(uv - pos);
float size = 0.0008 + hash(seed + 4.0) * 0.0014;
float twinkle = 0.35 + 0.65 * sin(time * (0.3 + hash(seed + 5.0) * 1.5)
+ hash(seed + 6.0) * TAU);
vec3 iceColor = vec3(0.55, 0.72, 0.92) + vec3(
(hash(seed + 7.0) - 0.5) * 0.12,
(hash(seed + 8.0) - 0.5) * 0.08,
hash(seed + 9.0) * 0.08);
float core = smoothstep(size * 3.0, size * 0.2, d) * twinkle;
float halo = exp(-d * 300.0) * twinkle;
float bloom = exp(-d * 80.0) * twinkle;
result += iceColor * core * 0.9 + iceColor * halo * 0.4 + iceColor * bloom * 0.12;
}
}
// diffuse haze at the shell distance
float dist = length(uv);
float haze = exp(-pow((dist - 0.36) / 0.09, 2.0)) * 0.06;
result += vec3(0.4, 0.55, 0.8) * haze;
return result;
}
void mainImage( out vec4 fragColor, in vec2 fragCoord )
{
vec2 uv = fragCoord / iResolution.xy;
vec2 centered = uv - 0.5;
float aspect = iResolution.x / iResolution.y;
centered.x *= aspect;
centered *= zoom;
float r = length(centered);
float maxR = 0.8 * zoom;
vec3 c1 = iAccent;
vec3 c2 = vec3(0.35, 0.75, 1.00);
vec3 c3 = vec3(1.00, 0.35, 0.55);
vec3 c4 = vec3(0.55, 0.25, 0.85);
vec3 baseColor = vec3(0.0);
if(r < maxR){
// blend perspective and fisheye projections
float phi_fisheye = r * PI * 0.8;
float phi_persp = atan(r, 1.0);
float phi = mix(phi_persp, phi_fisheye, fisheye);
float theta = atan(centered.y, centered.x);
// rayDir points into the scene. At the projection origin (r=0) it
// looks straight up; larger r tilts it toward horizontal. The z sign
// is negated so screen +y (top of screen) maps to -z (behind camera
// after pitch), which puts the ground plane in the LOWER half of
// the screen — the standard outrun-grid look.
vec3 rayDir;
rayDir.x = sin(phi) * cos(theta);
rayDir.y = cos(phi);
rayDir.z = -sin(phi) * sin(theta);
// pitch the camera by the slider (0 = looking straight up,
// 90 = looking horizontally forward)
float pitchAngle = pitch * PI / 180.0;
float cy = cos(pitchAngle);
float sy = sin(pitchAngle);
vec3 tilted = vec3(
rayDir.x,
rayDir.y * cy - rayDir.z * sy,
rayDir.y * sy + rayDir.z * cy);
// hit-test against the ground plane (y = 0)
if(tilted.y < -0.001){
float tParam = -1.0 / tilted.y;
float x = tilted.x * tParam;
float z = tilted.z * tParam;
float movement = iTime * tempo * 5.0;
z += movement;
float gridSize = 1.0;
float lineWidth = 0.03;
float xGrid = mod(x, gridSize);
float zGrid = mod(z, gridSize);
float xAA = fwidth(x) * 1.5;
float zAA = fwidth(z) * 1.5;
float hLine = smoothstep(lineWidth + zAA, lineWidth, zGrid)
+ smoothstep(gridSize - lineWidth - zAA, gridSize - lineWidth, zGrid);
float vLine = smoothstep(lineWidth + xAA, lineWidth, xGrid)
+ smoothstep(gridSize - lineWidth - xAA, gridSize - lineWidth, xGrid);
float cellX = floor(x);
float cellZ = floor(z);
int colorIdx = int(mod(cellX + cellZ, 4.0));
vec3 lineColor;
if(colorIdx == 0) lineColor = c1;
else if(colorIdx == 1) lineColor = c2;
else if(colorIdx == 2) lineColor = c3;
else lineColor = c4;
float grid = min(1.0, hLine + vLine);
float dist = length(vec2(x, z - movement));
float fade = 1.0 - smoothstep(5.0, 30.0, dist);
grid *= fade;
baseColor = lineColor * grid * 0.3;
}
}
// overlays in screen space, aspect-corrected and zoom-independent
vec2 overlayUV = (uv - 0.5) * vec2(aspect, 1.0);
float dt = iTime * 0.04;
vec2 epiCenter = vec2(sin(dt * 0.7) * 0.05, cos(dt * 0.5) * 0.04);
vec3 epicycles = drawEpicycle(overlayUV, iTime, epiCenter, 1.0, c1);
vec3 oort = drawOortCloud(overlayUV, iTime);
fragColor = vec4(baseColor + epicycles + oort, 1.0);
}