RayMarching Shader pt.4 (3D Primitive Shapes)

These examples show a collection of different distance functions for basic 3D primitive shapes and how they can be implemented in a ray marching shader. Keep in mind that many more shapes can be created by combining them together with other distance functions like boolean and smooth blending operators.

All of the distance functions can be found in the first example. The second example in this post only contains the necessary distance functions needed for the example.

For more information on these 3D primitive shape distance functions visit Inigo Quilez’s blog: http://www.iquilezles.org which is the main source of the DF functions.

Trying the examples
If you quickly want to try out the examples I recommend the web based GLSL editor from the book of shaders over here:
http://editor.thebookofshaders.com

If you prefer working with an offline editor I recommend Visual Studio Code with the glsl-canvas extension: https://code.visualstudio.com
Or if you prefer Sublime Text then you can install this package : https://packagecontrol.io/packages/glslViewer

If you are reading this on a iOS device I recommend the app ‘Shaders’ which can be used to program and run GLSL shaders and also has a public timeline that you can use to share your work with others:
https://apps.apple.com/app/shaders-shader-editor/id1221602862?l=en

1. 3D Primitives

3D Primitives
Inside of the GetDist() function the octahedron, link and rounded box are created positioned and rotated on two axes.
To position a primitive we can simply subtract the vec3 position we want the primitive to have from p:
oPos = p - vec3(-3,1,7);
To Rotate on one axis at a time we can multiply the position as a vector2 with a matrix2x2:
oPos.xy *= Rotate(-u_time);

precision highp float;

uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed

// Constants
#define PI 3.1415925359
#define MAX_STEPS 100 // Mar Raymarching steps
#define MAX_DIST 100. // Max Raymarching distance
#define SURF_DIST .01 // Surface Distance

mat2 Rotate(float a)
{
  float s = sin(a),c = cos(a);
  return mat2(c,-s,s,c);
}

///////////////////////
// Primitives
///////////////////////

// Dot2 helper function
float dot2( vec2 v ) { return dot(v,v); }
float dot2( vec3 v ) { return dot(v,v); }

// Sphere - exact
float sphereSDF( vec3 p, float s ) {
  return length(p)-s;
}

// Box - exact 
float boxSDF( vec3 p, vec3 b ) {
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0);
}

// Round Box - exact
float roundBoxSDF( vec3 p, vec3 b, float r ) {
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}

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

// Hexagonal Prism - exact
float hexPrismSDF(vec3 p, vec2 h) {
  const vec3 k = vec3(-0.8660254, 0.5, 0.57735);
  p = abs(p);
  p.xy -= 2.0*min(dot(k.xy, p.xy), 0.0)*k.xy;
  vec2 d = vec2(
       length(p.xy-vec2(clamp(p.x,-k.z*h.x,k.z*h.x), h.x))*sign(p.y-h.x),
       p.z-h.y );
  return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

// Triangular Prism - exact
float triPrismSDF( vec3 p, vec2 h ) {
    const float k = sqrt(3.0);
    h.x *= 0.5*k;
    p.xy /= h.x;
    p.x = abs(p.x) - 1.0;
    p.y = p.y + 1.0/k;
    if( p.x+k*p.y>0.0 ) p.xy=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0;
    p.x -= clamp( p.x, -2.0, 0.0 );
    float d1 = length(p.xy)*sign(-p.y)*h.x;
    float d2 = abs(p.z)-h.y;
    return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.);
}

//Capsule / Line
float capsuleSDF( 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;
}

//Capsule / Line - exact
float verticalCapsuleSDF(vec3 p, float h, float r) {
  p.y -= clamp( p.y, 0.0, h );
  return length( p ) - r;
}

//Ininite Cylinder - exact
float cylinderSDF( vec3 p, vec3 c ) {
  return length(p.xz-c.xy)-c.z;
}

//Capped Cylinder - exact
float cappedCylinderSDF( vec3 p, float h, float r ) {
  vec2 d = abs(vec2(length(p.xz),p.y)) - vec2(h,r);
  return min(max(d.x,d.y),0.0) + length(max(d,0.0));
}

