About this technique →
mandelbulb/sketch.js
export const PARAMS = {
  power:    { value: 8.0, min: 2, max: 16, step: 0.1, label: "Fractal Power", folder: "Structure" },
  bailout:  { value: 4.0, min: 1, max: 32, step: 0.5, label: "Bailout", folder: "Structure" },
  iterations:{ value: 8, min: 2, max: 14, step: 1, label: "Iterations", folder: "Structure" },
  glow:     { value: 0.6, min: 0, max: 2, step: 0.01, label: "Glow", folder: "Appearance" },
  hue:      { value: 30, min: 0, max: 360, step: 1, label: "Hue Center", folder: "Appearance" },
};

export const SHARE = { bookmarked: ["power","bailout","iterations","glow","hue"] };

export function shaderUniforms(params, seed) {
  return {
    u_power:      { value: params.power },
    u_bailout:    { value: params.bailout },
    u_iterations: { value: params.iterations },
    u_glow:       { value: params.glow },
    u_hue:        { value: params.hue },
  };
}

export function shaderAnimate(uniforms, params, time) {
  uniforms.u_power.value = params.power;
  uniforms.u_bailout.value = params.bailout;
  uniforms.u_iterations.value = params.iterations;
  uniforms.u_glow.value = params.glow;
  uniforms.u_hue.value = params.hue;
}

export function fragmentShader() {
  return `
    uniform float u_time;
    uniform vec2  u_resolution;
    uniform float u_power;
    uniform float u_bailout;
    uniform float u_iterations;
    uniform float u_glow;
    uniform float u_hue;

    float DE(vec3 p) {
      vec3 z = p;
      float dr = 1.0;
      float r = 0.0;
      for (int i = 0; i < 14; i++) {
        if (float(i) >= u_iterations) break;
        r = length(z);
        if (r > u_bailout) break;
        float theta = acos(z.z / r);
        float phi   = atan(z.y, z.x);
        dr = pow(r, u_power - 1.0) * u_power * dr + 1.0;
        float zr = pow(r, u_power);
        theta *= u_power;
        phi   *= u_power;
        z = zr * vec3(sin(theta) * cos(phi), sin(phi) * sin(theta), cos(theta));
        z += p;
      }
      return 0.5 * log(r) * r / dr;
    }

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

    void main() {
      vec2 uv = (gl_FragCoord.xy - 0.5 * u_resolution) / u_resolution.y;
      vec3 ro = vec3(2.5 * cos(u_time * 0.2), 0.5, 2.5 * sin(u_time * 0.2));
      vec3 ta = vec3(0.0);
      vec3 ww = normalize(ta - ro);
      vec3 uu = normalize(cross(vec3(0.0, 1.0, 0.0), ww));
      vec3 vv = cross(ww, uu);
      vec3 rd = normalize(uv.x * uu + uv.y * vv + 1.5 * ww);

      float t = 0.0;
      float steps = 0.0;
      bool hit = false;
      for (int i = 0; i < 200; i++) {
        vec3 p = ro + rd * t;
        float d = DE(p);
        if (d < 0.0005) { hit = true; break; }
        if (t > 8.0) break;
        t += d;
        steps += 1.0;
      }

      vec3 col = vec3(0.02, 0.02, 0.04);
      if (hit) {
        col = palette(0.5 + 0.5 * cos(t * 1.0), u_hue);
      }
      col += u_glow * vec3(steps / 200.0) * palette(steps / 200.0, u_hue);
      col = pow(col, vec3(1.0 / 2.2));
      gl_FragColor = vec4(col, 1.0);
    }
  `;
}