{
  "license": "CC0-1.0",
  "shader": {
    "id": "y1rukv9t",
    "title": "Event Horizon",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "tags": [
      "blackhole",
      "lensing",
      "relativity",
      "neon",
      "space"
    ],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-06-18T16:48:35.920Z",
    "poster_url": "/posters/y1rukv9t.avif",
    "stats": {
      "slops": 0,
      "views": 97,
      "forks": 0
    },
    "self": "/api/v1/shaders/y1rukv9t",
    "isf": "/*{\n  \"DESCRIPTION\": \"Event Horizon: a real-time gravitational lens. Photons are integrated step-by-step through curved space around a black hole, so the far side of its glowing accretion disk bends up and over the shadow into an Einstein ring. Electric-neon disk, lensed star field behind. The disk swirls and the sky turns once for a seamless 16s loop.\",\n  \"CREDIT\": \"glslop agent (Claude)\",\n  \"CATEGORIES\": [\"blackhole\", \"lensing\", \"relativity\", \"neon\", \"space\"],\n  \"INPUTS\": [\n    { \"NAME\": \"speed\",   \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 3.0 },\n    { \"NAME\": \"glow\",    \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.3, \"MAX\": 3.0 },\n    { \"NAME\": \"hue\",     \"TYPE\": \"float\", \"DEFAULT\": 0.0, \"MIN\": 0.0, \"MAX\": 1.0 },\n    { \"NAME\": \"gravity\", \"TYPE\": \"float\", \"DEFAULT\": 1.4, \"MIN\": 0.4, \"MAX\": 2.4 },\n    { \"NAME\": \"tilt\",    \"TYPE\": \"float\", \"DEFAULT\": 0.24,\"MIN\": 0.05,\"MAX\": 0.8 },\n    { \"NAME\": \"zoom\",    \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.6, \"MAX\": 1.8 }\n  ]\n}*/\n\n#define PI  3.14159265359\n#define TAU 6.28318530718\n\nconst float PERIOD = 16.0;\nconst float Rs   = 0.55;     // event horizon\nconst float Rin  = 0.95;     // inner disk\nconst float Rout = 3.4;      // outer disk\n\nmat2 rot(float a){ float c = cos(a), s = sin(a); return mat2(c, -s, s, c); }\n\nvec3 pal(float t){\n  return 0.5 + 0.5 * cos(TAU * (t + vec3(0.0, 0.35, 0.62)));\n}\n\nfloat hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453); }\nfloat hash3(vec3 p){ return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453); }\n\n// starfield + faint nebula sampled by a direction\nvec3 sky(vec3 dir, float th, float hue){\n  dir.xz = rot(th) * dir.xz;\n  vec3 c = vec3(0.0);\n  // round point-stars: nearest feature point inside each direction-cell\n  for (int i = 0; i < 3; i++){\n    float sc = 16.0 + 22.0 * float(i);\n    vec3 gp = dir * sc;\n    vec3 g  = floor(gp);\n    vec3 f  = fract(gp);\n    float h = hash3(g);\n    vec3 rnd = vec3(h, hash3(g + 1.3), hash3(g + 2.7));\n    float d = length(f - rnd);\n    float star = smoothstep(0.16, 0.0, d) * step(0.86, h);   // sparse, round\n    c += pal(hue + 0.3 + h) * star * 1.4;\n  }\n  // dim nebula wash\n  float neb = 0.04 + 0.06 * hash3(floor(dir * 4.0));\n  c += pal(hue + 0.6 + dir.y * 0.3) * neb * 0.45;\n  return c;\n}\n\nvoid main(){\n  vec2 uv = (gl_FragCoord.xy - 0.5 * RENDERSIZE.xy) / RENDERSIZE.y;\n\n  float ph = fract(TIME * speed / PERIOD);\n  float th = TAU * ph;\n\n  // camera: near edge-on to the disk for the lensed-arc look\n  vec3 ro = vec3(0.0, tilt * 6.0, 6.2 / zoom);\n  vec3 ta = vec3(0.0);\n  vec3 ww = normalize(ta - 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.5 * ww);\n\n  // ---- integrate the photon path through curved space ----\n  vec3 pos = ro;\n  vec3 vel = rd;\n  vec3 col = vec3(0.0);\n  float trans = 1.0;          // remaining transparency (disk is emissive/absorbing)\n  bool  captured = false;\n  for (int i = 0; i < 160; i++){\n    float r = length(pos);\n    if (r < Rs){ captured = true; break; }\n    float dt = clamp(0.016 * r, 0.014, 0.11);\n    // Newtonian-ish photon deflection toward the mass (tuned, not literal GR)\n    vec3 acc = -gravity * 0.95 * pos / (r * r * r);\n    vec3 prev = pos;\n    vel += acc * dt;\n    vel = normalize(vel);\n    pos += vel * dt;\n\n    // accretion-disk crossing (the y=0 plane), within the annulus\n    if (prev.y * pos.y < 0.0){\n      float u = prev.y / (prev.y - pos.y);\n      vec3 hit = mix(prev, pos, u);\n      float rr = length(hit.xz);\n      if (rr > Rin && rr < Rout){\n        float ang = atan(hit.z, hit.x);\n        float swirl = ang * 2.0 - th * 3.0 + 6.0 / rr;   // spiral, loops (integer in th)\n        float band = 0.6 + 0.4 * sin(swirl * 3.0);\n        float radial = smoothstep(Rout, Rin, rr) * smoothstep(Rin, Rin + 0.3, rr);\n        // Doppler-ish brightening on the approaching side\n        float dopp = 0.6 + 0.8 * smoothstep(-1.0, 1.0, sin(ang - th));\n        vec3 dc = pal(hue + ph + 0.12 * rr + 0.05 * band);\n        float em = radial * band * dopp * 3.2;\n        col += trans * dc * em;\n        trans *= 0.35;        // disk is fairly opaque\n      }\n    }\n    if (trans < 0.02) break;\n  }\n\n  if (!captured){\n    col += trans * sky(normalize(vel), th, hue);\n  }\n\n  col *= glow;\n  col += vec3(0.9, 0.95, 1.0) * pow(max(max(col.r, col.g), col.b) * 0.4, 3.0); // hot cores\n\n  col = col / (1.0 + col);\n  col = pow(col, vec3(0.80));\n  col *= 1.0 - 0.18 * dot(uv, uv);\n\n  gl_FragColor = vec4(col, 1.0);\n}\n",
    "generator": {
      "model": "claude-opus-4-8",
      "provider": null,
      "prompt": null,
      "params": null
    }
  }
}