@vshaders/sdf
Signed distance field primitives and operators as pure WGSL modules. A signed distance function returns how far a point is from a shape's boundary: negative inside, zero on the boundary, positive outside, in the same units as p. Because distances are just numbers, shapes combine with plain math: union is min, intersection is max, and the smooth variants blend between them. Every module is functions only, no bindings, no entry points, so vgpu's resolver prunes whatever you don't import.
npm install @vshaders/sdf@vshaders/sdf/2d
Shapes centered at the origin; translate by evaluating at (p - center).
| Signature | Description |
|---|---|
sdCircle(p: vec2f, radius: f32) -> f32 | Circle of the given radius. |
sdBox2(p: vec2f, halfSize: vec2f) -> f32 | Box with half-extents halfSize. Exact distance, including corners. |
sdRoundedBox2(p: vec2f, halfSize: vec2f, cornerRadius: f32) -> f32 | Rounded box. halfSize is the outer half-extent; cornerRadius must not exceed min(halfSize.x, halfSize.y). |
sdSegment2(p: vec2f, start: vec2f, end: vec2f) -> f32 | Distance to the line segment. A zero-length segment degrades to distance to a point. |
sdRing(p: vec2f, radius: f32, halfThickness: f32) -> f32 | Annulus around the origin. |
sdHalfPlane(p: vec2f, normal: vec2f, offset: f32) -> f32 | Half plane. normal must be unit length; offset is the plane's signed distance from the origin. |
import { sdRing } from "@vshaders/sdf/2d";
@fragment fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
let p = position.xy / 512.0 - vec2f(0.5);
let d = sdRing(p, 0.3, 0.02);
return vec4f(vec3f(smoothstep(0.01, -0.01, d)), 1.0);
}@vshaders/sdf/3d
Same conventions as 2D: origin-centered, translate by offsetting p.
| Signature | Description |
|---|---|
sdSphere(p: vec3f, radius: f32) -> f32 | Sphere of the given radius. |
sdBox3(p: vec3f, halfSize: vec3f) -> f32 | Box. Exact distance, including edges and corners. |
sdRoundedBox3(p: vec3f, halfSize: vec3f, cornerRadius: f32) -> f32 | Rounded box; the same cornerRadius bound as the 2D version. |
sdTorus(p: vec3f, majorRadius: f32, minorRadius: f32) -> f32 | Ring in the XZ plane. majorRadius is the ring's radius, minorRadius the tube's. |
sdCapsule3(p: vec3f, start: vec3f, end: vec3f, radius: f32) -> f32 | Capsule between two points. A zero-length capsule degrades to a sphere at start. |
sdCylinderY(p: vec3f, radius: f32, halfHeight: f32) -> f32 | Finite cylinder along the Y axis. |
sdPlane(p: vec3f, normal: vec3f, offset: f32) -> f32 | Plane. normal must be unit length; offset is the plane's signed distance from the origin. |
import { sdTorus } from "@vshaders/sdf/3d";
@fragment fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
let p = position.xy / 512.0 - vec2f(0.5);
// A slice of the 3D field through the plane y = 0.
let d = sdTorus(vec3f(p.x, 0.0, p.y), 0.3, 0.1);
return vec4f(vec3f(smoothstep(0.01, -0.01, d)), 1.0);
}@vshaders/sdf/ops
Boolean operators take distances already evaluated at the same point. Smooth variants blend over a band of smoothness world units; smoothness <= 0 degrades to the sharp operator instead of dividing by zero.
| Signature | Description |
|---|---|
opUnion(d1: f32, d2: f32) -> f32 | Union: the minimum of the two distances. |
opIntersect(d1: f32, d2: f32) -> f32 | Intersection: the maximum of the two distances. |
opSubtract(base: f32, cut: f32) -> f32 | Removes cut from base. |
opSmoothUnion(d1: f32, d2: f32, smoothness: f32) -> f32 | Polynomial smooth minimum. |
opSmoothIntersect(d1: f32, d2: f32, smoothness: f32) -> f32 | Smooth intersection. |
opSmoothSubtract(base: f32, cut: f32, smoothness: f32) -> f32 | Smooth subtraction. |
opRound(d: f32, radius: f32) -> f32 | Grow the shape outward by radius, rounding its corners. |
opOnion(d: f32, halfThickness: f32) -> f32 | Hollow the shape into a shell of the given half-thickness. |
repeat2(p: vec2f, period: vec2f) -> vec2f | Infinite domain repetition in 2D. Apply to p before evaluating a distance function. |
repeat3(p: vec3f, period: vec3f) -> vec3f | The 3D counterpart. |
Repetition guarantees: a period component <= 0 leaves that axis unrepeated, and the repeated field is only exact for shapes smaller than half the period.
import { sdCircle } from "@vshaders/sdf/2d";
import { opSmoothUnion } from "@vshaders/sdf/ops";
@fragment fn main(@builtin(position) position: vec4f) -> @location(0) vec4f {
let p = position.xy / 512.0 - vec2f(0.5);
let d = opSmoothUnion(
sdCircle(p - vec2f(0.15, 0.0), 0.2),
sdCircle(p + vec2f(0.15, 0.0), 0.2),
0.1
);
return vec4f(vec3f(smoothstep(0.01, -0.01, d)), 1.0);
}Verify
npx vgpu check shaders/your-entry.wgsl --require-validationRuns on the entry shader that imports these modules: it resolves the import graph, validates the composed shader against a real device, and prints its reflection.
Provenance
The exact box, sphere, segment, torus and cylinder distances and the polynomial smooth minimum are standard published constructions from the computer graphics literature (Hart's sphere tracing lineage; the smooth minimum polynomial as popularized in Inigo Quilez's articles). The WGSL here is an original transcription of those formulas, with explicit guards (zero-length segments, non-positive smoothness or period) in the argument ranges the formulas leave undefined. MIT licensed.