About this technique →
worley/sketch.js
export const PARAMS = {
  density:    { value: 8.0, min: 1.5, max: 30, step: 0.1, label: "Cell Density", folder: "Structure" },
  edgeSharp:  { value: 0.4, min: 0.05, max: 1.5, step: 0.01, label: "Edge Sharpness", folder: "Appearance" },
  warp:       { value: 0.2, min: 0, max: 1.0, step: 0.01, label: "Domain Warp", folder: "Behavior" },
  speed:      { value: 0.15, min: 0, max: 1.0, step: 0.01, label: "Animation Speed", folder: "Behavior" },
  hue:        { value: 200, min: 0, max: 360, step: 1, label: "Base Hue", folder: "Appearance" },
};

export function shaderUniforms(params) {
  return {
    u_density:   { value: params.density },
    u_edgeSharp: { value: params.edgeSharp },
    u_warp:      { value: params.warp },
    u_speed:     { value: params.speed },
    u_hue:       { value: params.hue },
  };
}

export function shaderAnimate(uniforms, params) {
  uniforms.u_density.value = params.density;
  uniforms.u_edgeSharp.value = params.edgeSharp;
  uniforms.u_warp.value = params.warp;
  uniforms.u_speed.value = params.speed;
  uniforms.u_hue.value = params.hue;
}

export function fragmentShader() {
  return `
    uniform float u_time;
    uniform vec2  u_resolution;
    uniform float u_density;
    uniform float u_edgeSharp;
    uniform float u_warp;
    uniform float u_speed;
    uniform float u_hue;

    vec2 hash22(vec2 p) {
      p = vec2(dot(p, vec2(127.1, 311.7)), dot(p, vec2(269.5, 183.3)));
      return -1.0 + 2.0 * fract(sin(p) * 43758.5453);
    }

    vec2 worley2(vec2 p) {
      vec2 i = floor(p);
      vec2 f = fract(p);
      float F1 = 999.0, F2 = 999.0;
      for (int y=-1; y<=1; y++) for (int x=-1; x<=1; x++) {
        vec2 g = vec2(float(x), float(y));
        vec2 o = 0.5 + 0.5 * sin(u_time * u_speed + 6.2831 * hash22(i + g));
        float d = length(g + o - f);
        if (d < F1) { F2 = F1; F1 = d; } else if (d < F2) { F2 = d; }
      }
      return vec2(F1, F2);
    }

    vec3 palette(float t, float baseHue) {
      vec3 a = vec3(0.5), b = vec3(0.5), c = vec3(1.0);
      vec3 d = vec3(0.0, 0.10, 0.20) + baseHue / 360.0;
      return a + b * cos(6.2831853 * (c * t + d));
    }

    void main() {
      vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;

      // Domain warp
      vec2 q = uv;
      if (u_warp > 0.001) {
        vec2 wp = uv * u_density * 0.4;
        vec2 warpV = u_warp * vec2(
          sin(wp.x * 1.7 + u_time * 0.5) * cos(wp.y * 1.3),
          cos(wp.x * 1.4) * sin(wp.y * 1.1 + u_time * 0.3)
        );
        q += warpV;
      }

      vec2 v = worley2(q * u_density);
      float edge = v.y - v.x;
      // F2-F1 small at edges, large at centers -- invert and sharpen
      float membrane = 1.0 - smoothstep(0.0, u_edgeSharp, edge);

      vec3 cellCol = palette(v.x * 0.5, u_hue);
      vec3 edgeCol = vec3(0.95, 0.95, 0.98);
      vec3 col = mix(cellCol, edgeCol, membrane * 0.7);
      col = pow(col, vec3(1.0 / 2.2));
      gl_FragColor = vec4(col, 1.0);
    }
  `;
}