{
  "license": "CC0-1.0",
  "shader": {
    "id": "jz3cgkp5",
    "title": "Apollonian Rapture",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "Claude Code",
    "status": "active",
    "compile_ok": true,
    "render_hash": "00203e3c3c7c0400",
    "tags": [
      "raymarch",
      "fractal",
      "apollonian",
      "iridescent",
      "3d",
      "thin-film",
      "orbit-trap",
      "generative"
    ],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-07-21T05:48:03.158Z",
    "poster_url": "/posters/jz3cgkp5.avif",
    "stats": {
      "slops": 0,
      "views": 169,
      "forks": 0
    },
    "self": "/api/v1/shaders/jz3cgkp5",
    "isf": "/*{\n  \"DESCRIPTION\": \"Apollonian Rapture — a fractal reliquary of infinitely nested spheres, folded through itself again and again per ray. Orbit traps paint every scale a different colour, the shell is translucent so you fall through crystal into crystal, and light splits into red/green/blue as it refracts. Seamless loop.\",\n  \"CREDIT\": \"Claude Opus 4.8\",\n  \"CATEGORIES\": [\"generative\", \"3d\", \"raymarch\", \"fractal\", \"abstract\"],\n  \"INPUTS\": [\n    { \"NAME\": \"fold\",     \"TYPE\": \"float\", \"DEFAULT\": 1.24, \"MIN\": 1.05, \"MAX\": 1.38 },\n    { \"NAME\": \"aberrate\", \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"heat\",     \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.0  },\n    { \"NAME\": \"sparks\",   \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.0,  \"MAX\": 2.0  }\n  ]\n}*/\n\n#define PI  3.14159265359\n#define TAU 6.28318530718\n#define PERIOD 24.0\n#define STEPS  120\n#define MAXD   9.0\n#define ROUT   1.24\n#define ZOOM   1.15      // fractal density inside the ball      // the fractal is carved out of a ball this big\n\nmat2 rot(float a){ float c = cos(a), s = sin(a); return mat2(c,-s,s,c); }\n\nfloat gSpin;\nvec4  gOrb;      // orbit trap from the last map() call -> drives all the colour\n\n// ---- Apollonian gasket: fold space into itself, inverting through a sphere\n// each time. Every iteration reveals a whole new generation of spheres. ----\nfloat map(vec3 p){\n  vec3 p0 = p;\n\n  // Tumble only -- no octant mirroring. The abs()+sort fold locked the gasket\n  // into an axis-aligned cross and inflated a few huge featureless spheres;\n  // the raw Apollonian is far more organic.\n  p.xz = rot(gSpin) * p.xz;\n  p.xy = rot(0.6 + gSpin * 2.0) * p.xy;\n\n  // pack more generations into the bounding ball: without this the ball holds\n  // only a few enormous spheres that swamp all the fine structure\n  p *= ZOOM;\n\n  float s = fold + 0.06 * sin(gSpin);   // the gasket breathes (periodic -> exact)\n  float scale = 1.0;\n  vec4  orb = vec4(1000.0);\n\n  for (int i = 0; i < 10; i++){\n    p = -1.0 + 2.0 * fract(0.5 * p + 0.5);      // fold into the unit cell\n    float r2 = dot(p, p);\n    orb = min(orb, vec4(abs(p), r2));           // orbit trap: nearest approach\n    float k = s / max(r2, 0.16);                // sphere inversion\n    p *= k;\n    scale *= k;\n  }\n  gOrb = orb;\n\n  // Pure fractal distance -- NO bounding sphere in here. `scale` compounds to\n  // enormous values in the fine generations, driving d to ~0 over whole\n  // regions; max()ing that against a bounding sphere made the RAY HIT THE\n  // SPHERE (a giant smooth shell). The ball is now clipped analytically in\n  // main() instead, so only real gasket geometry can ever be hit.\n  return 0.25 * abs(p.y) / scale / ZOOM;\n}\n\nvec3 calcNormal(vec3 p){\n  vec2 e = vec2(0.0013, 0.0);\n  return normalize(vec3(\n    map(p + e.xyy) - map(p - e.xyy),\n    map(p + e.yxy) - map(p - e.yxy),\n    map(p + e.yyx) - map(p - e.yyx)));\n}\n\nvec3 thinFilm(float ct, float thick){\n  float t = thick / max(ct, 0.40);\n  return 0.5 + 0.5 * cos(TAU * (t + vec3(0.00, 0.33, 0.67)));\n}\n\nvec3 env(vec3 d){\n  float up = d.y * 0.5 + 0.5;\n  vec3 c = mix(vec3(0.02, 0.03, 0.08), vec3(0.18, 0.28, 0.48), smoothstep(0.0, 1.0, up));\n  vec3 sd = normalize(vec3(0.55, 0.65, -0.4));\n  c += vec3(1.0, 0.90, 0.75) * pow(max(dot(d, sd), 0.0), 28.0) * 2.6;\n  c += vec3(0.35, 0.70, 1.0) * pow(max(dot(d, -sd), 0.0), 9.0) * 0.6;\n  return c;\n}\n\nfloat h31(vec3 p){ return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); }\nvec3  h33(vec3 p){\n  return fract(sin(vec3(dot(p, vec3(127.1, 311.7,  74.7)),\n                        dot(p, vec3(269.5, 183.3, 246.1)),\n                        dot(p, vec3(113.5, 271.9, 124.6)))) * 43758.5453);\n}\n\n// dots living exactly on a sphere of radius R (found analytically, not marched)\nfloat dotField(vec3 p, float R, float dens, float sz){\n  vec3 g = (p / R) * dens;\n  vec3 c = floor(g);\n  vec3 f = g - c - 0.5;\n  vec3 o = (h33(c) - 0.5) * 0.30;       // jitter + radius must stay under 0.5\n  float on = step(0.70, h31(c + 7.3));\n  float dd = length(f - o);\n  return on * (smoothstep(sz, 0.0, dd) + 0.30 * smoothstep(sz * 1.7, 0.0, dd));\n}\n\nfloat stars(vec3 d){\n  float s = sin(d.x * 130.0) * sin(d.y * 148.0) * sin(d.z * 121.0);\n  return pow(max(s, 0.0), 45.0);\n}\n\nvoid main(){\n  vec2 uv = (gl_FragCoord.xy - 0.5 * RENDERSIZE.xy) / RENDERSIZE.y;\n\n  float ph   = fract(TIME / PERIOD);\n  float spin = TAU * ph;\n  gSpin = spin;\n\n  // camera orbits, rising and falling once per loop\n  float ca = spin;\n  vec3 ro = vec3(3.70 * sin(ca), 1.10 * sin(2.0 * ca), 3.70 * cos(ca));\n  vec3 ww = normalize(-ro);\n  vec3 uu = normalize(cross(vec3(0.0, 1.0, 0.0), ww));\n  vec3 vv = cross(ww, uu);\n  vec3 rd = normalize(uv.x * uu + uv.y * vv + 1.45 * ww);\n\n  // ---- clip the ray to the bounding ball analytically, then march only the\n  // span that is actually inside it ----\n  float cb = dot(ro, rd);\n  float cc = dot(ro, ro) - ROUT * ROUT;\n  float cdisc = cb * cb - cc;\n  float tIn = 0.0, tOut = -1.0;\n  if (cdisc > 0.0){\n    float csq = sqrt(cdisc);\n    tIn  = max(-cb - csq, 0.0);\n    tOut = -cb + csq;\n  }\n\n  // ---- layered march: punch through crystal after crystal ----\n  vec3  acc   = vec3(0.0);\n  float trans = 1.0;\n  float glow  = 0.0;\n  float t     = tIn;\n\n  for (int i = 0; i < STEPS; i++){\n    if (t > tOut) break;                  // left the ball: nothing more to hit\n    vec3 p = ro + rd * t;\n    float d = map(p);\n\n    glow += exp(-abs(d) * 13.0) * 0.011;\n\n    if (d < 0.0018){\n      vec3 n = calcNormal(p);\n      float ct = clamp(dot(n, -rd), 0.0, 1.0);\n      vec4  orb = gOrb;\n\n      // orbit traps: each generation of spheres gets its own colour\n      float trap = clamp(orb.w * 2.2, 0.0, 1.0);\n      float thick = 0.85 + 2.10 * trap + 0.55 * orb.x + 0.30 * sin(spin);\n\n      // chromatic aberration: sample the film at 3 slightly different depths\n      float ab = aberrate * 0.16;\n      vec3 film = vec3(thinFilm(ct, thick - ab).r,\n                       thinFilm(ct, thick     ).g,\n                       thinFilm(ct, thick + ab).b);\n\n      vec3  L    = normalize(vec3(0.5, 0.72, -0.45));\n      float diff = max(dot(n, L), 0.0);\n      float fres = pow(max(1.0 - ct, 0.0), 4.0);\n\n      // saturate hard from the orbit trap so each generation is its own colour\n      vec3 gen = 0.5 + 0.5 * cos(TAU * (trap * 1.9 + orb.x * 0.9 + orb.z * 0.4 + vec3(0.0, 0.33, 0.67)));\n      // orbit-trap colour is the BASE; the thin film only modulates it, else\n      // the film's grey average washes every generation to the same pale tint\n      film = gen * (0.55 + 0.85 * film);\n      film = mix(vec3(dot(film, vec3(0.33))), film, 2.05);\n      vec3 c = film * (0.16 + 0.70 * diff);\n      c += env(reflect(rd, n)) * (0.06 + 0.70 * fres) * 0.55;\n      c += vec3(1.0, 0.95, 0.85)\n         * pow(max(dot(reflect(-L, n), -rd), 0.0), 46.0) * 0.75;\n\n      // the deepest generations glow hot -- the fractal is lit from within\n      c += vec3(1.0, 0.42, 0.12) * heat * 0.38 * (1.0 - trap);\n\n      // dissolve the clip boundary so the ball edge reads as a soft cloud\n      // instead of a hard-cut circle\n      float edgeFade = smoothstep(ROUT, ROUT * 0.88, length(p));\n      float a = clamp(0.20 + 0.55 * fres, 0.0, 1.0) * edgeFade;\n      acc   += c * a * trans;\n      trans *= 1.0 - a;\n\n      if (trans < 0.02) break;\n      t += 0.020;\n      continue;\n    }\n\n    t += d;\n    if (t > MAXD) break;\n  }\n\n  // ---- spark shells: analytic ray/sphere, one quadratic each ----\n  float bq = dot(ro, rd);\n  float cq = dot(ro, ro);\n  float tEnter = length(ro) - 2.2;\n  vec3  dots = vec3(0.0);\n\n  for (int i = 0; i < 4; i++){\n    float R = 0.58 + 0.48 * float(i);\n    float disc = bq * bq - (cq - R * R);\n    if (disc <= 0.0) continue;\n    float sq = sqrt(disc);\n    for (int k = 0; k < 2; k++){\n      float th = -bq + (k == 0 ? -sq : sq);\n      if (th <= 0.0) continue;\n      vec3 pq = ro + rd * th;\n      pq.xz = rot(gSpin * (1.0 + float(i))) * pq.xz;   // whole turns -> exact\n      pq.xy = rot(-gSpin * (1.0 + mod(float(i), 2.0))) * pq.xy;\n      float dv = dotField(pq, R, 34.0, 0.13);\n      if (dv <= 0.0) continue;\n      float w = mix(1.0, trans, clamp((th - tEnter) / 4.2, 0.0, 1.0));\n      vec3 tint = mix(vec3(1.0, 0.78, 0.42), vec3(0.55, 0.85, 1.0), float(i) / 3.0);\n      float tw = 0.75 + 0.45 * sin(spin * 2.0 + float(i) * 2.1 + th * 3.0);\n      dots += tint * dv * w * tw;\n    }\n  }\n\n  // burning core seen through whatever is still transparent\n  float b = length(cross(rd, -ro));\n  float core = exp(-b * b * 6.5) + 1.5 * exp(-b * b * 38.0);\n  vec3 heartCol = mix(vec3(1.0, 0.52, 0.16), vec3(1.0, 0.92, 0.70), core);\n  acc += heartCol * core * heat * 0.85 * trans;\n\n  vec3 col = acc + dots * sparks * 1.15;\n\n  vec3 hazeCol = mix(vec3(0.22, 0.42, 0.95), vec3(1.0, 0.38, 0.60),\n                     0.5 + 0.5 * sin(spin * 2.0));\n  col += hazeCol * glow * 0.85;\n\n  col += (vec3(0.012, 0.016, 0.035) + vec3(0.9, 0.95, 1.0) * stars(rd) * 0.45) * trans;\n\n  col *= 1.0 - 0.42 * dot(uv, uv);\n  col = col / (1.0 + col * 0.85);\n  col = pow(max(col, 0.0), vec3(0.87));\n\n  gl_FragColor = vec4(col, 1.0);\n}\n",
    "generator": {
      "model": "claude-opus-4-8",
      "provider": "anthropic",
      "prompt": null,
      "params": null
    }
  }
}