2D SDFs
A 2D signed-distance function maps a point on the plane to a single
f32: the shortest distance from that point to the surface of a shape.
The sign tells you which side you’re on — negative inside the shape,
positive outside, zero exactly on the boundary. From that one number you
can render filled shapes, hollow outlines, soft borders, drop shadows,
boolean unions and intersections, and more, all without a vertex buffer.
Every entry below is a single pure WGSL function in the registry. Pull one into a composition by slug, then call it from your fragment stage:
let main = r#"@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4<f32> { let p = (in.uv - vec2<f32>(0.5)) * 2.0; let d = circle(p, 0.7); let inside = 1.0 - smoothstep(-0.005, 0.005, d); return vec4<f32>(vec3<f32>(0.95, 0.55, 0.85) * inside, 1.0);}"#;let shader = Shader::new(&["sdf2d/circle", main])?;Each preview below renders the SDF on a UV grid remapped to [-1, 1] and
threshold-shades the result against a flat background. Tune the parameters
to your own scene; the registry shape is just the function.
Axis-aligned rectangle centered at the origin with half-extents b.
fn box(p: vec2<f32>, b: vec2<f32>) -> f32
circle
Section titled “circle”Signed distance to a circle of radius r at the origin.
fn circle(p: vec2<f32>, r: f32) -> f32
equilateral_triangle
Section titled “equilateral_triangle”Equilateral triangle centered at the origin with circumradius r,
pointing in the +y direction (math coordinates). Flip p.y at the call
site if you want the apex pointing up on a y-down UV surface.
fn equilateral_triangle(p: vec2<f32>, r: f32) -> f32
Heart shape with the apex (the pointy bottom) at the origin and the lobes
at positive y. Scale by s. For a y-down UV surface, pass
heart(vec2<f32>(p.x, -p.y), s) to flip it upright.
fn heart(p: vec2<f32>, s: f32) -> f32
hexagon
Section titled “hexagon”Regular hexagon with circumradius r and a flat top.
fn hexagon(p: vec2<f32>, r: f32) -> f32
Pie-slice with radius r and full opening angle 2 * theta, where c is
vec2<f32>(sin(theta), cos(theta)) — the sin and cos of the half-angle.
fn pie(p: vec2<f32>, c: vec2<f32>, r: f32) -> f32
rhombus
Section titled “rhombus”Rhombus centered at the origin, with axis half-lengths b.x (horizontal)
and b.y (vertical).
fn rhombus(p: vec2<f32>, b: vec2<f32>) -> f32
Annulus (a ring) centered at the origin with outer radius r and
thickness th.
fn ring(p: vec2<f32>, r: f32, th: f32) -> f32
rounded_box
Section titled “rounded_box”Axis-aligned rectangle with per-corner radii. The r vector follows
Inigo Quilez’s convention: r.x = top-right corner, r.y = bottom-right,
r.z = bottom-left, r.w = top-left, with b the rectangle half-extents.
fn rounded_box(p: vec2<f32>, b: vec2<f32>, r: vec4<f32>) -> f32
segment
Section titled “segment”Unsigned distance from p to the line segment between endpoints a and
b. To render a stroked line, subtract a thickness from the result before
shading.
fn segment(p: vec2<f32>, a: vec2<f32>, b: vec2<f32>) -> f32
n-pointed star centered at the origin, outer radius r. The float
parameter m lives in the open interval (2, n) and controls sharpness:
values near n produce blunt points; values near 2 produce sharp ones.
fn star(p: vec2<f32>, r: f32, n: u32, m: f32) -> f32
trapezoid
Section titled “trapezoid”Isoceles trapezoid centered at the origin. r1 is the top half-width,
r2 is the bottom half-width, and h is the half-height. Pass through a
y-flip if you want the wider base at the bottom of a y-down UV surface.
fn trapezoid(p: vec2<f32>, r1: f32, r2: f32, h: f32) -> f32