← shader.gallery
Mycelium Strand
‹ molten nacre ›
Post-processing

One-click post-FX looks — stack as many as you like. Each card's own sliders fine-tune it.

Embed this background

A one-line web component, loaded from the CDN.

Fragment shader

GLSL ES · MIT · yours to copy

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 E. T. Carter <support@shader.gallery>
// mycelium (Strand) - a hyphal colony spreading across dark substrate, seen
// from above under a black light. No cells anywhere: every thread is an open
// strand. The colony fans out from an origin off the lower-left corner as a
// family of leading hyphae (implicit radial lines in a slowly warped domain),
// and as the fan widens each new generation of child hyphae peels off its
// parent line at an acute angle and runs on outward, so branching keeps the
// spacing roughly even from the near corner to the far one. Short lateral
// twigs sprout off the runners as explicit polyline segments measured by
// distance-to-segment in a 3x3 cell neighbourhood. The line fields give
// thin uniform-width threads with dark ground between them.
// Growth is real: a ragged front of swollen bright tips sweeps outward
// across the frame, twigs extend behind it, and pulses of bioluminescence
// stream out along every thread. When one wave has crossed the frame the
// next seeds at the origin with a fresh warp and branching, and the old
// network dims into the soil beneath it, so the piece loops without ever
// un-growing. Under it all a combed haze of fine fibres and a mottled,
// gritty ground.
precision highp float;

uniform float u_time;        // seconds, monotonically increasing
uniform vec2  u_resolution;  // drawing-buffer size in device pixels
uniform vec2  u_mouse;       // pointer in device px, (0,0) when absent (unused)
uniform float u_pixelRatio;  // devicePixelRatio of the buffer
uniform vec3  u_palette[4];  // four theme colours, 0..1 rgb

// tweakable params (see meta.json; the runtime feeds defaults)
uniform float u_scale;    // runner density, leading hyphae per fan   (default 1.0)
uniform float u_speed;    // front advance and pulse speed            (default 1.0)
uniform float u_twigs;    // lateral twig density                      (default 0.8)
uniform float u_glow;     // bioluminescent halo, tips and pulses      (default 1.0)
uniform float u_wander;   // how far the hyphae curve off straight     (default 1.0)

const float TAU = 6.28318530718;
const vec2  ORIGIN = vec2(-1.15, -0.78);   // colony origin, off the lower-left corner
const float RMIN = 0.30;                   // radius where the near corner starts
const float RMAX = 2.45;                   // radius past the far corner

float hash21(vec2 p) {
  p = fract(p * vec2(234.34, 435.345));
  p += dot(p, p + 34.23);
  return fract(p.x * p.y);
}

float vnoise(vec2 p) {
  vec2 i = floor(p), f = fract(p);
  vec2 u = f * f * (3.0 - 2.0 * f);
  float a = hash21(i);
  float b = hash21(i + vec2(1.0, 0.0));
  float c = hash21(i + vec2(0.0, 1.0));
  float d = hash21(i + vec2(1.0, 1.0));
  return mix(mix(a, b, u.x), mix(c, d, u.x), u.y);
}

const mat2 M2 = mat2(0.80, 0.60, -0.60, 0.80);

float fbm(vec2 p) {
  float a = 0.5, s = 0.0;
  for (int i = 0; i < 3; i++) {
    s += a * vnoise(p);
    p = M2 * p * 2.03 + vec2(11.7, 5.3);
    a *= 0.5;
  }
  return s * 1.14;
}

vec2 dirOf(float a) { return vec2(cos(a), sin(a)); }

// where the growth front of generation g sits, in radius, for the line n of
// family f: a ragged margin, each hypha a little ahead or behind its fellows
float frontOf(float tg, float n, float f, float g) {
  float j = hash21(vec2(n * 1.7 + f * 13.1, g * 7.3)) - 0.5;
  return RMIN - 0.20 + (RMAX - RMIN + 0.45) * smoothstep(0.0, 0.85, tg) + j * 0.35;
}

// per-line angular offset in spacing units: the bend that peels a child line
// off its parent near its start (half a spacing, decaying outward) plus a
// small fixed jitter that only takes hold once the bend has relaxed, so no
// line ever crosses its own index boundary
float offsetOf(float r, float rs, float sgn, float jit) {
  float b = sgn * 0.49 * exp(-max(r - rs, 0.0) / 0.14);
  return b + jit * (1.0 - abs(b) / 0.49);
}
// radius where child line n of family f gives up: many hyphae simply stop
float lifeOf(float rs, float n, float f, float g) {
  float h = hash21(vec2(n * 3.1 + f * 7.7, g * 5.9 + 2.0));
  return rs + 0.25 + 2.4 * h * h;
}
// start radius of family f in generation g: where its lines branch off
float startOf(float f, float g) {
  if (f < 0.5) return RMIN - 0.3;
  return RMIN + 0.12 + f * 0.50 + (hash21(vec2(g, f)) - 0.5) * 0.28;
}

