Retrogrid

Three layers stack on top of each other:

  • a perspective ground plane raymarched from a rayDir vector — 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);
}
// settings
theme:
fx: