Skip to content

Instantly share code, notes, and snippets.

@munrocket
Last active May 3, 2024 13:49
Show Gist options
  • Star 33 You must be signed in to star a gist
  • Fork 4 You must be signed in to fork a gist
  • Save munrocket/30e645d584b5300ee69295e54674b3e4 to your computer and use it in GitHub Desktop.
Save munrocket/30e645d584b5300ee69295e54674b3e4 to your computer and use it in GitHub Desktop.
WGSL 2D SDF Primitives

WGSL 2D SDF Primitives

Revision: 06.08.2023, https://compute.toys/view/398

Circle - exact

fn sdCircle(p: vec2f, r: f32) -> f32 {
  return length(p) - r;
}

Rounded Box - exact

fn sdRoundedBox(p: vec2f, b: vec2f, r: vec4f) -> f32 {
  var x = r.x;
  var y = r.y;
  x = select(r.z, r.x, p.x > 0.);
  y = select(r.w, r.y, p.x > 0.);
  x  = select(y, x, p.y > 0.);
  let q = abs(p) - b + x;
  return min(max(q.x, q.y), 0.) + length(max(q, vec2f(0.))) - x;
}

Box - exact

fn sdBox(p: vec2f, b: vec2f) -> f32 {
  let d = abs(p) - b;
  return length(max(d, vec2f(0.))) + min(max(d.x, d.y), 0.);
}

Oriented Box - exact

fn sdOrientedBox(p: vec2f, a: vec2f, b: vec2f, th: f32) -> f32 {
  let l = length(b - a);
  let d = (b - a) / l;
  var q = p - (a + b) * 0.5;
  q = q * mat2x2<f32>(vec2f(d.x, d.y), vec2f(-d.y, d.x));
  q = abs(q) - vec2f(l, th) * 0.5;
  return length(max(q, vec2f(0.))) + min(max(q.x, q.y), 0.);
}

Segment - exact

fn sdSegment(p: vec2f, a: vec2f, b: vec2f) -> f32 {
  let pa = p - a;
  let ba = b - a;
  let h = clamp(dot(pa, ba) / dot(ba, ba), 0., 1.);
  return length(pa - ba * h);
}

Rhombus - exact

fn sdRhombus(p: vec2f, b: vec2f) -> f32 {
  let q = abs(p);
  let qb = dot(q, vec2f(b.x, -b.y));
  let bb = dot(b, vec2f(b.x, -b.y));
  let h = clamp((-2. * qb + bb) / dot(b, b), -1., 1.);
  let d = length(q - 0.5 * b * vec2f(1. - h, 1. + h));
  return d * sign(q.x * b.y + q.y * b.x - b.x * b.y);
}

Isosceles Trapezoid - exact

fn sdTrapezoid(p: vec2f, r1: f32, r2: f32, he: f32) -> f32 {
  let k1 = vec2f(r2, he);
  let k2 = vec2f(r2 - r1, 2. * he);
  let q = vec2f(abs(p.x), p.y);
  let ca = vec2f(q.x - min(q.x, select(r2, r1, q.y < 0.0)), abs(q.y) - he);
  let cb = q - k1 + k2 * clamp(dot(k1 - q, k2) / dot(k2, k2), 0., 1.);
  let s = select(1., -1., cb.x < 0.0 && ca.y < 0.0);
  return s * sqrt(min(dot(ca, ca), dot(cb, cb)));
}

Parallelogram - exact

fn sdParallelogram(p: vec2f, wi: f32, he: f32, sk: f32) -> f32 {
  let e = vec2f(sk, he);
  var q: vec2f = select(p, -p, p.y < 0.);
  // horizontal edge
  var w: vec2f = q - e;
  w.x = w.x - clamp(w.x, -wi, wi);
  var d: vec2f = vec2f(dot(w, w), -w.y);
  // vertical edge
  let s = q.x * e.y - q.y * e.x;
  q = select(q, -q, s < 0.);
  var v: vec2f = q - vec2f(wi, 0.);
  v = v - e * clamp(dot(v, e) / dot(e, e), -1., 1.);
  d = min(d, vec2f(dot(v, v), wi * he - abs(s)));
  return sqrt(d.x) * sign(-d.y);
}

Equilateral Triangle - exact

fn sdEquilateralTriangle(p: vec2f) -> f32 {
  let k = sqrt(3.);
  var q: vec2f = vec2f(abs(p.x) - 1.0, p.y + 1. / k);
  if (q.x + k * q.y > 0.) { q = vec2f(q.x - k * q.y, -k * q.x - q.y) / 2.; }
  q.x = q.x - clamp(q.x, -2., 0.);
  return -length(q) * sign(q.y);
}

Isosceles Triangle - exact

fn sdTriangleIsosceles(p: vec2f, c: vec2f) -> f32 {
  let q = vec2f(abs(p.x), p.y);
  let a = q - c * clamp(dot(q, c) / dot(c, c), 0., 1.);
  let b = q - c * vec2f(clamp(q.x / c.x, 0., 1.), 1.);
  let s = -sign(c.y);
  let d = min(vec2f(dot(a, a), s * (q.x * c.y - q.y * c.x)), vec2f(dot(b, b), s * (q.y - c.y)));
  return -sqrt(d.x) * sign(d.y);
}

Triangle - exact

fn sdTriangle(p: vec2f, p0: vec2f, p1: vec2f, p2: vec2f) -> f32 {
  let e0 = p1 - p0; let e1 = p2 - p1; let e2 = p0 - p2;
  let v0 = p - p0; let v1 = p - p1; let v2 = p - p2;
  let pq0 = v0 - e0 * clamp(dot(v0, e0) / dot(e0, e0), 0., 1.);
  let pq1 = v1 - e1 * clamp(dot(v1, e1) / dot(e1, e1), 0., 1.);
  let pq2 = v2 - e2 * clamp(dot(v2, e2) / dot(e2, e2), 0., 1.);
  let s = sign(e0.x * e2.y - e0.y * e2.x);
  let d = min(min(vec2f(dot(pq0, pq0), s * (v0.x * e0.y - v0.y * e0.x)),
                  vec2f(dot(pq1, pq1), s * (v1.x * e1.y - v1.y * e1.x))),
                  vec2f(dot(pq2, pq2), s * (v2.x * e2.y - v2.y * e2.x)));
  return -sqrt(d.x) * sign(d.y);
}

Uneven Capsule - exact

fn sdUnevenCapsule(p: vec2f, r1: f32, r2: f32, h: f32) -> f32 {
  let q = vec2f(abs(p.x), p.y);
  let b = (r1 - r2) / h;
  let a = sqrt(1. - b * b);
  let k = dot(q, vec2f(-b, a));
  if (k < 0.) { return length(q) - r1; }
  if (k > a * h) { return length(q - vec2f(0., h)) - r2; }
  return dot(q, vec2f(a, b)) - r1;
}

Regular Pentagon - exact

fn sdPentagon(p: vec2f, r: f32) -> f32 {
  let k = vec3f(0.809016994, 0.587785252, 0.726542528);
  var q: vec2f = vec2f(abs(p.x), p.y);
  q = q - 2. * min(dot(vec2f(-k.x, k.y), q), 0.) * vec2f(-k.x, k.y);
  q = q - 2. * min(dot(vec2f(k.x, k.y), q), 0.) * vec2f(k.x, k.y);
  q = q - vec2f(clamp(q.x, -r * k.z, r * k.z), r);
  return length(q) * sign(q.y);
}

Regular Hexagon - exact

fn sdHexagon(p: vec2f, r: f32) -> f32 {
  let k = vec3f(-0.866025404, 0.5, 0.577350269);
  var q: vec2f = abs(p);
  q = q - 2. * min(dot(k.xy, q), 0.) * k.xy;
  q = q - vec2f(clamp(q.x, -k.z * r, k.z * r), r);
  return length(q) * sign(q.y);
}

Regular Octogon - exact

fn sdOctogon(p: vec2f, r: f32) -> f32 {
  let k = vec3f(-0.9238795325, 0.3826834323, 0.4142135623);
  var q: vec2f = abs(p);
  q = q - 2. * min(dot(vec2f(k.x, k.y), q), 0.) * vec2f(k.x, k.y);
  q = q - 2. * min(dot(vec2f(-k.x, k.y), q), 0.) * vec2f(-k.x, k.y);
  q = q - vec2f(clamp(q.x, -k.z * r, k.z * r), r);
  return length(q) * sign(q.y);
}

Hexagram - exact

fn sdHexagram(p: vec2f, r: f32) -> f32 {
  let k = vec4f(-0.5, 0.8660254038, 0.5773502692, 1.7320508076);
  var q: vec2f = abs(p);
  q = q - 2. * min(dot(k.xy, q), 0.) * k.xy;
  q = q - 2. * min(dot(k.yx, q), 0.) * k.yx;
  q = q - vec2f(clamp(q.x, r * k.z, r * k.w), r);
  return length(q) * sign(q.y);
}

Star 5 - exact

fn sdStar5(p: vec2f, r: f32, rf: f32) -> f32 {
  let k1 = vec2f(0.809016994375, -0.587785252292);
  let k2 = vec2f(-k1.x, k1.y);
  var q: vec2f = vec2f(abs(p.x), p.y);
  q = q - 2. * max(dot(k1, q), 0.) * k1;
  q = q - 2. * max(dot(k2, q), 0.) * k2;
  q.x = abs(q.x);
  q.y = q.y - r;
  let ba = rf * vec2f(-k1.y, k1.x) - vec2f(0., 1.);
  let h = clamp(dot(q, ba) / dot(ba, ba), 0., r);
  return length(q - ba * h) * sign(q.y * ba.x - q.x * ba.y);
}

Regular Star - exact

fn sdStar(p: vec2f, r: f32, n: u32, m: f32) ->f32 {
  let an = 3.141593 / f32(n);
  let en = 3.141593 / m;
  let acs = vec2f(cos(an), sin(an));
  let ecs = vec2f(cos(en), sin(en));
  let bn = (atan2(abs(p.x), p.y) % (2. * an)) - an;
  var q: vec2f = length(p) * vec2f(cos(bn), abs(sin(bn)));
  q = q - r * acs;
  q = q + ecs * clamp(-dot(q, ecs), 0., r * acs.y / ecs.y);
  return length(q) * sign(q.x);
}

Pie - exact

fn sdPie(p: vec2f, sc: vec2f, r: f32) -> f32 {
  let q = vec2f(abs(p.x), p.y);
  let l = length(q) - r;
  let m = length(q - sc * clamp(dot(q, sc), 0., r));
  return max(l, m * sign(sc.y * q.x - sc.x * q.y));
}

Arc - exact

fn sdArc(p: vec2f, sc1: vec2f, sc2: vec2f, r1: f32, r2: f32) -> f32 {
  var q: vec2f = p * mat2x2<f32>(vec2f(sc1.x, sc1.y), vec2f(-sc1.y, sc1.x));
  q.x = abs(q.x);
  let k = select(length(q), dot(q, sc2), sc2.y * q.x > sc2.x * q.y);
  return sqrt(dot(q, q) + r1 * r1 - 2. * r1 * k) - r2;
}

Horseshoe - exact

fn sdHorseshoe(p: vec2f, sc: vec2f, r: f32, l: f32, w: f32) -> f32 {
  var q: vec2f = vec2f(abs(p.x), p.y);
  let m = length(p);
  q = q * mat2x2<f32>(vec2f(-sc.y, sc.x), vec2f(sc.x, sc.y));
  q = vec2f(select(m * sign(-sc.y), q.x, q.y > 0.0 || q.x > 0.), select(m, q.y, q.x > 0.));
  q = vec2f(q.x, abs(q.y - r)) - vec2f(l, w);
  return length(max(q, vec2f(0.))) + min(0., max(q.x, q.y));
}

Vesica - exact

fn sdVesica(p: vec2f, r: f32, d: f32) -> f32 {
  let q = abs(p);
  let b = sqrt(r * r - d * d);
  let cond = (q.y -b) * d > q.x * b;
  return select(length(q - vec2f(-d, 0.))-r, length(q - vec2f(0., b)), cond);
}

Moon - exact

fn sdMoon(p: vec2f, d: f32, ra: f32, rb: f32) -> f32 {
  let q = vec2f(p.x, abs(p.y));
  let a = (ra * ra - rb * rb + d * d) / (2. * d);
  let b = sqrt(max(ra * ra - a * a, 0.));
  if (d * (q.x * b - q.y * a) > d * d * max(b - q.y, 0.)) { return length(q-vec2f(a, b)); }
  return max((length(q) - ra), -(length(q - vec2f(d, 0.)) - rb));
}

Rounded Cross - exact

fn sdRoundedCross(p: vec2f, h: f32) -> f32 {
  let k = 0.5 * (h + 1. / h);
  let q = abs(p);
  let v1 = q - vec2f(1., k);
  let v2 = q - vec2f(0., h);
  let v3 = q - vec2f(1., 0.);
  let d1 = k - sqrt(dot(v1, v1));
  let d2 = sqrt(min(dot(v2, v2), dot(v3, v3)));
  return select(d2, d1, q.x < 1. && q.y < q.x * (k - h) + h);
}

Egg - exact

fn sdEgg(p: vec2f, ra: f32, rb: f32) -> f32 {
  let k = sqrt(3.);
  let q = vec2f(abs(p.x), p.y);
  let r = ra - rb;
  let d1 = length(q) - r;
  let d2 = length(vec2f(q.x,  q.y - k * r));
  let d3 = length(vec2f(q.x + r, q.y)) - 2. * r;
  return select(select(d3, d2, k * (q.x + r) < q.y), d1, q.y < 0.) - rb;
}

Heart - exact

fn sdHeart(p: vec2f) -> f32 {
  let q = vec2f(abs(p.x), p.y);
  let w = q - vec2f(0.25, 0.75);
  if (q.x + q.y > 1.0) { return sqrt(dot(w, w)) - sqrt(2.) / 4.; }
  let u = q - vec2f(0., 1.);
  let v = q - 0.5 * max(q.x + q.y, 0.);
  return sqrt(min(dot(u, u), dot(v, v))) * sign(q.x - q.y);
}

Cross - exact exterior, bound interior

fn sdCross(p: vec2f, b: vec2f) -> f32 {
  var q: vec2f = abs(p);
  q = select(q.xy, q.yx, q.y > q.x);
  let t = q - b;
  let k = max(t.y, t.x);
  let w = select(vec2f(b.y - q.x, -k), t, k > 0.);
  return sign(k) * length(max(w, vec2f(0.)));
}

Rounded X - exact

fn sdRoundedX(p: vec2f, w: f32, r: f32) -> f32 {
  let q = abs(p);
  return length(q - min(q.x + q.y, w) * 0.5) - r;
}

Polygon - exact

const N: i32 = 5;
fn sdPolygon(p: vec2f, v: ptr<function, array<vec2f, 5>>) -> f32 {
  let c = *v;
  var d = dot(p - c[0], p - c[0]);
  var s: f32 = 1.;
  for (var i: i32 = 0; i < N; i = i + 1) {
    let j = (i + 1) % N;
    let e = c[i] - c[j];
    let w = p - c[j];
    let b = w - e * clamp(dot(w, e) / dot(e, e), 0., 1.);
    d = min(d, dot(b, b));
    let c1 = p.y >= c[j].y;
    let c2 = p.y < c[i].y;
    let c3 = e.x * w.y > e.y * w.x;
    let c = vec3<bool>(c1, c2, c3);
    if (all(c) || all(!c)) { s = -s; };
  }
  return s * sqrt(d);
}

Ellipse - exact

fn sdEllipse(p: vec2f, ab: vec2f) -> f32 {
  var q: vec2f = abs(p);
  var e: vec2f = ab;
  if (q.x > q.y) {
    q = q.yx;
    e = ab.yx;
  }
  let l = e.y * e.y - e.x * e.x;
  let m = e.x * q.x / l;
  let m2 = m * m;
  let n = e.y * q.y / l;
  let n2 = n * n;
  let c = (m2 + n2 - 1.) / 3.;
  let c3 = c * c * c;
  let b = c3 + m2 * n2 * 2.;
  let d = c3 + m2 * n2;
  let g = m + m * n2;
  var co: f32;
  if (d < 0.) {
    let h = acos(b / c3) / 3.0;
    let s = cos(h);
    let t = sin(h) * sqrt(3.);
    let rx = sqrt(-c * (s + t + 2.0) + m2);
    let ry = sqrt(-c * (s - t + 2.0) + m2);
    co = (ry + sign(l) * rx + abs(g) / (rx * ry) - m) / 2.;
  } else {
    let h = 2. * m * n * sqrt(d);
    let s = sign(b + h) * pow(abs(b + h), 1. / 3.);
    let u = sign(b - h) * pow(abs(b - h), 1. / 3.);
    let rx = -s - u - c * 4. + 2. * m2;
    let ry = (s - u) * sqrt(3.);
    let rm = sqrt(rx * rx + ry * ry);
    co = (ry / sqrt(rm - rx) + 2. * g / rm - m) / 2.;
  }
  let r = e * vec2f(co, sqrt(1.0-co*co));
  return length(r - q) * sign(q.y - r.y);
}