//Capped Cylinder - exact
float cappedCylinderSDF(vec3 p, vec3 a, vec3 b, float r) {
  vec3  ba = b - a;
  vec3  pa = p - a;
  float baba = dot(ba,ba);
  float paba = dot(pa,ba);
  float x = length(pa*baba-ba*paba) - r*baba;
  float y = abs(paba-baba*0.5)-baba*0.5;
  float x2 = x*x;
  float y2 = y*y*baba;
  float d = (max(x,y)<0.0)?-min(x2,y2):(((x>0.0)?x2:0.0)+((y>0.0)?y2:0.0));
  return sign(d)*sqrt(abs(d))/baba;
}

// Rounded Cylinder - exact
float roundedCylinderSDF( vec3 p, float ra, float rb, float h ) {
  vec2 d = vec2( length(p.xz)-2.0*ra+rb, abs(p.y) - h );
  return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rb;
}

// Cone - exact
float coneSDF( vec3 p, vec2 c ) {
  // c is the sin/cos of the angle
  float q = length(p.xy);
  return dot(c,vec2(q,p.z));
}

// Capped Cone - exact
//float dot2( vec3 v ) { return dot(v,v); }
float cappedConeSDF( 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)/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)) );
}

// Capped Cone - exact
float cappedConeSDF(vec3 p, vec3 a, vec3 b, float ra, float rb) {
  float rba  = rb-ra;
  float baba = dot(b-a,b-a);
  float papa = dot(p-a,p-a);
  float paba = dot(p-a,b-a)/baba;
  float x = sqrt( papa - paba*paba*baba );
  float cax = max(0.0,x-((paba<0.5)?ra:rb));
  float cay = abs(paba-0.5)-0.5;
  float k = rba*rba + baba;
  float f = clamp( (rba*(x-ra)+paba*baba)/k, 0.0, 1.0 );
  float cbx = x-ra - f*rba;
  float cby = paba - f;
  float s = (cbx<0.0 && cay<0.0) ? -1.0 : 1.0;
  return s*sqrt( min(cax*cax+cay*cay*baba, cbx*cbx+cby*cby*baba) );
}

// Solid Angle - exact
float solidAngleSDF(vec3 p, vec2 c, float ra) {
  // c is the sin/cos of the angle
  vec2 q = vec2( length(p.xz), p.y );
  float l = length(q) - ra;
  float m = length(q - c*clamp(dot(q,c),0.0,ra) );
  return max(l,m*sign(c.y*q.x-c.x*q.y));
}