// one generation of the colony at warped point pw. Accumulates core coverage
// of the nearest line, its brightness, the halo and the tip glow.
void colony(vec2 pw, float g, float tg, float s0, float px,
            inout float core, inout float bright, inout float halo, inout float tip) {
  vec2  d  = pw - ORIGIN;
  float r  = length(d);
  float th = atan(d.y, d.x);
  float alpha = 1.0 - smoothstep(1.0, 1.85, tg);
  if (alpha <= 0.0) return;
  float s = s0;
  for (int f = 0; f < 5; f++) {
    float ff  = float(f);
    float rsf = startOf(ff, g);
    float u0  = th / s;
    float n   = floor(u0);
    float hn  = hash21(vec2(n, ff + g * 3.0));
    float sgn = (f == 0) ? 0.0 : (hn < 0.5 ? 1.0 : -1.0);
    float jit = (hash21(vec2(n * 1.3, ff * 2.1 + g)) - 0.5) * 0.30;
    float F   = fract(u0 + offsetOf(r, rsf, sgn, jit)) - 0.5;
    // perpendicular distance: |F| over the gradient magnitude of u
    float db  = -sgn * 0.49 / 0.14 * exp(-max(r - rsf, 0.0) / 0.14);
    float gu  = sqrt(1.0 / (s * s * r * r) + db * db);
    float dist = abs(F) / gu;
    // visibility: born at its start radius, alive up to the growth front or
    // until the hypha gives up on its own
    float front = min(frontOf(tg, n, ff, g), lifeOf(rsf, n, ff, g));
    float born  = smoothstep(rsf - 0.02, rsf + 0.02, r);
    float alive = smoothstep(front + 0.008, front - 0.008, r);
    float vis   = born * alive * alpha;
    // width: leaders thickest, each family finer, tapering to the tip
    float w = 0.0021 * pow(0.80, ff) * (1.0 - 0.4 * smoothstep(front - 0.5, front, r));
    float c = smoothstep(w + px, w - px * 0.6, dist) * vis;
    // the body brightens toward its tip where the cytoplasm is busiest
    float age = exp(-(front - r) * 1.1);
    float bb  = 0.55 + 0.45 * age;
    bright = mix(bright, bb, step(core, c) * step(0.001, c));
    core  = max(core, c);
    halo += vis * exp(-dist * dist * 3500.0) * (0.3 + 0.7 * age) * pow(0.85, ff);
    // apex: swollen bright tip at the end of every living line
    vec2  tipv = vec2(dist * 1.6, (r - front) * 0.9);
    float td   = dot(tipv, tipv);
    tip += alpha * born * exp(-td * 30000.0) * pow(0.9, ff);
    s *= 0.5;
  }
}

// one twig segment; see the twig layer below
void seg(vec2 q, vec2 a, vec2 b, float s0, float len, float w, float reach, float alpha,
         inout float minD, inout float minW, inout float minA, inout float halo, inout float tip) {
  float frac = clamp((reach - s0) / len, 0.0, 1.0);
  if (frac <= 0.0) return;
  vec2  ab = (b - a) * frac;
  vec2  aq = q - a;
  float h  = clamp(dot(aq, ab) / max(dot(ab, ab), 1e-6), 0.0, 1.0);
  float dd = length(aq - ab * h);
  float ww = w * (1.0 - 0.45 * (s0 + len * frac * h));
  if (dd < minD) { minD = dd; minW = ww; minA = alpha; }
  halo += alpha * exp(-dd * dd * 500.0);
  if (frac < 1.0) {
    vec2 tp = q - (a + ab);
    tip += alpha * exp(-dot(tp, tp) * 2500.0);
  }
}

// lateral twigs of generation g: short branching polylines rooted on the
// finest runner line of their cell and growing outward behind the front
void twigs(vec2 pw, float g, float tg, float s0, float cell, float density,
           inout float minD, inout float minW, inout float minA, inout float halo, inout float tip) {
  float alpha = 1.0 - smoothstep(1.0, 1.85, tg);
  if (alpha <= 0.0) return;
  vec2 q  = pw / cell;
  vec2 ci = floor(q);
  for (int y = -1; y <= 1; y++) {
    for (int x = -1; x <= 1; x++) {
      vec2  id = ci + vec2(float(x), float(y));
      vec2  gs = id + vec2(g * 5.3, g * 2.1);
      float h0 = hash21(gs);
      float h1 = hash21(gs + 11.1);
      float h2 = hash21(gs + 23.7);
      float h3 = hash21(gs + 37.3);
      if (h3 > density) continue;
      // find the finest family born at this cell and its nearest line, then
      // put the root exactly on that line, bend and jitter included
      vec2  pc = (id + 0.5) * cell;
      vec2  d  = pc - ORIGIN;
      float r  = length(d) + (h0 - 0.5) * 0.6 * cell;
      float th = atan(d.y, d.x);
      float s  = s0;
      float ff = 0.0;
      float rsf = startOf(0.0, g);
      for (int f = 1; f < 5; f++) {
        float rt = startOf(float(f), g);
        if (r > rt + 0.05) { s *= 0.5; ff = float(f); rsf = rt; }
      }
      float n   = floor(th / s);
      float hn  = hash21(vec2(n, ff + g * 3.0));
      float sgn = (ff < 0.5) ? 0.0 : (hn < 0.5 ? 1.0 : -1.0);
      float jit = (hash21(vec2(n * 1.3, ff * 2.1 + g)) - 0.5) * 0.30;
      float lineTh = (n + 0.5 - offsetOf(r, rsf, sgn, jit)) * s;
      vec2  root = ORIGIN + dirOf(lineTh) * r;
      // keep the root within reach of the cell so nothing clips at borders
      root = clamp(root / cell, id - 0.35, id + 1.35);
      float front = min(frontOf(tg, n, ff, g), lifeOf(rsf, n, ff, g));
      // grows once the front has passed its root; none past a dead end
      float reach = smoothstep(0.0, 0.45, front - r) * 0.8;
      // outward lateral: off the runner at an acute angle, then wavering on
      float sb = h1 < 0.5 ? 1.0 : -1.0;
      float a0 = lineTh + sb * (0.45 + 0.5 * h2);
      float a1 = a0 - sb * (0.2 + 0.5 * h0);
      vec2  P1 = root + dirOf(a0) * 0.36;
      vec2  P2 = P1 + dirOf(a1) * 0.30;
      vec2  B1 = P1 + dirOf(a0 + sb * (0.5 + 0.4 * h3)) * 0.20;
      float w  = 0.012 * (0.8 + 0.4 * h2);
      seg(q, root, P1, 0.00, 0.34, w,        reach, alpha, minD, minW, minA, halo, tip);
      seg(q, P1,   P2, 0.34, 0.28, w * 0.75, reach, alpha, minD, minW, minA, halo, tip);
      seg(q, P1,   B1, 0.34, 0.18, w * 0.55, reach, alpha, minD, minW, minA, halo, tip);
    }
  }
}

