RayMarching Shader pt.3 (Smooth Blending Operators)

These ray marching shader examples show smooth blending versions of the boolean distance functions for intersection, union and subtraction.
These functions can be used to blend different objects together smoothly to create complex organic looking shapes.
The functions can be used in the same way as the boolean intersection, union and subtraction operators but have an extra parameter k that controls the size of the transition.

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. Smooth Intersection

Smooth Intersection
The same effect as with the boolean intersection function but with a controllable smoothness. For the example the smoothness value ‘K’ was set to 0.05:

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
 
///////////////////////
// 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*(distB+distA)/k, 0., 1.);
  return mix(distA, -distB, h ) + k*h*(1.-h); 
}

/////////////////////////
 
mat2 Rotate(float a) {
    float s = sin(a);
    float c = cos(a);
    return mat2(c,-s,s,c);
}
 
float sphereSDF( vec3 p, float s ) {
  return length(p)-s;
}
 
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);
}
 
float GetDist(vec3 p) 
{   
    // Rotate the whole scene
    //p.xz *=Rotate(u_time / 2.0);
    p.xz *=Rotate(PI * .25);
   
    // Sphere. xyz is position w is radius
    vec4 s0 = vec4(0,1,0,1);
    float sphereDist_0 = sphereSDF(p-s0.xyz,s0.w);
     
    // Box
    vec3 b0s = vec3(.8,.8,.8); //box size
    vec3 b0p = vec3(0,1,0); // box position
    float boxDist_0 = boxSDF(p-b0p,b0s);
   
    // Plane
    float planeDist  = p.y;
 
    float d = 0.0;
    d = smoothIntersectSDF(boxDist_0,sphereDist_0,.05);

    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). 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.,5.0*cos(u_time)); // Light Position
    vec3 lightPos = vec3(-2.,5.,-4.); // 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,3,-3.5); // Ray Origin/Camera position
    vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction
    
    rd.zy *= Rotate(PI*-.2); // Rotate camera down on the x-axis
   
    float d = RayMarch(ro,rd); // Distance
     
    vec3 p = ro + rd * d;
    float dif = GetLight(p); // Diffuse lighting
    d*= .2;
    vec3 color = vec3(dif);
 
    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

2. Smooth Union

Smooth Union
The same as the boolean union function but with a controllable smoothness factor. For the example it is set to 0.1:

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
 
///////////////////////
// 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(distB, distA, 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*(distB+distA)/k, 0., 1.);
  return mix(distB, -distA, h ) + k*h*(1.-h); 
}

/////////////////////////
 
mat2 Rotate(float a) {
    float s = sin(a);
    float c = cos(a);
    return mat2(c,-s,s,c);
}
 
float sphereSDF( vec3 p, float s ) {
  return length(p)-s;
}
 
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);
}

float cappedCylinderSDF( 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));
}
 
float GetDist(vec3 p) 
{   
    // Rotate the whole scene
    //p.xz *=Rotate(u_time / 2.0);
    p.xz *=Rotate(PI * .25);
   
//     // Sphere. xyz is position w is radius
//     vec4 s0 = vec4(0,1,0,1);
//     float sphereDist_0 = sphereSDF(p-s0.xyz,s0.w);
     
//     // Box
//     vec3 b0s = vec3(.8,.8,.8); //box size
//     vec3 b0p = vec3(0,1,0); // box position
//     float boxDist_0 = boxSDF(p-b0p,b0s);
     
    // Cylinders
    float c0h = 1.,c0r = .8; // Cylinder height, radius.
    vec3 c0p = p - vec3 (0,1,0); // Position
    float cylDist_0 = cappedCylinderSDF(c0p,c0h,c0r); 
     
    float c1h = 1.,c1r = .8; 
    vec3 c1p = p - vec3 (0,1,0); 
    c1p.xy *= Rotate(PI*.5);  // Rotate
    float cylDist_1 = cappedCylinderSDF(c1p,c1h,c1r); 
     
    float c2h = 1.,c2r = .8; 
    vec3 c2p = p - vec3 (0,1,0);
    c2p.xy *= Rotate(PI*.5);  
    c2p.yz *= Rotate(PI*.5);  
    float cylDist_2 = cappedCylinderSDF(c2p,c2h,c2r); 
   
    // Plane
    float planeDist  = p.y;
 
    float d = 0.0;
     
    //d = smoothIntersectSDF(boxDist_0,sphereDist_0,.05);
     
    d = smoothUnionSDF(cylDist_0,cylDist_1,.1);
    d = smoothUnionSDF(d,cylDist_2,.1);
     
    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). 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.,5.0*cos(u_time)); // Light Position
    vec3 lightPos = vec3(-2.,5.,-4.); // 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,3,-3.5); // Ray Origin/Camera position
    vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction
    
    rd.zy *= Rotate(PI*-.2); // Rotate camera down on the x-axis
   
    float d = RayMarch(ro,rd); // Distance
     
    vec3 p = ro + rd * d;
    float dif = GetLight(p); // Diffuse lighting
    d*= .2;
    vec3 color = vec3(dif);
 
    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