// Round cone - exact
float roundConeSDF( vec3 p, 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 - bound (not exact!)
float ellipsoidSDF( vec3 p, vec3 r ) {
  float k0 = length(p/r);
  float k1 = length(p/(r*r));
  return k0*(k0-1.0)/k1;
}

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

// Capped Torus - exact 
float cappedTorusSDF(in vec3 p, in vec2 sc, in float ra, in float rb) {
  p.x = abs(p.x);
  float k = (sc.y*p.x>sc.x*p.y) ? dot(p.xy,sc) : length(p.xy);
  return sqrt( dot(p,p) + ra*ra - 2.0*ra*k ) - rb;
}

// Joint - exact
// returns distance in .x and UVW parametrization in .yzw
vec4 joint3DSphereSDF( in vec3 p, in float l, in float a, in float w) {
  if( abs(a)<0.001 ) return vec4( length(p-vec3(0,clamp(p.y,0.0,l),0))-w, p );
    
  vec2  sc = vec2(sin(a),cos(a));
  float ra = 0.5*l/a;
  p.x -= ra;
  vec2 q = p.xy - 2.0*sc*max(0.0,dot(sc,p.xy));
  float u = abs(ra)-length(q);
  float d2 = (q.y<0.0) ? dot2( q+vec2(ra,0.0) ) : u*u;
  float s = sign(a);
  return vec4( sqrt(d2+p.z*p.z)-w,
               (p.y>0.0) ? s*u : s*sign(-p.x)*(q.x+ra),
               (p.y>0.0) ? atan(s*p.y,-s*p.x)*ra : (s*p.x<0.0)?p.y:l-p.y,
               p.z );
}

// Link - exact
float linkSDF(vec3 p, float le, float r1, float r2) {
  vec3 q = vec3( p.x, max(abs(p.y)-le,0.0), p.z );
  return length(vec2(length(q.xy)-r1,q.z)) - r2;
}

// Octahedron - exact
float octahedronSDF(vec3 p, float s) {
  p = abs(p);
  float m = p.x+p.y+p.z-s;
  vec3 q;
       if( 3.0*p.x < m ) q = p.xyz;
  else if( 3.0*p.y < m ) q = p.yzx;
  else if( 3.0*p.z < m ) q = p.zxy;
  else return m*0.57735027;
    
  float k = clamp(0.5*(q.z-q.y+s),0.0,s); 
  return length(vec3(q.x,q.y-s+k,q.z-k)); 
}

// Octahedron - bound (not exact)
float octahedron2SDF(vec3 p, float s) {
  p = abs(p);
  return (p.x+p.y+p.z-s)*0.57735027;
}

// Pyramid - exact
float pyramidSDF(vec3 p, float h) {
  float m2 = h*h + 0.25;
    
  p.xz = abs(p.xz);
  p.xz = (p.z>p.x) ? p.zx : p.xz;
  p.xz -= 0.5;

  vec3 q = vec3( p.z, h*p.y - 0.5*p.x, h*p.x + 0.5*p.y);
   
  float s = max(-q.x,0.0);
  float t = clamp( (q.y-0.5*p.z)/(m2+0.25), 0.0, 1.0 );
    
  float a = m2*(q.x+s)*(q.x+s) + q.y*q.y;
  float b = m2*(q.x+0.5*t)*(q.x+0.5*t) + (q.y-m2*t)*(q.y-m2*t);
    
  float d2 = min(q.y,-q.x*m2-q.y*0.5) > 0.0 ? 0.0 : min(a,b);
    
  return sqrt( (d2+q.z*q.z)/m2 ) * sign(max(q.z,-p.y));
}

// Triangle UDF - exact 
float triangleUDF( 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 UDF - exact
float quadUDF( 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) );
}
//////////////////////

float GetDist(vec3 p) 
{
  float d = 0.;

  // Octahedron
  vec3 oPos;
  oPos = p - vec3(-3,1,7);
  oPos.xy *= Rotate(-u_time); // Rotates on one axis
  oPos.xz *= Rotate(-u_time);

  d = octahedronSDF(oPos,1.);

  // Link
  vec3 lPos = vec3(0,1,6);
  lPos = p-lPos;
  lPos.xz *= Rotate(-u_time);
  lPos.xy *= Rotate(-u_time);
  
  d = min(d,linkSDF(lPos,.2,.5,.2));
  
  // Box
  vec3 bPos = p-vec3(3,1,7);
  bPos.xy *= Rotate(u_time);
  bPos.xz *= Rotate(u_time);

  d = min(d, roundBoxSDF(bPos,vec3(.5,.5,.5),.1));

  // Plane
  float planeDist  = p.y;

  d = min(d,planeDist);

  return d;
}

float RayMarch(vec3 ro, vec3 rd) {
  float dO = 0.; //Distane Origin
  for(int i=0;i<MAX_STEPS;i++) {
    vec3 p = ro + rd * dO;
    float ds = GetDist(p); // ds is Distance Scene
    dO += ds;
    if(dO > MAX_DIST || ds < SURF_DIST) 
      break;
  }
  return dO;
}

vec3 GetNormal(vec3 p) { 
  float d = GetDist(p); // Distance
  vec2 e = vec2(.01,0); // Epsilon

  vec3 n = d - vec3(
    GetDist(p-e.xyy),  // e.xyy is the same as vec3(.01,0,0).
    GetDist(p-e.yxy),
    GetDist(p-e.yyx));

  return normalize(n);
}

float GetLight(vec3 p) { 
  // Directional light
  vec3 lightPos = vec3(5.*sin(u_time),5.,5.0*cos(u_time)); // Light Position
  vec3 l = normalize(lightPos-p); // Light Vector
  vec3 n = GetNormal(p); // Normal Vector
  
  float dif = dot(n,l); // Diffuse light
  dif = clamp(dif,0.,1.); // Clamp so it doesnt go below 0

  // Shadows
  float d = RayMarch(p+n*SURF_DIST*2., l); 

  if(d<length(lightPos-p)) dif *= .1;
  return dif;
}

void main() {
  vec2 uv = (gl_FragCoord.xy-.5*u_resolution.xy)/u_resolution.y;

  vec3 ro = vec3(0,1,0); // Ray Origin/Camera
  vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction
  
  float d = RayMarch(ro,rd); // Distance
  
  vec3 p = ro + rd * d;
  float dif = GetLight(p); // Diffuse lighting

  vec3 color = vec3(dif);

  // Set the output color
  gl_FragColor = vec4(color,1.0);
}

2. 3D Primitives Example 2

Example 2
This example shows how smooth blending and boolean subtraction can be used to create more interesting shapes from simple geometry:

precision highp float;

uniform vec2 u_resolution;// Width & height of the shader
uniform float u_time;// Time elapsed

// Constants
#define PI 3.1415925359
#define MAX_STEPS 100// Mar Raymarching steps
#define MAX_DIST 100.// Max Raymarching distance
#define SURF_DIST.01// Surface Distance

mat2 Rotate(float a) {
    float s=sin(a), c=cos(a);
    return mat2(c,-s,s,c);
}

///////////////////////
// Primitives
///////////////////////

// Round Box - exact
float roundBoxSDF( vec3 p, vec3 b, float r ) {
  vec3 q = abs(p) - b;
  return length(max(q,0.0)) + min(max(q.x,max(q.y,q.z)),0.0) - r;
}

// Triangular Prism - exact
float triPrismSDF( vec3 p, vec2 h ) {
    const float k = sqrt(3.0);
    h.x *= 0.5*k;
    p.xy /= h.x;
    p.x = abs(p.x) - 1.0;
    p.y = p.y + 1.0/k;
    if( p.x+k*p.y>0.0 ) p.xy=vec2(p.x-k*p.y,-k*p.x-p.y)/2.0;
    p.x -= clamp( p.x, -2.0, 0.0 );
    float d1 = length(p.xy)*sign(-p.y)*h.x;
    float d2 = abs(p.z)-h.y;
    return length(max(vec2(d1,d2),0.0)) + min(max(d1,d2), 0.);
}

// Rounded Cylinder - exact
float roundedCylinderSDF( vec3 p, float ra, float rb, float h ) {
  vec2 d = vec2( length(p.xz)-2.0*ra+rb, abs(p.y) - h );
  return min(max(d.x,d.y),0.0) + length(max(d,0.0)) - rb;
}

///////////////////////
// Boolean Operators
///////////////////////
 
float intersectSDF(float distA, float distB) {
    return max(distA, distB);
}
 
float unionSDF(float distA, float distB) {
    return min(distA, distB);
}
 
float differenceSDF(float distA, float distB) {
    return max(distA, -distB);
}

/////////////////////////////
// Smooth blending operators
/////////////////////////////

float smoothIntersectSDF(float distA, float distB, float k ) {
  float h = clamp(0.5 - 0.5*(distA-distB)/k, 0., 1.);
  return mix(distA, distB, h ) + k*h*(1.-h); 
}

float smoothUnionSDF(float distA, float distB, float k ) {
  float h = clamp(0.5 + 0.5*(distA-distB)/k, 0., 1.);
  return mix(distA, distB, h) - k*h*(1.-h); 
}

float smoothDifferenceSDF(float distA, float distB, float k) {
  float h = clamp(0.5 - 0.5*(distA+distB)/k, 0., 1.);
  return mix(distA, -distB, h ) + k*h*(1.-h); 
}
/////////////////////////
                        
