Superformula — Parametric Shape Reference

Overview

The superformula (Johan Gielis, 2003) is a single parametric equation that generalizes the circle into an enormous family of natural-looking shapes: starfish, flowers, leaves, crystals, gears, and everything in between. It’s one of the highest return-on-investment equations in generative art — six parameters produce infinite organic variety.

Cinder’s SuperformulaGPU sample demonstrates GPU-accelerated superformula meshes with animated parameter interpolation. The math is pure and portable to any platform.

The Equation

The superformula defines a radius as a function of angle θ:

r(θ) = ( |cos(m₁·θ/4) / a|^n₂ + |sin(m₂·θ/4) / b|^n₃ )^(-1/n₁)

Parameters:

When m₁ = m₂ = m, a = b = 1 (the common case), it simplifies to:

r(θ) = ( |cos(m·θ/4)|^n₂ + |sin(m·θ/4)|^n₃ )^(-1/n₁)

Shape Presets

Namemn₁n₂n₃Character
Circle4222Perfect circle
Square4100100100Rounded square (high n → sharp corners)
Triangle3100100100Rounded triangle
Starfish50.30.30.3Five-armed starfish
Flower6111Six-petaled flower
Asteroid40.50.50.5Four-pointed star with concave sides
Clover30.50.50.5Three-lobed clover
Gear12101010Gear-like with 12 teeth
Petal10.51.50.5Asymmetric leaf/petal
Blob70.21.71.7Organic amoeba-like
Spike50.11.71.7Spiky radial form
Cushion420.50.5Puffy rounded square

2D Implementation (p5.js / Canvas)

function superformula(theta, m, n1, n2, n3, a = 1, b = 1) {
  const t1 = Math.abs(Math.cos(m * theta / 4) / a);
  const t2 = Math.abs(Math.sin(m * theta / 4) / b);
  const r = Math.pow(Math.pow(t1, n2) + Math.pow(t2, n3), -1 / n1);
  return r;
}

// Draw a superformula shape
function drawSuperformula(cx, cy, scale, m, n1, n2, n3, steps = 360) {
  p.beginShape();
  for (let i = 0; i <= steps; i++) {
    const theta = (i / steps) * Math.PI * 2;
    const r = superformula(theta, m, n1, n2, n3) * scale;
    const x = cx + r * Math.cos(theta);
    const y = cy + r * Math.sin(theta);
    p.vertex(x, y);
  }
  p.endShape(p.CLOSE);
}

3D Supershape (spherical product)

To create 3D supershapes, evaluate two superformulas at latitude (φ) and longitude (θ), then combine on a sphere:

function supershape3D(theta, phi, m1, n11, n12, n13, m2, n21, n22, n23) {
  const r1 = superformula(theta, m1, n11, n12, n13);
  const r2 = superformula(phi, m2, n21, n22, n23);

  const x = r1 * Math.cos(theta) * r2 * Math.cos(phi);
  const y = r1 * Math.sin(theta) * r2 * Math.cos(phi);
  const z = r2 * Math.sin(phi);

  return { x, y, z };
}

// Generate mesh vertices
function buildSupershapeMesh(res, params) {
  const vertices = [];
  for (let i = 0; i <= res; i++) {
    const phi = (i / res) * Math.PI - Math.PI / 2;  // -π/2 to π/2
    for (let j = 0; j <= res; j++) {
      const theta = (j / res) * Math.PI * 2;         // 0 to 2π
      const p = supershape3D(theta, phi,
        params.m1, params.n11, params.n12, params.n13,
        params.m2, params.n21, params.n22, params.n23
      );
      vertices.push(p.x, p.y, p.z);
    }
  }
  return vertices;
}

Three.js Scene Mode

function createSupershapeGeometry(THREE, res, params) {
  const geo = new THREE.BufferGeometry();
  const positions = [];
  const normals = [];
  const indices = [];

  // Generate grid of vertices
  for (let i = 0; i <= res; i++) {
    const phi = (i / res) * Math.PI - Math.PI / 2;
    for (let j = 0; j <= res; j++) {
      const theta = (j / res) * Math.PI * 2;
      const p = supershape3D(theta, phi, /* params */);
      positions.push(p.x, p.y, p.z);
    }
  }

  // Generate triangle indices
  for (let i = 0; i < res; i++) {
    for (let j = 0; j < res; j++) {
      const a = i * (res + 1) + j;
      const b = a + 1;
      const c = a + (res + 1);
      const d = c + 1;
      indices.push(a, c, b);
      indices.push(b, c, d);
    }
  }

  geo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
  geo.setIndex(indices);
  geo.computeVertexNormals();
  return geo;
}

GLSL Implementation (shader mode)

float superformula(float theta, float m, float n1, float n2, float n3) {
  float t1 = abs(cos(m * theta / 4.0));
  float t2 = abs(sin(m * theta / 4.0));
  return pow(pow(t1, n2) + pow(t2, n3), -1.0 / n1);
}

// In a vertex shader or raymarching SDF:
vec3 supershape(float theta, float phi, float m1, float n11, float n12, float n13,
                float m2, float n21, float n22, float n23) {
  float r1 = superformula(theta, m1, n11, n12, n13);
  float r2 = superformula(phi, m2, n21, n22, n23);
  return vec3(
    r1 * cos(theta) * r2 * cos(phi),
    r1 * sin(theta) * r2 * cos(phi),
    r2 * sin(phi)
  );
}

Animation Techniques

Parameter Morphing

Interpolate between preset parameter sets for smooth shape transitions:

function lerpParams(presetA, presetB, t) {
  const e = easeInOutCubic(t);  // smooth the interpolation
  return {
    m:  presetA.m  * (1 - e) + presetB.m  * e,
    n1: presetA.n1 * (1 - e) + presetB.n1 * e,
    n2: presetA.n2 * (1 - e) + presetB.n2 * e,
    n3: presetA.n3 * (1 - e) + presetB.n3 * e,
  };
}

Cycle through presets on a timer for hypnotic morphing loops.

Noise-Modulated Parameters

const m = baseM + noise(time * 0.1) * 2;
const n1 = baseN1 + noise(time * 0.15 + 100) * 0.5;

Produces organic breathing/pulsing shapes.

Radial Noise Displacement

Apply noise displacement to the radius after computing the superformula:

const r = superformula(theta, m, n1, n2, n3);
const displaced = r * (1 + noise(theta * 3, time) * 0.2);

Compositional Patterns

Parameter Design

Expose these as named presets with a dropdown, plus individual sliders for exploration:

Demos in the gallery