1. Smooth Difference

Smooth Difference
The same as the boolean difference operator but with controllable smoothness. For this example it was set to 0.05.
This example also shows how the three smooth blend ray marching operators can be combined to create the shape from the Wikipedia CSG diagram below, but with smooth edges :

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

/////////////////////////
 
mat2 Rotate(float a) {
    float s = sin(a);
    float c = cos(a);
    return mat2(c,-s,s,c);
}
 
float sphereSDF( vec3 p, float s ) {
  return length(p)-s;
}
 
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);
}

float cappedCylinderSDF( 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));
}
 
float GetDist(vec3 p) 
{   
    // Rotate the whole scene
    //p.xz *=Rotate(u_time / 2.0);
    p.xz *=Rotate(PI * .25);
   
    // Sphere. xyz is position w is radius
    vec4 s0 = vec4(0,1,0,1.05);
    float sphereDist_0 = sphereSDF(p-s0.xyz,s0.w);
     
    // Box
    vec3 b0s = vec3(.75,.75,.75); //box size
    vec3 b0p = vec3(0,1,0); // box position
    float boxDist_0 = boxSDF(p-b0p,b0s);
     
    // Cylinders
    float c0h = 1.,c0r = .55; // Cylinder height, radius.
    vec3 c0p = p - vec3 (0,1,0); // Position
    float cylDist_0 = cappedCylinderSDF(c0p,c0h,c0r); 
     
    float c1h = 1.,c1r = .55; 
    vec3 c1p = p - vec3 (0,1,0); 
    c1p.xy *= Rotate(PI*.5);  // Rotate
    float cylDist_1 = cappedCylinderSDF(c1p,c1h,c1r); 
     
    float c2h = 1.,c2r = .55; 
    vec3 c2p = p - vec3 (0,1,0);
    c2p.xy *= Rotate(PI*.5);  
    c2p.yz *= Rotate(PI*.5);  
    float cylDist_2 = cappedCylinderSDF(c2p,c2h,c2r); 
   
    // Plane
    float planeDist  = p.y;
 
    float d0 = 0., d1 = 0., d2 = 0.;
     
    d1 = smoothIntersectSDF(boxDist_0,sphereDist_0,.05);
     
    d2 = smoothUnionSDF(cylDist_0,cylDist_1,.1);
    d2 = smoothUnionSDF(d2,cylDist_2,.1);
     
    d0 = smoothDifferenceSDF(d1,d2,.05);
     
    d0 = min(d0,planeDist);
 
    return d0;
}
 
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.,5.0*cos(u_time)); // Light Position
    vec3 lightPos = vec3(-2.,5.,-4.); // 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,3,-3.5); // Ray Origin/Camera position
    vec3 rd = normalize(vec3(uv.x,uv.y,1)); // Ray Direction
    
    rd.zy *= Rotate(PI*-.2); // Rotate camera down on the x-axis
   
    float d = RayMarch(ro,rd); // Distance
     
    vec3 p = ro + rd * d;
    float dif = GetLight(p); // Diffuse lighting
    d*= .2;
    vec3 color = vec3(dif);
 
    // Set the output color
    gl_FragColor = vec4(color,1.0);
}

Sources
The Art of Code (Youtube): The Art Of Code
Inigo Quilez: http://www.iquilezles.org
Wikipedia: https://en.wikipedia.org/wiki/Constructive_solid_geometry

Advertisement

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 )

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.