1 SDF Catalog
A reference catalog of signed distance functions. See Day 5 for concepts; see Inigo Quilez’s pages for the complete collection.
1.1 1. 2D Primitives
Circle
Circle centered at origin.
p: query pointr: radius
float sdCircle(vec2 p, float r) {
return length(p) - r;
}Box
Axis-aligned rectangle centered at origin.
p: query pointb: half-size (box extends from-bto+b)
float sdBox(vec2 p, vec2 b) {
vec2 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0);
}Rounded Box
Axis-aligned rectangle with rounded corners, centered at origin.
p: query pointb: half-size before roundingr: corner radius
float sdRoundedBox(vec2 p, vec2 b, float r) {
vec2 d = abs(p) - b + r;
return length(max(d, 0.0)) + min(max(d.x, d.y), 0.0) - r;
}Line Segment
Line segment between two points.
p: query pointa: start pointb: end point
float sdSegment(vec2 p, vec2 a, vec2 b) {
vec2 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h);
}Ring / Annulus
Ring (thick circle) centered at origin.
p: query pointr: center radius (distance from origin to middle of ring)w: half-thickness
float sdRing(vec2 p, float r, float w) {
return abs(length(p) - r) - w;
}Arc
Circular arc centered at origin.
p: query pointr: radiusa1: start angle (radians)a2: end angle (radians)w: half-thickness
float sdArc(vec2 p, float r, float a1, float a2, float w) {
float a = atan(p.y, p.x);
a = clamp(a, a1, a2);
vec2 q = r * vec2(cos(a), sin(a));
return length(p - q) - w;
}Pie / Sector
Pie slice (wedge) centered at origin, symmetric about the y-axis.
p: query pointr: radiusa: half-angle (radians) — total wedge spans from-ato+a
float sdPie(vec2 p, float r, float a) {
vec2 c = vec2(sin(a), cos(a));
p.x = abs(p.x);
float l = length(p) - r;
float m = length(p - c * clamp(dot(p, c), 0.0, r));
return max(l, m * sign(c.y * p.x - c.x * p.y));
}Equilateral Triangle
Equilateral triangle centered at origin.
p: query pointr: distance from center to vertex
float sdEquilateralTriangle(vec2 p, float r) {
const float k = sqrt(3.0);
p.x = abs(p.x) - r;
p.y = p.y + r / k;
if (p.x + k * p.y > 0.0) p = vec2(p.x - k * p.y, -k * p.x - p.y) / 2.0;
p.x -= clamp(p.x, -2.0 * r, 0.0);
return -length(p) * sign(p.y);
}Hexagon
Regular hexagon centered at origin.
p: query pointr: circumradius (center to vertex)
float sdHexagon(vec2 p, float r) {
const vec3 k = vec3(-0.866025404, 0.5, 0.577350269);
p = abs(p);
p -= 2.0 * min(dot(k.xy, p), 0.0) * k.xy;
p -= vec2(clamp(p.x, -k.z * r, k.z * r), r);
return length(p) * sign(p.y);
}Regular Polygon (n-gon)
Regular polygon centered at origin. Approximate SDF.
p: query pointr: circumradius (center to vertex)n: number of sides
float sdPolygon(vec2 p, float r, int n) {
float a = atan(p.x, p.y) + 3.14159265;
float s = 6.28318530 / float(n);
return length(p) * cos(mod(a, s) - s * 0.5) - r;
}1.2 2. 3D Primitives
Sphere
Sphere centered at origin.
p: query pointr: radius
float sdSphere(vec3 p, float r) {
return length(p) - r;
}Ellipsoid
Ellipsoid centered at origin.
p: query pointr: radii along each axis
float sdEllipsoid(vec3 p, vec3 r) {
float k0 = length(p / r);
float k1 = length(p / (r * r));
return k0 * (k0 - 1.0) / k1;
}Box
Axis-aligned box centered at origin.
p: query pointb: half-size (box extends from-bto+balong each axis)
float sdBox(vec3 p, vec3 b) {
vec3 d = abs(p) - b;
return length(max(d, 0.0)) + min(max(d.x, max(d.y, d.z)), 0.0);
}Rounded Box
Axis-aligned box with rounded edges, centered at origin.
p: query pointb: half-size before roundingr: edge radius
float sdRoundBox(vec3 p, vec3 b, float r) {
vec3 d = abs(p) - b + r;
return length(max(d, 0.0)) + min(max(d.x, max(d.y, d.z)), 0.0) - r;
}Plane
Infinite plane.
p: query pointn: plane normal (will be normalized)h: signed distance from origin to plane along normal
float sdPlane(vec3 p, vec3 n, float h) {
return dot(p, normalize(n)) + h;
}Torus
Torus centered at origin, lying in the xz-plane.
p: query pointR: major radius (center of torus to center of tube)r: minor radius (tube thickness)
float sdTorus(vec3 p, float R, float r) {
vec2 q = vec2(length(p.xz) - R, p.y);
return length(q) - r;
}Cylinder (Infinite)
Infinite cylinder along the y-axis, centered at origin.
p: query pointr: radius
float sdCylinder(vec3 p, float r) {
return length(p.xz) - r;
}Capped Cylinder
Finite cylinder along the y-axis, centered at origin.
p: query pointh: half-height (extends from-hto+hin y)r: radius
float sdCappedCylinder(vec3 p, float h, float r) {
vec2 d = abs(vec2(length(p.xz), p.y)) - vec2(r, h);
return min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
}Capsule
Capsule (cylinder with hemispherical caps) between two points.
p: query pointa: start point (center of first hemisphere)b: end point (center of second hemisphere)r: radius
float sdCapsule(vec3 p, vec3 a, vec3 b, float r) {
vec3 pa = p - a, ba = b - a;
float h = clamp(dot(pa, ba) / dot(ba, ba), 0.0, 1.0);
return length(pa - ba * h) - r;
}Cone (Infinite)
Infinite cone with apex at origin, opening downward along negative y-axis.
p: query pointa: half-angle (radians)
float sdCone(vec3 p, float a) {
vec2 c = vec2(sin(a), cos(a));
vec2 q = vec2(length(p.xz), -p.y);
float d = length(q - c * max(dot(q, c), 0.0));
return d * ((q.x * c.y - q.y * c.x < 0.0) ? -1.0 : 1.0);
}Capped Cone
Finite cone (frustum) along the y-axis, centered at origin.
p: query pointh: half-height (extends from-hto+hin y)r1: radius at bottom (y = -h)r2: radius at top (y = +h)
float sdCappedCone(vec3 p, float h, float r1, float r2) {
vec2 q = vec2(length(p.xz), p.y);
vec2 k1 = vec2(r2, h);
vec2 k2 = vec2(r2 - r1, 2.0 * h);
vec2 ca = vec2(q.x - min(q.x, (q.y < 0.0) ? r1 : r2), abs(q.y) - h);
vec2 cb = q - k1 + k2 * clamp(dot(k1 - q, k2) / dot(k2, k2), 0.0, 1.0);
float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0;
return s * sqrt(min(dot(ca, ca), dot(cb, cb)));
}Octahedron
Regular octahedron centered at origin.
p: query points: vertex-to-center distance
float sdOctahedron(vec3 p, float s) {
p = abs(p);
return (p.x + p.y + p.z - s) * 0.57735027;
}1.3 3. Operations
Union
The closest surface:
float opUnion(float d1, float d2) {
return min(d1, d2);
}Intersection
Where both surfaces overlap:
float opIntersection(float d1, float d2) {
return max(d1, d2);
}Subtraction
Cut d2 out of d1:
float opSubtraction(float d1, float d2) {
return max(d1, -d2);
}Smooth Union
Blends two surfaces smoothly:
float opSmoothUnion(float d1, float d2, float k) {
float h = clamp(0.5 + 0.5 * (d2 - d1) / k, 0.0, 1.0);
return mix(d2, d1, h) - k * h * (1.0 - h);
}k controls the blend radius. Larger k means a wider, smoother blend.
Smooth Intersection
float opSmoothIntersection(float d1, float d2, float k) {
float h = clamp(0.5 - 0.5 * (d2 - d1) / k, 0.0, 1.0);
return mix(d2, d1, h) + k * h * (1.0 - h);
}Smooth Subtraction
float opSmoothSubtraction(float d1, float d2, float k) {
float h = clamp(0.5 - 0.5 * (d2 + d1) / k, 0.0, 1.0);
return mix(d1, -d2, h) + k * h * (1.0 - h);
}1.4 4. Transformations
Translation
To move a shape, translate the point in the opposite direction:
float d = sdSphere(p - vec3(1.0, 0.0, 0.0), 1.0); // sphere at (1, 0, 0)Rotation
To rotate a shape, rotate the point by the inverse (or just transpose the rotation matrix for orthogonal matrices):
mat3 rot = rotateY(0.5); // see Coordinates appendix
float d = sdBox(rot * p, vec3(1.0)); // rotated boxScaling
Scale the point, then scale the result:
float s = 2.0; // scale factor
float d = sdSphere(p / s, 1.0) * s; // sphere scaled by 2You must multiply the result by s to keep it a valid distance field.
Repetition (Euclidean Grid)
Use mod to repeat space infinitely:
float opRep(vec3 p, vec3 c) {
vec3 q = mod(p + 0.5 * c, c) - 0.5 * c;
return sdPrimitive(q);
}c is the cell size. This places one copy of the primitive in each cell of a Euclidean grid.
Note: This technique uses the Euclidean structure — the cells are rectangles and mod wraps coordinates linearly. For non-Euclidean repetition (e.g., hyperbolic tilings), you need different methods; see Day 3.
Symmetry
Use abs to mirror across a plane:
p.x = abs(p.x); // mirror across the yz-plane
float d = sdPrimitive(p);This places the primitive (or whatever part is in \(x > 0\)) on both sides.
Combine for multi-axis symmetry:
p = abs(p); // octant symmetry — 8 copies1.5 5. Normals
The gradient of an SDF points in the direction of steepest increase — away from the surface. This is the surface normal.
The Formula
For an SDF \(f\), the normal at point \(\mathbf{p}\) is:
\[\mathbf{n} = \frac{\nabla f}{|\nabla f|} = \text{normalize}\left( \frac{\partial f}{\partial x}, \frac{\partial f}{\partial y}, \frac{\partial f}{\partial z} \right)\]
Implementation
Approximate the gradient numerically with central differences:
vec3 getNormal(vec3 p) {
float e = 0.0001;
return normalize(vec3(
sceneSDF(p + vec3(e, 0, 0)) - sceneSDF(p - vec3(e, 0, 0)),
sceneSDF(p + vec3(0, e, 0)) - sceneSDF(p - vec3(0, e, 0)),
sceneSDF(p + vec3(0, 0, e)) - sceneSDF(p - vec3(0, 0, e))
));
}This requires 6 SDF evaluations. A cheaper version uses tetrahedral sampling:
vec3 getNormal(vec3 p) {
float e = 0.0001;
vec2 k = vec2(1, -1);
return normalize(
k.xyy * sceneSDF(p + k.xyy * e) +
k.yyx * sceneSDF(p + k.yyx * e) +
k.yxy * sceneSDF(p + k.yxy * e) +
k.xxx * sceneSDF(p + k.xxx * e)
);
}This uses 4 evaluations at the vertices of a tetrahedron centered on p.
Choosing Epsilon
- Too large: normals are smoothed, fine details lost
- Too small: numerical noise, especially far from origin
A value around 0.0001 to 0.001 works for most scenes. For very small or detailed geometry, scale epsilon accordingly.
1.6 6. Resources
For the complete collection of SDF primitives and operations:
- 2D SDFs: https://iquilezles.org/articles/distfunctions2d/
- 3D SDFs: https://iquilezles.org/articles/distfunctions/
These pages include many more primitives, exact vs. bound SDFs, and advanced operations.