void main() {
  vec3 c0 = u_palette[0], c1 = u_palette[1], c2 = u_palette[2], c3 = u_palette[3];
  if (dot(c0,c0)+dot(c1,c1)+dot(c2,c2)+dot(c3,c3) < 1e-5) {
    c0 = vec3(0.231,0.510,0.965); c1 = vec3(0.659,0.333,0.969);
    c2 = vec3(0.133,0.827,0.933); c3 = vec3(0.957,0.247,0.369);
  }

  vec2  uv     = gl_FragCoord.xy / u_resolution.xy;
  float aspect = u_resolution.x / u_resolution.y;
  vec2  p      = vec2((uv.x - 0.5) * aspect, uv.y - 0.5);
  float spd    = clamp(u_speed, 0.0, 3.0);
  float t      = u_time * spd / 32.0 + 0.22;          // one wave per 32 s
  float tp     = u_time * spd;
  float s0     = 0.17 / max(u_scale, 0.3);            // angular spacing of the leaders
  float glow   = clamp(u_glow, 0.0, 2.0);
  float wander = clamp(u_wander, 0.0, 2.0);
  float dens   = clamp(u_twigs, 0.0, 1.0);
  float px     = 1.0 / u_resolution.y;                // frame units per device px

  float gen = floor(t);
  float tgA = t - gen;          // the growing wave
  float tgB = tgA + 1.0;        // the previous wave, dimming

  // ---- ground: dark damp substrate with pulp mottle and grit
  float mott = fbm(p * 3.0 + 4.0);
  float grit = hash21(floor(gl_FragCoord.xy / max(u_pixelRatio, 1.0) * 0.5));
  vec3  soil = mix(vec3(0.030, 0.027, 0.025), c3 * 0.11 + c0 * 0.03, 0.75);
  soil *= 0.70 + 0.60 * mott;
  soil += (grit - 0.5) * 0.012;
  float colonyF = fbm(p * 0.9 + vec2(1.0, 5.0) + tp * 0.006);
  vec3  hazeCol = mix(c2, c0, smoothstep(0.35, 0.65, fbm(p * 0.6 + 13.0)));
  soil += hazeCol * 0.04 * glow * smoothstep(0.35, 0.8, colonyF);

  // ---- fine fuzz: the felt of finest hyphae the colony lays down, streaks
  // aligned with the radial flow, very faint
  vec2  dp   = p - ORIGIN;
  float rp   = length(dp);
  float thp  = atan(dp.y, dp.x);
  float fz1  = vnoise(vec2(thp * 90.0, rp * 5.0) + 7.0);
  float fz2  = vnoise(vec2(thp * 170.0, rp * 9.0) + 19.0);
  float fmat = smoothstep(0.60, 0.92, fz1) * 0.6 + smoothstep(0.66, 0.94, fz2) * 0.4;
  fmat *= 0.4 + 0.6 * smoothstep(0.30, 0.75, colonyF);
  vec3  matCol = mix(c2, c1, 0.4) * 0.14 * glow + vec3(0.04);
  vec3  col = soil + matCol * fmat;

  // ---- the two waves, each in its own warped domain
  vec2 warpA = vec2(fbm(p * 1.4 + vec2(3.1, 7.7) + gen * 4.7),
                    fbm(p * 1.4 + vec2(9.2, 1.4) + gen * 2.9)) - 0.5;
  vec2 warpB = vec2(fbm(p * 1.4 + vec2(3.1, 7.7) + (gen - 1.0) * 4.7),
                    fbm(p * 1.4 + vec2(9.2, 1.4) + (gen - 1.0) * 2.9)) - 0.5;
  // a fine wobble so the threads are never ruler-straight
  vec2 wob = vec2(vnoise(p * 9.0 + 41.0), vnoise(p * 9.0 + 77.0)) - 0.5;
  vec2 pwA = p + (warpA * 0.30 + wob * 0.014) * wander;
  vec2 pwB = p + (warpB * 0.30 + wob * 0.014) * wander;

  // previous wave first: it sits beneath
  float coreB = 0.0, brB = 0.0, haloB = 0.0, tipB = 0.0;
  colony(pwB, gen - 1.0, tgB, s0, px, coreB, brB, haloB, tipB);
  float coreA = 0.0, brA = 0.0, haloA = 0.0, tipA = 0.0;
  colony(pwA, gen, tgA, s0, px, coreA, brA, haloA, tipA);

  // twigs on both waves
  float cell = 0.13;
  float tD = 10.0, tW = 0.02, tA = 0.0, tHalo = 0.0, tTip = 0.0;
  twigs(pwB, gen - 1.0, tgB, s0, cell, dens, tD, tW, tA, tHalo, tTip);
  twigs(pwA, gen, tgA, s0, cell, dens, tD, tW, tA, tHalo, tTip);
  float pxC = px / cell;
  float twigCore = smoothstep(tW + pxC, tW - pxC * 0.5, tD) * tA;

  // pulses of light streaming outward along every thread
  float rA = length(pwA - ORIGIN);
  float pulse = 0.5 + 0.5 * cos(rA * 26.0 - tp * 1.6);
  pulse = pulse * pulse; pulse = pulse * pulse;

  // ---- colour. Halo of bioluminescence, then the pale hyphal bodies
  vec3 haloCol = mix(c2, c1, smoothstep(0.3, 0.7, colonyF));
  float haloAll = haloB * 0.5 + haloA + tHalo * 0.6;
  col += haloCol * 0.22 * glow * min(haloAll, 2.5);

  // old wave: dimmer, cooler, sunk into the ground
  vec3 bodyB = mix(vec3(0.60, 0.60, 0.57), c2, 0.45) * (0.6 + 0.3 * brB);
  col = mix(col, bodyB, coreB * 0.9);
  col = mix(col, bodyB * 0.9, twigCore * 0.8);
  // new wave: pale translucent body lit from within by the pulse
  vec3 bodyA = mix(vec3(0.90, 0.88, 0.82), c2, 0.28) * (0.55 + 0.45 * brA);
  bodyA *= 0.75 + 0.55 * pulse * glow;
  bodyA += c0 * 0.25 * pulse * glow;
  col = mix(col, bodyA, coreA);
  col = mix(col, bodyA, twigCore * step(0.001, tA) * 0.9);

  // apex glow: swollen bright tips along the advancing margin
  vec3 tipCol = mix(c1, vec3(1.0), 0.45);
  col += tipCol * min(tipA + tipB * 0.3 + tTip * 0.5, 1.4) * (0.4 + 0.5 * glow);

  gl_FragColor = vec4(col, 1.0);
}