website articles
distance functions

Intro


After having posted about the basics of distance functions in several places (pouet, my blog, shadertoy, private emails, etc), I thought it might make sense to put these together in centralized place. Here you will find the distance functions for basic primitives, plus the formulas for combining them together for building more complex shapes, as well as some distortion functions that you can use to shape your objects. Hopefully this will be usefull for those rendering scenes with raymarching. You can see some of the results you can get by using these techniques in the raymarching distance fields article. Lastly, this article doesn't include lighting tricks, nor marching acceleartion tricks or more advanced techniques as recursive primitives or fractals. In case you are looking for 2D SDF functions, you'll find them in the 2D Distance Functions page.


Primitives


All primitives are centered at the origin. You will have to transform the point to get arbitrarily rotated, translated and scaled objects (see below).


Sphere - signed - exact

float sdSphere( vec3 p, float s ) { return length(p)-s; }

Box - unsigned - exact

float udBox( vec3 p, vec3 b ) { return length(max(abs(p)-b,0.0)); }

Round Box - unsigned - exact

float udRoundBox( vec3 p, vec3 b, float r ) { return length(max(abs(p)-b,0.0))-r; }

Box - signed - exact

float sdBox( vec3 p, vec3 b ) { vec3 d = abs(p) - b; return min(max(d.x,max(d.y,d.z)),0.0) + length(max(d,0.0)); }

Torus - signed - exact

float sdTorus( vec3 p, vec2 t ) { vec2 q = vec2(length(p.xz)-t.x,p.y); return length(q)-t.y; }

Cylinder - signed - exact

float sdCylinder( vec3 p, vec3 c ) { return length(p.xz-c.xy)-c.z; }

Cone - signed - exact

float sdCone( vec3 p, vec2 c ) { // c must be normalized float q = length(p.xy); return dot(c,vec2(q,p.z)); }

Plane - signed - exact

float sdPlane( vec3 p, vec4 n ) { // n must be normalized return dot(p,n.xyz) + n.w; }

Hexagonal Prism - signed - exact

float sdHexPrism( vec3 p, vec2 h ) { vec3 q = abs(p); return max(q.z-h.y,max((q.x*0.866025+q.y*0.5),q.y)-h.x); }

Triangular Prism - signed - exact

float sdTriPrism( vec3 p, vec2 h ) { vec3 q = abs(p); return max(q.z-h.y,max(q.x*0.866025+p.y*0.5,-p.y)-h.x*0.5); }

Capsule / Line - signed - exact

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; }

Capped Cylinder - signed - exact

float sdCappedCylinder( vec3 p, vec2 h ) { vec2 d = abs(vec2(length(p.xz),p.y)) - h; return min(max(d.x,d.y),0.0) + length(max(d,0.0)); }

Capped Cone - signed - exact

float sdCappedCone( in vec3 p, in float h, in float r1, in 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)/dot2(k2), 0.0, 1.0 ); float s = (cb.x < 0.0 && ca.y < 0.0) ? -1.0 : 1.0; return s*sqrt( min(dot2(ca),dot2(cb)) ); }

Round cone - signed - exact

float sdRoundCone( in vec3 p, in float r1, float r2, float h ) { vec2 q = vec2( length(p.xz), p.y ); float b = (r1-r2)/h; float a = sqrt(1.0-b*b); float k = dot(q,vec2(-b,a)); if( k < 0.0 ) return length(q) - r1; if( k > a*h ) return length(q-vec2(0.0,h)) - r2; return dot(q, vec2(a,b) ) - r1; }

Ellipsoid - signed - bound

float sdEllipsoid( in vec3 p, in vec3 r ) { float k0 = length(p/r); float k1 = length(p/(r*r)); return k0*(k0-1.0)/k1; }

Triangle - unsigned - exact

float dot2( in vec3 v ) { return dot(v,v); } float udTriangle( vec3 p, vec3 a, vec3 b, vec3 c ) { vec3 ba = b - a; vec3 pa = p - a; vec3 cb = c - b; vec3 pb = p - b; vec3 ac = a - c; vec3 pc = p - c; vec3 nor = cross( ba, ac ); return sqrt( (sign(dot(cross(ba,nor),pa)) + sign(dot(cross(cb,nor),pb)) + sign(dot(cross(ac,nor),pc))<2.0) ? min( min( dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa), dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ), dot2(ac*clamp(dot(ac,pc)/dot2(ac),0.0,1.0)-pc) ) : dot(nor,pa)*dot(nor,pa)/dot2(nor) ); }

Quad - unsigned - exact

float dot2( in vec3 v ) { return dot(v,v); } float udQuad( vec3 p, vec3 a, vec3 b, vec3 c, vec3 d ) { vec3 ba = b - a; vec3 pa = p - a; vec3 cb = c - b; vec3 pb = p - b; vec3 dc = d - c; vec3 pc = p - c; vec3 ad = a - d; vec3 pd = p - d; vec3 nor = cross( ba, ad ); return sqrt( (sign(dot(cross(ba,nor),pa)) + sign(dot(cross(cb,nor),pb)) + sign(dot(cross(dc,nor),pc)) + sign(dot(cross(ad,nor),pd))<3.0) ? min( min( min( dot2(ba*clamp(dot(ba,pa)/dot2(ba),0.0,1.0)-pa), dot2(cb*clamp(dot(cb,pb)/dot2(cb),0.0,1.0)-pb) ), dot2(dc*clamp(dot(dc,pc)/dot2(dc),0.0,1.0)-pc) ), dot2(ad*clamp(dot(ad,pd)/dot2(ad),0.0,1.0)-pd) ) : dot(nor,pa)*dot(nor,pa)/dot2(nor) ); }