Parabola - exact

fn sdParabola(pos: vec2f, k: f32) -> f32 {
  let p = vec2f(abs(pos.x), pos.y);
  let ik = 1. / k;
  let u = ik * (p.y - 0.5 * ik) / 3.;
  let v = 0.25 * ik * ik * p.x;
  let h = v * v - u * u * u;
  let r = sqrt(abs(h));
  let x = select(2. * cos(atan2(r, v) / 3.) * sqrt(u),
    pow(v + r, 1. / 3.) - pow(abs(v - r), 1. / 3.) * sign(r - v),
    h > 0.0);
  return length(p - vec2f(x, k * x * x)) * sign(p.x - x);
}

Parabola Segment - exact

fn sdParabolaSegment(pos: vec2f, wi: f32, he: f32) -> f32 {
  let p = vec2f(abs(pos.x), pos.y);
  let ik = wi * wi / he;
  let u = ik * (he - p.y - 0.5 * ik) / 3.;
  let v = p.x * ik * ik * 0.25;
  let h = v * v - u * u * u;
  let r = sqrt(abs(h));
  var x: f32 = select(2. * cos(atan(r / v) / 3.) * sqrt(u),
    pow(v + r, 1. / 3.) - pow(abs(v - r), 1. / 3.) * sign(r - v),
    h > 0.0);
  x = min(x, wi);
  return length(p - vec2f(x, he - x * x / ik)) * sign(ik * (p.y - he) + p.x * p.x);
}

Quadratic Bezier - exact

fn sdBezier(p: vec2f, A: vec2f, B: vec2f, C: vec2f) -> vec2f {
  let a = B - A;
  let b = A - 2. * B + C;
  let c = a * 2.;
  let d = A - p;
  let kk = 1. / dot(b, b);
  let kx = kk * dot(a, b);
  let ky = kk * (2. * dot(a, a) + dot(d, b)) / 3.;
  let kz = kk * dot(d, a);

  let p1 = ky - kx * kx;
  let p3 = p1 * p1 * p1;
  let q = kx * (2.0 * kx * kx - 3.0 * ky) + kz;
  var h: f32 = q * q + 4. * p3;

  var res: vec2f;
  if (h >= 0.) {
    h = sqrt(h);
    let x = (vec2f(h, -h) - q) / 2.;
    let uv = sign(x) * pow(abs(x), vec2f(1. / 3.));
    let t = clamp(uv.x + uv.y - kx, 0., 1.);
    let f = d + (c + b * t) * t;
    res = vec2f(dot(f, f), t);
  } else {
    let z = sqrt(-p1);
    let v = acos(q / (p1 * z * 2.)) / 3.;
    let m = cos(v);
    let n = sin(v) * 1.732050808;
    let t = clamp(vec2f(m + m, -n - m) * z - kx, vec2f(0.0), vec2f(1.0));
    let f = d + (c + b * t.x) * t.x;
    var dis: f32 = dot(f, f);
    res = vec2f(dis, t.x);

    let g = d + (c + b * t.y) * t.y;
    dis = dot(g, g);
    res = select(res, vec2f(dis, t.y), dis < res.x);
  }
  res.x = sqrt(res.x);
  return res;
}

Bobbly Cross - exact

fn sdBlobbyCross(pos: vec2f, he: f32) -> f32 {
  var p: vec2f = abs(pos);
  p = vec2f(abs(p.x - p.y), 1. - p.x - p.y) / sqrt(2.);

  let u = (he - p.y - 0.25 / he) / (6. * he);
  let v = p.x / (he * he * 16.);
  let h = v * v - u * u * u;

  var x: f32; var y: f32;
  if (h > 0.) {
    let r = sqrt(h);
    x = pow(v + r, 1. / 3.) - pow(abs(v - r), 1. / 3.) * sign(r - v);
  } else {
    let r = sqrt(u);
    x = 2. * r * cos(acos(v / (u * r)) / 3.);
  }
  x = min(x, sqrt(2.) / 2.);

  let z = vec2f(x, he * (1. - 2. * x * x)) - p;
  return length(z) * sign(z.y);
}

MIT License. © 2023 Inigo Quilez, Munrocket

@philholden
Copy link

I'd love to play round with WebGPU. What is the boilerplate needed to produce these images?

@munrocket
Copy link
Author

Here online example on compute.toys and hello webgpu

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment