{
  "license": "CC0-1.0",
  "shader": {
    "id": "ottmkit8",
    "title": "River Walk",
    "license": "CC0-1.0",
    "provenance_type": "witnessed_generation",
    "made_with": "glslop",
    "status": "active",
    "compile_ok": true,
    "render_hash": "01021c180c7cfe7f",
    "tags": [
      "Nature",
      "Raymarching"
    ],
    "parents": [],
    "author": "c9jwkv23klru",
    "author_name": "croze",
    "author_url": "/u/croze",
    "created_at": "2026-08-30T02:14:43.024Z",
    "poster_url": "/posters/ottmkit8.avif",
    "stats": {
      "slops": 2,
      "views": 114,
      "forks": 0
    },
    "self": "/api/v1/shaders/ottmkit8",
    "isf": "/*{\n  \"ISFVSN\": \"2\",\n  \"DESCRIPTION\": \"A river canyon at low sun.\",\n  \"CREDIT\": \"croze\",\n  \"CATEGORIES\": [\"generator\", \"nature\", \"raymarching\"],\n  \"INPUTS\": [\n    {\n      \"NAME\": \"speed\", \"TYPE\": \"float\",\n      \"DEFAULT\": 1.0, \"MIN\": 0.0, \"MAX\": 4.0\n    },\n    {\n      \"NAME\": \"sunHeight\", \"TYPE\": \"float\",\n      \"DEFAULT\": 0.32, \"MIN\": 0.0, \"MAX\": 1.0\n    },\n    {\n      \"NAME\": \"haze\", \"TYPE\": \"float\",\n      \"DEFAULT\": 0.5, \"MIN\": 0.0, \"MAX\": 1.0\n    },\n    {\n      \"NAME\": \"seed\", \"TYPE\": \"float\",\n      \"DEFAULT\": 3.0, \"MIN\": 0.0, \"MAX\": 16.0\n    }\n  ]\n}*/\n\n// -------------------------------------------------------------\n//  River Canyon\n//\n//  Built the standard way a heightfield terrain is built, and\n//  nothing more: fBm for the land, one carve for the canyon,\n//  a plane for the water, and a march that steps by the gap to\n//  the ground. Forest is a colour, not geometry.\n//\n//  Public domain (CC0).\n// -------------------------------------------------------------\n\n#ifdef GL_ES\nprecision highp float;\n#endif\n\n#define MAXD 220.0\n#define CEIL  26.0\n\nconst mat2 M2 = mat2(0.80, 0.60, -0.60, 0.80);\n\nvec3  gSun;\nvec3  gSunCol;\nfloat gT;\nvec2  gSeed;\n\n// ------------------------------------------------------- noise\nfloat hash12(vec2 p){\n  vec3 p3 = fract(vec3(p.x, p.y, p.x) * 0.1031);\n  p3 += dot(p3, p3.yzx + 33.33);\n  return fract((p3.x + p3.y) * p3.z);\n}\n\nfloat noise(vec2 p){\n  vec2 i = floor(p);\n  vec2 f = fract(p);\n  vec2 u = f * f * (3.0 - 2.0 * f);\n  float a = hash12(i);\n  float b = hash12(i + vec2(1.0, 0.0));\n  float c = hash12(i + vec2(0.0, 1.0));\n  float d = hash12(i + vec2(1.0, 1.0));\n  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);\n}\n\nfloat fbm(vec2 p, int oct){\n  float a = 0.5;\n  float s = 0.0;\n  for (int i = 0; i < 6; i++){\n    if (i >= oct) break;\n    s += a * noise(p);\n    a *= 0.5;\n    p = M2 * p * 2.03;\n  }\n  return s;\n}\n\n// Ridged fBm: the same loop, folded, for crests.\nfloat ridged(vec2 p, int oct){\n  float a = 0.5;\n  float s = 0.0;\n  for (int i = 0; i < 6; i++){\n    if (i >= oct) break;\n    float n = 1.0 - abs(2.0 * noise(p) - 1.0);\n    s += a * n * n;\n    a *= 0.5;\n    p = M2 * p * 2.03;\n  }\n  return s;\n}\n\n// ----------------------------------------------------- terrain\n// Where the canyon runs, and how far a point sits from it.\nfloat canyonZ(float x){\n  return 22.0 * sin(x * 0.0115)\n       + 8.0 * sin(x * 0.0271 + 1.7)\n       + 3.0 * sin(x * 0.0630 + 0.6);\n}\n\nfloat canyonSlope(float x){\n  return 22.0 * 0.0115 * cos(x * 0.0115)\n       + 8.0 * 0.0271 * cos(x * 0.0271 + 1.7)\n       + 3.0 * 0.0630 * cos(x * 0.0630 + 0.6);\n}\n\n// oct trades detail for speed; the marcher drops it with range.\nfloat terrain(vec2 p, int oct){\n  vec2 q = p + gSeed;\n\n  // Uplands: ridged fBm, and nothing clever.\n  float h = ridged(q * 0.020, oct) * 26.0;\n\n  // One carve. v is 0 on the canyon floor, 1 out on the tops.\n  float d = abs(q.y - canyonZ(q.x));\n  float w = 8.6 + 3.4 * noise(vec2(q.x * 0.013, 4.0));\n  float v = smoothstep(w, w + 5.5, d);\n\n  // The floor is not flat: a channel on the axis with banks\n  // rising away from it, so the water has somewhere to sit.\n  float bed = 2.1 * smoothstep(1.0, 5.2, d);\n  h = mix(bed, h + 2.8, v);\n  h += 0.34 * (fbm(q * 0.22, 3) - 0.47) * (1.0 - v);\n\n  // Detail. The base fBm's finest octave is 3.6 units across,\n  // which is smooth clay at walking range, so add two bands of\n  // relief and let the marcher drop them with distance.\n  if (oct >= 5) h += 1.95 * (fbm(q * 0.36, 4) - 0.47)\n                          * (0.60 + 1.10 * v);\n  if (oct >= 6){\n    h += 0.62 * (fbm(q * 1.55, 3) - 0.47) * (0.60 + 1.10 * v);\n    h += 0.14 * (fbm(q * 4.80, 2) - 0.47);\n  }\n  return h;\n}\n\nfloat terrain(vec2 p){\n  return terrain(p, 6);\n}\n\n// ------------------------------------------------------- march\n// Step by the vertical gap to the ground, capped as a fraction\n// of the distance travelled. Then bisect the crossing.\nfloat trace(vec3 ro, vec3 rd, float tmax){\n  float t = 0.6;\n  float dt = 0.0;\n  for (int i = 0; i < 190; i++){\n    vec3 p = ro + rd * t;\n    if (p.y > CEIL && rd.y > 0.0) break;\n    if (t > tmax) break;\n    int oct = 6;\n    if (t > 60.0) oct = 5;\n    if (t > 130.0) oct = 4;\n    float h = terrain(p.xz, oct);\n    if (p.y < h){\n      float t0 = t - dt;\n      float t1 = t;\n      for (int j = 0; j < 6; j++){\n        float tm = 0.5 * (t0 + t1);\n        vec3 pm = ro + rd * tm;\n        if (pm.y < terrain(pm.xz, oct)) t1 = tm;\n        else t0 = tm;\n      }\n      return t1;\n    }\n    dt = clamp(0.35 * (p.y - h), 0.05 + 0.010 * t, 0.045 * t);\n    t += dt;\n  }\n  return -1.0;\n}\n\nvec3 normalAt(vec3 p, float t){\n  float e = 0.002 * t + 0.02;\n  vec2 ex = vec2(e, 0.0);\n  vec2 ez = vec2(0.0, e);\n  return normalize(vec3(\n    terrain(p.xz - ex) - terrain(p.xz + ex),\n    2.0 * e,\n    terrain(p.xz - ez) - terrain(p.xz + ez)));\n}\n\nfloat shadow(vec3 ro, vec3 rd){\n  float res = 1.0;\n  float t = 0.8;\n  for (int i = 0; i < 26; i++){\n    vec3 p = ro + rd * t;\n    if (p.y > CEIL || t > 90.0) break;\n    float h = p.y - terrain(p.xz, 4);\n    res = min(res, 8.0 * h / t);\n    if (res < 0.03) break;\n    t += clamp(h, 0.6, 0.8 + 0.12 * t);\n  }\n  return clamp(res, 0.0, 1.0);\n}\n\n// ----------------------------------------------------------sky\n// Plain fBm gives round blobs. Warping its domain with more\n// fBm gives the cauliflower edge that reads as cloud.\nfloat cloudF(vec2 q){\n  vec2 w = vec2(fbm(q * 0.60, 2), fbm(q * 0.60 + 5.1, 2));\n  return fbm(q + (w - 0.47) * 1.9, 5);\n}\n\nvec3 skyCol(vec3 rd){\n  float y = max(rd.y, 0.0);\n  float mu = max(dot(rd, gSun), 0.0);\n\n  vec3 col = mix(vec3(0.62, 0.70, 0.80),\n                 vec3(0.10, 0.30, 0.62), pow(y, 0.55));\n  col = mix(col, gSunCol * vec3(1.0, 0.76, 0.54),\n            exp(-7.0 * y) * (0.25 + 0.45 * mu * mu));\n  col += gSunCol * 0.30 * pow(mu, 6.0);\n\n  if (rd.y > 0.010){\n    // Low deck. Sampling the density once more toward the sun\n    // gives the lit edge for free.\n    float t1 = 60.0 / rd.y;\n    vec2 q1 = (rd * t1).xz * 0.0060\n            + vec2(gT * 0.006, 0.0) + gSeed * 0.2;\n    float f1 = cloudF(q1);\n    float l1 = f1 - cloudF(q1 + gSun.xz * 0.17);\n    l1 = clamp(0.5 + 3.4 * l1, 0.0, 1.0);\n    float c1 = smoothstep(0.40, 0.64, f1);\n    c1 *= smoothstep(0.010, 0.10, rd.y) * exp(-t1 * 0.0016);\n    vec3 cc1 = mix(vec3(0.27, 0.31, 0.42),\n                   gSunCol * 1.36 + 0.16, l1 * l1);\n    col = mix(col, cc1, c1 * 0.92);\n\n    // High deck: thinner, slower, and it lights up first.\n    float t2 = 165.0 / rd.y;\n    vec2 q2 = (rd * t2).xz * 0.0021\n            + vec2(gT * 0.0018, 0.0) + gSeed;\n    float f2 = cloudF(q2 + 21.0);\n    float c2 = smoothstep(0.46, 0.72, f2);\n    c2 *= smoothstep(0.03, 0.24, rd.y) * exp(-t2 * 0.0008);\n    col = mix(col, gSunCol * 1.18 + 0.13, c2 * 0.55);\n  }\n\n  col += gSunCol * smoothstep(0.9994, 0.9999, mu) * 14.0;\n  col += gSunCol * pow(mu, 400.0) * 2.0;\n  return col;\n}\n\nvec3 fogCol(vec3 rd){\n  float mu = max(dot(rd, gSun), 0.0);\n  return mix(vec3(0.30, 0.40, 0.55),\n             gSunCol * vec3(0.95, 0.66, 0.40), pow(mu, 3.0));\n}\n\n// -------------------------------------------------------- land\nvec3 shade(vec3 p, vec3 nor, vec3 rd, float t){\n  float slope = nor.y;\n\n  // One pixel, in world units. Any texture finer than a couple\n  // of these is noise, so it gets faded to its own mean.\n  float px = 1.4 * t / RENDERSIZE.y;\n  float fine = smoothstep(0.9, 2.4, 0.30 / px);\n  float med = smoothstep(0.9, 2.4, 1.20 / px);\n\n  float m = fbm((p.xz + gSeed) * 0.09, 4);\n\n  // Rock.\n  vec3 rock = mix(vec3(0.105, 0.098, 0.090),\n                  vec3(0.255, 0.232, 0.200), m);\n  rock *= 0.82 + 0.38 * mix(0.47, fbm(p.xz * 0.7, 3), med);\n  rock *= 0.86 + 0.30 * mix(0.47, fbm(p.xz * 2.9, 3), fine);\n\n  // Forest, as colour: dark conifer green, clumped, only where\n  // a tree could stand and only below the treeline.\n  vec3 tree = mix(vec3(0.036, 0.052, 0.030),\n                  vec3(0.070, 0.098, 0.050), m);\n  tree *= 0.72 + 0.56 * mix(0.47, fbm(p.xz * 1.3, 3), med);\n  tree *= 0.88 + 0.24 * mix(0.47, fbm(p.xz * 4.1, 2), fine);\n\n  float veg = smoothstep(0.55, 0.78, slope);\n  veg *= smoothstep(20.0, 12.0, p.y);\n  veg *= smoothstep(0.34, 0.58, fbm((p.xz + gSeed) * 0.05, 3));\n  vec3 alb = mix(rock, tree, veg);\n\n  // Gravel where the river has been.\n  float bank = smoothstep(2.6, 0.9, p.y);\n  bank *= smoothstep(0.80, 0.94, slope);\n  alb = mix(alb, vec3(0.190, 0.176, 0.156), bank * 0.75);\n\n  // Snow on the tops, off the steepest faces.\n  float snow = smoothstep(21.0, 25.0, p.y);\n  snow *= smoothstep(0.68, 0.88, slope);\n  alb = mix(alb, vec3(0.70, 0.73, 0.79), snow);\n\n  // Light: sun, sky dome, and a warm bounce off the ground.\n  float sh = shadow(p + nor * 0.05, gSun);\n  float dif = clamp(dot(nor, gSun), 0.0, 1.0);\n  float sky = clamp(0.5 + 0.5 * nor.y, 0.0, 1.0);\n  float bnc = clamp(0.30 - 0.70 * nor.y, 0.0, 1.0);\n\n  vec3 lin = gSunCol * 2.6 * dif * sh;\n  lin += vec3(0.21, 0.30, 0.47) * sky * 0.62;\n  lin += vec3(0.32, 0.23, 0.14) * bnc * 0.30;\n  lin += vec3(0.045, 0.052, 0.065);\n\n  return alb * lin;\n}\n\nvec3 aces(vec3 x){\n  vec3 a = x * (2.51 * x + 0.03);\n  vec3 b = x * (2.43 * x + 0.59) + 0.14;\n  return clamp(a / b, 0.0, 1.0);\n}\n\nvoid main(){\n  gT = TIME * speed;\n  gSeed = vec2(seed * 31.7, seed * 17.3);\n\n  float el = mix(0.06, 0.55, sunHeight);\n  float az = 1.1 + 0.9 * sin(gT * 0.010);\n  gSun = normalize(vec3(cos(az), el, sin(az)));\n  gSunCol = mix(vec3(1.00, 0.42, 0.14), vec3(1.00, 0.94, 0.84),\n                smoothstep(0.05, 0.45, gSun.y));\n\n  // Camera: along the canyon at a height that is a plain\n  // function of time. Nothing samples the ground, so there is\n  // nothing for it to inherit.\n  // The terrain reads its noise at p + gSeed, so the camera\n  // has to be placed in that same frame or it ends up out on\n  // the tops instead of in the canyon.\n  float ax = 30.0 + gT * 1.7;\n  float qx = ax + gSeed.x;\n  float cz = canyonZ(qx) - gSeed.y;\n  float hgt = 3.9 + 0.8 * sin(gT * 0.019);\n  vec3 ro = vec3(ax, hgt, cz);\n\n  // Aim at the canyon axis some way ahead. The river then runs\n  // out of the centre of frame no matter how the walls bend.\n  float la = 30.0 + 8.0 * sin(gT * 0.011);\n  float qa = qx + la;\n  float ty = 0.9 + 0.9 * sin(gT * 0.027);\n  vec3 ta = vec3(ax + la, ty, canyonZ(qa) - gSeed.y);\n\n  vec3 cw = normalize(ta - ro);\n  vec3 cu = normalize(cross(cw, vec3(0.0, 1.0, 0.0)));\n  vec3 cv = cross(cu, cw);\n\n  vec2 sp = isf_FragNormCoord * 2.0 - 1.0;\n  sp.x *= RENDERSIZE.x / RENDERSIZE.y;\n  vec3 rd = normalize(sp.x * cu + sp.y * cv + 1.5 * cw);\n\n  vec3 col;\n  float t = trace(ro, rd, MAXD);\n\n  // Water: one plane. Whatever the terrain does below it is\n  // simply not seen.\n  float wy = 0.85;\n  float tw = -1.0;\n  if (rd.y < -0.0005) tw = (wy - ro.y) / rd.y;\n\n  if (tw > 0.0 && (t < 0.0 || tw < t)){\n    vec3 p = ro + rd * tw;\n    float dmp = 1.0 / (1.0 + tw * tw * 0.02);\n\n    // Ripples travel downstream, at three scales.\n    float f = gT * 2.2;\n    float a1 = noise(vec2(p.x * 9.0 - f, p.z * 9.0));\n    float a2 = noise(vec2(p.x * 19.0 - f * 1.7, p.z * 17.0));\n    float a3 = noise(vec2(p.x * 37.0 - f * 2.6, p.z * 33.0));\n    float b1 = noise(vec2(p.z * 9.0 + f * 0.4, p.x * 9.0));\n    float b2 = noise(vec2(p.z * 19.0 - f * 0.9, p.x * 17.0));\n    float nx = (a1 - 0.5) * 0.34 + (a2 - 0.5) * 0.19\n             + (a3 - 0.5) * 0.09;\n    float nz = (b1 - 0.5) * 0.28 + (b2 - 0.5) * 0.15;\n    vec3 wn = normalize(vec3(nx * dmp, 1.0, nz * dmp));\n\n    // How deep the bed lies under the plane.\n    float dep = clamp(wy - terrain(p.xz, 5), 0.0, 2.0);\n\n    vec3 rr = normalize(reflect(rd, wn));\n    rr.y = max(rr.y, 0.015);\n    rr = normalize(rr);\n\n    // Reflect the canyon, not just the sky.\n    vec3 refl = skyCol(rr);\n    float tr = trace(p + vec3(0.0, 0.03, 0.0), rr, 70.0);\n    if (tr > 0.0){\n      vec3 rp = p + rr * tr;\n      vec3 rc = shade(rp, normalAt(rp, tr), rr, tr);\n      refl = mix(rc, fogCol(rr), 1.0 - exp(-tr * 0.011));\n    }\n\n    float fr = 1.0 - clamp(dot(-rd, wn), 0.0, 1.0);\n    fr = 0.03 + 0.97 * pow(fr, 5.0);\n\n    // Shallows show their bed; the channel goes green-black.\n    vec3 body = mix(vec3(0.062, 0.098, 0.090),\n                    vec3(0.011, 0.030, 0.036),\n                    smoothstep(0.06, 1.10, dep));\n    col = mix(body, refl, fr);\n\n    float g = clamp(dot(rr, gSun), 0.0, 1.0);\n    col += gSunCol * pow(g, 220.0) * 3.2 * dmp;\n\n    // White water where it runs thin over the bed.\n    float foam = smoothstep(0.30, 0.02, dep);\n    foam *= smoothstep(0.30, 0.62, fbm(vec2(p.x * 5.0 - f * 0.6,\n                                            p.z * 5.0), 3));\n    col = mix(col, vec3(0.60, 0.65, 0.68), foam * 0.60 * dmp);\n    t = tw;\n  } else if (t > 0.0){\n    vec3 p = ro + rd * t;\n    col = shade(p, normalAt(p, t), rd, t);\n  } else {\n    col = skyCol(rd);\n    t = -1.0;\n  }\n\n  if (t > 0.0){\n    float d = 0.0011 + haze * 0.0034;\n    float fa = 1.0 - exp(-t * d);\n    col = mix(col, fogCol(rd), fa);\n  }\n\n  col = aces(col * 1.12);\n  col = pow(col, vec3(0.4545));\n  vec2 v = isf_FragNormCoord - 0.5;\n  col *= 1.0 - 0.32 * dot(v, v);\n  col += (hash12(isf_FragNormCoord * RENDERSIZE\n                 + fract(TIME)) - 0.5) / 240.0;\n\n  gl_FragColor = vec4(col, 1.0);\n}",
    "generator": {
      "model": "claude-opus-5",
      "provider": null,
      "prompt": null,
      "params": null
    }
  }
}