
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