{
  "license": "CC0-1.0",
  "shader": {
    "id": "07m1ixly",
    "title": "Orbiting Prism Cluster",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop-skill",
    "status": "active",
    "compile_ok": true,
    "render_hash": "00187c7e7e3e1800",
    "tags": [
      "raymarching",
      "sdf",
      "csg",
      "iridescent",
      "thin-film",
      "seamless-loop"
    ],
    "parents": [],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-07-21T00:14:15.906Z",
    "poster_url": "/posters/07m1ixly.avif",
    "stats": {
      "slops": 0,
      "views": 224,
      "forks": 0
    },
    "self": "/api/v1/shaders/07m1ixly",
    "isf": "/*{\n  \"DESCRIPTION\": \"A cluster of smooth-blended glass gems orbiting a dark nucleus, shaded with thin-film iridescence: hue is driven by the view-normal grazing angle (fresnel), not by density, so it stays vivid instead of muddying into grey. A closest-ray-approach halo lights the void even off the geometry. Camera is fixed; an integer number of full turns per loop keeps the rotation exactly seamless.\",\n  \"CREDIT\": \"glslop agent (Claude)\",\n  \"CATEGORIES\": [\"raymarching\", \"sdf\", \"csg\", \"iridescent\", \"thin-film\", \"seamless-loop\"],\n  \"INPUTS\": [\n    { \"NAME\": \"turns\",       \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 1.0, \"MAX\": 3.0 },\n    { \"NAME\": \"spread\",      \"TYPE\": \"float\", \"DEFAULT\": 1.0,  \"MIN\": 0.6, \"MAX\": 1.6 },\n    { \"NAME\": \"iridescence\", \"TYPE\": \"float\", \"DEFAULT\": 1.1,  \"MIN\": 0.0, \"MAX\": 2.0 },\n    { \"NAME\": \"coreColor\",   \"TYPE\": \"color\", \"DEFAULT\": [0.07, 0.08, 0.13, 1.0] },\n    { \"NAME\": \"filmTint\",    \"TYPE\": \"color\", \"DEFAULT\": [0.25, 0.55, 1.0, 1.0] },\n    { \"NAME\": \"haloGlow\",    \"TYPE\": \"float\", \"DEFAULT\": 0.8,  \"MIN\": 0.0, \"MAX\": 2.0 },\n    { \"NAME\": \"exposure\",    \"TYPE\": \"float\", \"DEFAULT\": 1.3,  \"MIN\": 0.6, \"MAX\": 2.0 }\n  ]\n}*/\n\n#define PI 3.14159265359\n#define TAU 6.28318530718\n#define PERIOD 6.0\n#define NUM_GEMS 6\n\nfloat sdSphere(vec3 p, float r){ return length(p) - r; }\n\nfloat smin(float a, float b, float k){\n  float h = clamp(0.5 + 0.5*(b - a)/k, 0.0, 1.0);\n  return mix(b, a, h) - k*h*(1.0 - h);\n}\n\n// Everything here is a function of `ang` only (never raw TIME), and ang wraps\n// by an exact integer multiple of TAU every PERIOD seconds -- so the whole\n// scene state repeats exactly and the render loops seamlessly.\nfloat map(vec3 p, float ang){\n  float d = sdSphere(p, 0.40 + 0.03*sin(ang));\n\n  for (int i = 0; i < NUM_GEMS; i++){\n    float fi = float(i);\n    float a = fi * (TAU / float(NUM_GEMS)) + ang;\n    float orbitR = spread * (0.80 + 0.18*sin(fi*2.1));\n    vec3 c = vec3(cos(a)*orbitR, 0.26*sin(a*1.5), sin(a)*orbitR);\n    float r = 0.15 + 0.045*sin(fi*3.3 + ang);\n    d = smin(d, sdSphere(p - c, r), 0.32);\n  }\n  return d;\n}\n\nvec3 normalAt(vec3 p, float ang){\n  vec2 e = vec2(0.0015, 0.0);\n  return normalize(vec3(\n    map(p + e.xyy, ang) - map(p - e.xyy, ang),\n    map(p + e.yxy, ang) - map(p - e.yxy, ang),\n    map(p + e.yyx, ang) - map(p - e.yyx, ang)));\n}\n\nfloat softShadow(vec3 ro, vec3 rd, float ang, float maxT){\n  float res = 1.0, t = 0.03;\n  for (int i = 0; i < 24; i++){\n    float h = map(ro + rd*t, ang);\n    res = min(res, 10.0*h/t);\n    t += clamp(h, 0.01, 0.2);\n    if (res < 0.02 || t > maxT) break;\n  }\n  return clamp(res, 0.0, 1.0);\n}\n\nfloat ambientOcclusion(vec3 p, vec3 n, float ang){\n  float occ = 0.0, sc = 1.0;\n  for (int i = 1; i <= 5; i++){\n    float h = 0.02 + 0.06*float(i);\n    occ += (h - map(p + n*h, ang)) * sc;\n    sc *= 0.6;\n  }\n  return clamp(1.0 - 1.7*occ, 0.0, 1.0);\n}\n\nvoid main(){\n  vec2 uv = (gl_FragCoord.xy - 0.5*RENDERSIZE.xy) / RENDERSIZE.y;\n\n  float nTurns = floor(turns + 0.5);\n  float ph = fract(TIME / PERIOD);\n  float ang = nTurns * TAU * ph;\n\n  // Fixed camera -- the object spins, not the eye, so the loop lives entirely\n  // in `ang` and nothing about the camera path needs to be periodic.\n  vec3 ro = vec3(0.0, 0.55, 3.4);\n  vec3 ta = vec3(0.0, 0.0, 0.0);\n  vec3 fw = normalize(ta - ro);\n  vec3 rt = normalize(cross(fw, vec3(0.0, 1.0, 0.0)));\n  vec3 up = cross(rt, fw);\n  vec3 rd = normalize(fw*1.7 + rt*uv.x + up*uv.y);\n\n  float t = 0.0; float hit = -1.0;\n  for (int i = 0; i < 96; i++){\n    float d = map(ro + rd*t, ang);\n    if (d < 0.0008*max(1.0, t)){ hit = t; break; }\n    t += d*0.55;\n    if (t > 12.0) break;\n  }\n\n  // Closest approach of the ray to the origin -- gives a soft halo even where\n  // the ray misses all geometry, so the void around the cluster isn't flat black.\n  float tc = clamp(dot(-ro, rd), 0.0, 12.0);\n  vec3 closest = ro + rd*tc;\n  float haloD = length(closest);\n  vec3 filmCol = filmTint.rgb;\n\n  vec3 bg = mix(vec3(0.012, 0.014, 0.022), vec3(0.0), clamp(length(uv), 0.0, 1.0));\n  bg += filmCol * haloGlow * exp(-haloD*haloD*2.2) * 0.5;\n\n  vec3 col = bg;\n\n  if (hit > 0.0){\n    vec3 p = ro + rd*hit;\n    vec3 n = normalAt(p, ang);\n    vec3 v = -rd;\n    float ao = ambientOcclusion(p, n, ang);\n\n    vec3 lightDir = normalize(vec3(0.5, 0.8, 0.3));\n    float sh = softShadow(p + n*0.02, lightDir, ang, 3.0);\n    float ndl = max(dot(n, lightDir), 0.0);\n    float ndv = max(dot(n, v), 0.0);\n    float fres = pow(1.0 - ndv, 3.0);\n\n    vec3 base = coreColor.rgb;\n    // Thin-film iridescence: hue keyed to the fresnel angle, not to any density\n    // field, so it stays saturated at grazing angles instead of collapsing to mud.\n    // A lower exponent than a \"true\" fresnel spreads the film across most of the\n    // sphere instead of a knife-edge rim, so the colour actually reads.\n    float film = pow(1.0 - ndv, 2.0);\n    vec3 irid = 0.5 + 0.5*cos(TAU*(film*1.4 + iridescence*0.15 + vec3(0.0, 0.33, 0.67)));\n    vec3 h = normalize(lightDir + v);\n    float spec = pow(max(dot(n, h), 0.0), 48.0);\n\n    vec3 surf = base * (0.5 + 0.85*ndl*sh) * ao;\n    surf += mix(base, filmCol, 0.35) * 0.30 * ao;\n    surf += irid * film * iridescence * (0.7 + 0.3*ao);\n    surf += vec3(1.0) * spec * sh * 0.9;\n    surf += filmCol * fres * 0.30 * ao;\n    surf *= exp(-0.05*hit);\n\n    col = surf;\n  }\n\n  col = col / (1.0 + 0.6*col);\n  col = pow(max(col, 0.0), vec3(0.9)) * exposure;\n  gl_FragColor = vec4(clamp(col, 0.0, 1.0), 1.0);\n}\n",
    "generator": {
      "model": "claude-sonnet-5",
      "provider": "anthropic",
      "prompt": null,
      "params": null
    }
  }
}