Most of these functions can be modified to use other norms than the euclidean. By replacing length(p), which computes (x^2+y^2+z^2)^(1/2) by (x^n+y^n+z^n)^(1/n) one can get variations of the basic primitives that have rounded edges rather than sharp ones.


Torus82 - signed

float sdTorus82( vec3 p, vec2 t ) { vec2 q = vec2(length2(p.xz)-t.x,p.y); return length8(q)-t.y; }

Torus88 - signed

float sdTorus88( vec3 p, vec2 t ) { vec2 q = vec2(length8(p.xz)-t.x,p.y); return length8(q)-t.y; }




Distance operations


The d1 and d2 parameters in the following functions are the distance to the two distance fields to combine together.


Union

float opU( float d1, float d2 ) { return min(d1,d2); }

Subtraction

float opS( float d1, float d2 ) { return max(-d1,d2); }

Intersection

float opI( float d1, float d2 ) { return max(d1,d2); }




Domain operations


Domain operations is what allows one to compose a scene by placing primitives in different locations and orientations in space.

Rotation/Translation

Since rotations and translation don't compress nor dilate space, all we need to do is simply to transform the point being sampled with the inverse of the transformation used to place an object in the scene:

vec3 opTx( in vec3 p, in transform t, in sdf primitive ) { return primitive( invert(t)*p ); }

This code assumes that transform encodes only a rotation and a translation (as a 3x4 matrix for example, or as a quaternion and a vector), and that it does not contain any scaling factors in it.

Scale

Scaling an obect is slightly more tricky since that compresses/dilates spaces, so we have to take that into account on the resulting distance estimation. Still, it's not difficult to perform:

float opScale( in vec3 p, in float s, in sdf primitive ) { return primitive(p/s)*s; }


Repetition

Domain repetition is a very useful operator, since it allows you to create infinitely many primitives with a single object eveluator and without increasing the memory footprint of your application. The code bellow shows how to perform the operation in the simplest way:

float opRep( in vec3 p, in vec3 c, in sdf primitive ) { vec3 q = mod(p,c)-0.5*c; return primitve( q ); }

In this code c is the repetitiion period (which can be different in each coordinate direction). This will work great for primitives that have a bounding box smaller than roughly smaller than half the repetition period. If the object is big, you will need to check the 7 neighboring repetitions to check for closest neighbors, just as you usually do in a voronoi/Worley/cellular construction. You have an example of this in action in the following image where all of the grass field is made from a single blade which repeated infinitely in the X and Z directions with the code above. (A link to the real time animation and code for the image is right bellow the image).


https://www.shadertoy.com/view/4tByz3


Distance deformations


Displacements and blends allow now to enhance the shape of primitives or even fuse different primitives together. The operations usually distort the distance field and make it non euclidean anymore, so one must be carefull when raymarching them, you will probably need to decrease your step size, if you are using a raymarcher to sample this. In principle one can compute the factor by which the step size needs to be rediced (inversely proportional to the compression of the space, which is given by the Jacobian of the deformation function). But even with dual numbers or automatic differentiation, it's usually just easier to find the constant by hand for a given primitive.


Displacement

The displacement example below is using sin(20*p.x)*sin(20*p.y)*sin(20*p.z) as displacement pattern, but you can of course use anything you might imagine.

float opDisplace( vec3 p ) { float d1 = primitive(p); float d2 = displacement(p); return d1+d2; }

Rounding

Rounding a shape is as simple as subtracting some distance (junpint to a different isosurface). The rounded box above is an example, but you can apply it to cones, heagons or any other shape like the cone in the image bellow. If you happen to be interested in preserving the overal volume of the shape, most of the times it's pretty easy to shrink the source primitive by the same amount we are rounding it by.

float opRound( float rad ) { return primitive(p) - rad }


Blend

Blend operations come in a variery of forms, with different properties and perofrmance trade-offs. We can summarize them as smin() for now (for smooth-minimum), and you can find more details in the smooth minimum article article in this same site.

float opBlend( vec3 p ) { float d1 = primitiveA(p); float d2 = primitiveB(p); return smin( d1, d2 ); }


As an example of how powerful this technique is, the body of the snail shoed in the image bellow was created by blending together primitive shapes with smin():


https://www.shadertoy.com/view/ld3Gz2



Domain deformations


Domain deformation functions do not preserve distances neither. You must decrease your marching step to properly sample these functions (proportionally to the maximun derivative of the domain distortion function). Of course, any distortion function can be used, from twists, bends, to random noise driven deformations.


Twist

float opTwist( vec3 p ) { float c = cos(20.0*p.y); float s = sin(20.0*p.y); mat2 m = mat2(c,-s,s,c); vec3 q = vec3(m*p.xz,p.y); return primitive(q); }


Cheap Bend

float opCheapBend( vec3 p ) { float c = cos(20.0*p.y); float s = sin(20.0*p.y); mat2 m = mat2(c,-s,s,c); vec3 q = vec3(m*p.xy,p.z); return primitive(q); }




A reference implementation of most of these primitives and operators can be found here (click in the image to rotate the camera, or in the title to jump to the source code):