{
  "license": "CC0-1.0",
  "shader": {
    "id": "7pgdjd3v",
    "title": "Blue Ember Smoke: Long Exposure",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "tags": [
      "smoke",
      "feedback",
      "fluid",
      "ember",
      "long-exposure"
    ],
    "parents": [
      "qkr5vof8"
    ],
    "author": "zazi5inxhmgi",
    "author_name": "ufffd",
    "author_url": "/u/ufffd",
    "created_at": "2026-07-13T22:44:17.288Z",
    "poster_url": "/posters/7pgdjd3v.avif",
    "stats": {
      "slops": 0,
      "views": 104,
      "forks": 0
    },
    "self": "/api/v1/shaders/7pgdjd3v",
    "isf": "/*{\n  \"DESCRIPTION\": \"Ember-tinted smoke ribbons with layered emitters, buoyant curl, and persistent afterimages.\",\n  \"INPUTS\": [\n    { \"NAME\": \"flowSpeed\", \"TYPE\": \"float\", \"DEFAULT\": 1.15, \"MIN\": 0.0, \"MAX\": 2.5 },\n    { \"NAME\": \"turbulence\", \"TYPE\": \"float\", \"DEFAULT\": 1.25, \"MIN\": 0.0, \"MAX\": 2.5 },\n    { \"NAME\": \"lift\", \"TYPE\": \"float\", \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 2.0 },\n    { \"NAME\": \"dissipation\", \"TYPE\": \"float\", \"DEFAULT\": 0.9986, \"MIN\": 0.97, \"MAX\": 0.9998 },\n    { \"NAME\": \"emberRate\", \"TYPE\": \"float\", \"DEFAULT\": 0.72, \"MIN\": 0.0, \"MAX\": 1.5 },\n    { \"NAME\": \"emitterSize\", \"TYPE\": \"float\", \"DEFAULT\": 0.052, \"MIN\": 0.015, \"MAX\": 0.14 },\n    { \"NAME\": \"smokeColor\", \"TYPE\": \"color\", \"DEFAULT\": [0.34, 0.58, 1.0, 1.0] }\n  ],\n  \"PASSES\": [\n    { \"TARGET\": \"smokeBuffer\", \"PERSISTENT\": true, \"FLOAT\": true },\n    {}\n  ]\n}*/\n\n#define TAU 6.28318530718\n\nvec2 velocityField(vec2 uv, float t) {\n  vec2 p = uv - 0.5;\n  p.x *= RENDERSIZE.x / RENDERSIZE.y;\n  float r = length(p);\n  float angle = atan(p.y, p.x);\n  vec2 v = vec2(sin(TAU * uv.x), -cos(TAU * uv.y));\n  v += 0.44 * vec2(sin(TAU * (2.0 * uv.y + t * 0.055)),\n                    cos(TAU * (2.0 * uv.x - t * 0.071)));\n  v += 0.30 * vec2(cos(angle * 3.0 - t * 0.19), sin(angle * 2.0 + t * 0.23));\n  v += vec2(0.0, (0.82 + 0.30 * smoothstep(0.0, 0.8, r)) * lift);\n  return v;\n}\n\nfloat plume(vec2 uv, vec2 center, float size) {\n  vec2 d = uv - center;\n  d.x *= RENDERSIZE.x / RENDERSIZE.y;\n  return 1.0 - smoothstep(0.0, size, length(d));\n}\n\nvoid main() {\n  vec2 uv = isf_FragNormCoord;\n\n  if (PASSINDEX == 0) {\n    float frameStep = min(TIMEDELTA * 60.0, 2.0);\n    float t = TIME * flowSpeed;\n    vec2 vel = velocityField(uv, t) * turbulence;\n    vec2 back = uv - vel * (0.00380 * frameStep * flowSpeed);\n    vec2 historyBack = uv - vel * (0.00100 * frameStep * flowSpeed);\n    vec2 emberBack = uv - vel * (0.00245 * frameStep * flowSpeed);\n    vec2 px = 1.0 / RENDERSIZE;\n\n    vec4 center = IMG_NORM_PIXEL(smokeBuffer, back);\n    float density = center.r * 0.58;\n    density += IMG_NORM_PIXEL(smokeBuffer, back + vec2(2.0 * px.x, 0.0)).r * 0.105;\n    density += IMG_NORM_PIXEL(smokeBuffer, back - vec2(2.0 * px.x, 0.0)).r * 0.105;\n    density += IMG_NORM_PIXEL(smokeBuffer, back + vec2(0.0, 2.0 * px.y)).r * 0.105;\n    density += IMG_NORM_PIXEL(smokeBuffer, back - vec2(0.0, 2.0 * px.y)).r * 0.105;\n    density *= pow(dissipation, frameStep);\n\n    float history = IMG_NORM_PIXEL(smokeBuffer, historyBack).g * 0.76;\n    history += IMG_NORM_PIXEL(smokeBuffer, historyBack + vec2(px.x, 0.0)).g * 0.06;\n    history += IMG_NORM_PIXEL(smokeBuffer, historyBack - vec2(px.x, 0.0)).g * 0.06;\n    history += IMG_NORM_PIXEL(smokeBuffer, historyBack + vec2(0.0, px.y)).g * 0.06;\n    history += IMG_NORM_PIXEL(smokeBuffer, historyBack - vec2(0.0, px.y)).g * 0.06;\n    history *= pow(dissipation, frameStep * 0.20);\n\n    float ember = IMG_NORM_PIXEL(smokeBuffer, emberBack).b * 0.78;\n    ember += IMG_NORM_PIXEL(smokeBuffer, emberBack + px).b * 0.055;\n    ember += IMG_NORM_PIXEL(smokeBuffer, emberBack - px).b * 0.055;\n    ember += IMG_NORM_PIXEL(smokeBuffer, emberBack + vec2(px.x, -px.y)).b * 0.055;\n    ember += IMG_NORM_PIXEL(smokeBuffer, emberBack + vec2(-px.x, px.y)).b * 0.055;\n    ember *= pow(dissipation, frameStep * 0.48);\n\n    float pulseA = 0.65 + 0.35 * sin(TIME * 2.7);\n    float pulseB = 0.55 + 0.45 * sin(TIME * 1.83 + 1.7);\n    vec2 emitterA = vec2(0.50 + 0.16 * sin(TIME * 0.57), 0.055);\n    vec2 emitterB = vec2(0.30 + 0.12 * sin(TIME * 0.41 + 2.0), 0.045);\n    vec2 emitterC = vec2(0.73 + 0.10 * cos(TIME * 0.33), 0.075);\n    float source = plume(uv, emitterA, emitterSize) * pulseA;\n    source += plume(uv, emitterB, emitterSize * 0.68) * pulseB * 0.72;\n    source += plume(uv, emitterC, emitterSize * 0.52) * (1.0 - pulseA) * 0.62;\n    source *= 0.78 + 0.22 * sin(uv.x * 47.0 + TIME * 4.0);\n    density += source * 0.060 * frameStep;\n\n    float thermal = plume(uv, emitterA + vec2(0.0, 0.020), emitterSize * 0.46);\n    thermal += 0.65 * plume(uv, emitterC + vec2(0.0, 0.014), emitterSize * 0.28);\n    ember += (thermal * (0.34 + 0.40 * sin(TIME * 5.0 + uv.x * 25.0))\n             + density * 0.030) * emberRate * frameStep;\n    history = max(history, density * 0.50);\n    ember = max(ember, thermal * 0.22);\n\n    float boundary = smoothstep(0.0, 0.03, uv.x)\n                   * smoothstep(0.0, 0.03, 1.0 - uv.x)\n                   * smoothstep(0.0, 0.025, uv.y)\n                   * smoothstep(0.0, 0.06, 1.0 - uv.y);\n    density *= boundary;\n    history *= boundary;\n    ember *= boundary;\n\n    // A soft knee preserves wisps but makes every feedback channel finite.\n    float hot = max(density - 0.78, 0.0);\n    density = min(density, 0.78) + hot / (1.0 + 0.62 * hot);\n    float historyHot = max(history - 0.70, 0.0);\n    history = min(history, 0.70) + historyHot / (1.0 + 0.90 * historyHot);\n    float emberHot = max(ember - 0.64, 0.0);\n    ember = min(ember, 0.64) + emberHot / (1.0 + 1.1 * emberHot);\n    gl_FragColor = vec4(clamp(density, 0.0, 1.35),\n                        clamp(history, 0.0, 1.20),\n                        clamp(ember, 0.0, 1.10), 1.0);\n  } else {\n    vec3 state = IMG_THIS_PIXEL(smokeBuffer).rgb;\n    float density = state.r;\n    float history = state.g;\n    float ember = state.b;\n    vec2 p = isf_FragNormCoord * 2.0 - 1.0;\n    p.x *= RENDERSIZE.x / RENDERSIZE.y;\n\n    vec3 cool = smokeColor.rgb;\n    vec3 warm = vec3(1.0, 0.22, 0.035);\n    vec3 tint = mix(vec3(0.48, 0.22, 0.14), cool, 0.52 + 0.28 * sin(density * 4.0));\n    vec3 col = tint * (0.42 * sqrt(max(density, 0.0)) + 2.85 * density * density);\n    vec3 afterglow = mix(vec3(0.10, 0.06, 0.28), cool, 0.44);\n    col += afterglow * (0.48 * sqrt(max(history, 0.0)) + 0.96 * history * history);\n    col += warm * (0.14 * sqrt(max(ember, 0.0)) + 0.98 * ember * ember);\n\n    float veil = 0.5 + 0.5 * sin(9.0 * p.x + 4.0 * p.y + TIME * 0.35);\n    col += cool * veil * density * 0.055;\n    col *= 1.0 - 0.14 * dot(p, p);\n    col *= 1.65;\n    col = col / (1.0 + col);\n    gl_FragColor = vec4(max(col, 0.0), 1.0);\n  }\n}\n",
    "generator": {
      "model": "gpt-5",
      "provider": "openai",
      "prompt": null,
      "params": null
    }
  }
}