float GetDist(vec3 p)
{
    
    float d=0.;
    
    // Circle
    vec3 tPos=vec3(-1,1,6);
    tPos=p-tPos;
    tPos.xz*=Rotate(-u_time);
    tPos.xy*=Rotate(-u_time);
    d=roundedCylinderSDF(tPos,.4,.05,.1);
    d=smoothDifferenceSDF(d,roundedCylinderSDF(tPos,.3,.2,.2),.05);
    
    // Triangle
    vec3 tr0Pos=p-vec3(-3,1,6);
    tr0Pos.xy*=Rotate(-u_time);
    tr0Pos.xz*=Rotate(-u_time);
    d=min(d,triPrismSDF(tr0Pos,vec2(1.,.1))-.05); // Subtracts -.05 from the distance in the end to give the triangle round edges 
    d=smoothDifferenceSDF(d,triPrismSDF(tr0Pos,vec2(.7,.1 + SURF_DIST*5.)),.05);

    // Cross
    vec3 cPos=p-vec3(1,1,6);
    cPos.xy*=Rotate(u_time);
    cPos.xz*=Rotate(u_time);
    d=min(d,roundBoxSDF(cPos,vec3(.8,.1,.1),.05));
    d=unionSDF(d,roundBoxSDF(cPos,vec3(.1,.8,.1),.05));

    // Square
    vec3 sq0Pos=p-vec3(3,1,6);
    sq0Pos.xy*=Rotate(u_time);
    sq0Pos.xz*=Rotate(u_time);
    d = min(d,roundBoxSDF(sq0Pos,vec3(.7,.7,.1),.05));
    d = smoothDifferenceSDF(d,roundBoxSDF(sq0Pos,vec3(.6,.6,.2),-.05),.05);

    // Plane
    float planeDist=p.y;
    d = smoothUnionSDF(d,planeDist,smoothstep(1.,0.,p.y));

    return d;
}
                            
float RayMarch(vec3 ro,vec3 rd)
{
    float dO=0.;//Distane Origin
    for(int i=0;i<MAX_STEPS;i++)
    {
        vec3 p=ro+rd*dO;
        float ds=GetDist(p);// ds is Distance Scene
        dO+=ds;
        if(dO>MAX_DIST||ds<SURF_DIST)
        break;
    }
    return dO;
}
                            
vec3 GetNormal(vec3 p)
{
    float d=GetDist(p);// Distance
    vec2 e=vec2(.01,0);// Epsilon
    
    vec3 n=d-vec3(
        GetDist(p-e.xyy),// e.xyy is the same as vec3(.01,0,0). The x of e is .01. this is called a swizzle
        GetDist(p-e.yxy),
        GetDist(p-e.yyx));
        
        return normalize(n);
}
                                
float GetLight(vec3 p)
{
    // Directional light
    vec3 lightPos=vec3(5.*sin(u_time),5.,6.+5.*cos(u_time));// Light Position

    vec3 l=normalize(lightPos-p);// Light Vector
    vec3 n=GetNormal(p);// Normal Vector
    
    float dif=dot(n,l);// Diffuse light
    dif=clamp(dif,0.,1.);// Clamp so it doesnt go below 0
    
    // Shadows
    float d=RayMarch(p+n*SURF_DIST*2.,l);
    
    if(d<length(lightPos-p))dif*=.1;
    return dif;
}
                                
void main()
{
    vec2 uv=(gl_FragCoord.xy-.5*u_resolution.xy)/u_resolution.y;
    
    vec3 ro=vec3(0,1,0);// Ray Origin/Camera
    vec3 rd=normalize(vec3(uv.x,uv.y,1));// Ray Direction
    
    float d=RayMarch(ro,rd);// Distance
    
    vec3 p=ro+rd*d;
    float dif=GetLight(p);// Diffuse lighting

    vec3 color=vec3(dif);
    
    // Set the output color
    gl_FragColor=vec4(color,1.);
}
                                

Sources
The Art of Code (Youtube): The Art Of Code
Inigo Quilez: http://www.iquilezles.org

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Powered by WordPress.com.

Up ↑

%d bloggers like this: