
These examples show how boolean operators can be used to create complex shapes from simple ones. This 3d-modelling method is called CSG (Constructive solid geometry).
CSG is primarily built on 3 primitive operations named intersection ( ∩ ), union ( ∪ ), and difference ( − ).
The diagram below shows an example of what is possible with using this technique:
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 an 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. Intersection (∩)

Ray Marching Boolean Operator: Intersection
This example shows how the intersection operator is used on a cube and a sphere to create the shape as seen in the bottom left of the diagram:
precision highp float;
uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed
// Constants
#define PI 3.1415925359
//#define TWO_PI 6.2831852
#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);
}
//////////////////////////
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);
// 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 = intersectSDF(boxDist_0,sphereDist_0);
// Uncomment to view the box
//d = min(boxDist_0,sphereDist_0);
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 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,-6); // 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
d*= .2;
vec3 color = vec3(dif);
// Set the output color
gl_FragColor = vec4(color,1.0);
}
2. Union (∪)

Ray Marching Boolean Operator: Union
This example shows how the Union operator is used to create a single shape from three capped cylinders.
precision highp float;
uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed
// Constants
#define PI 3.1415925359
//#define TWO_PI 6.2831852
#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);
}
//////////////////////////
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 / 3.0);
// // 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 = intersectSDF(boxDist_0,sphereDist_0);
// Uncomment to view the box
//d = min(boxDist_0,sphereDist_0);
d = unionSDF(cylDist_0,cylDist_1);
d = unionSDF(d,cylDist_2);
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 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,-6); // 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
d*= .2;
vec3 color = vec3(dif);
// Set the output color
gl_FragColor = vec4(color,1.0);
}
3. Difference (−)

Ray Marching Boolean Operator: Difference
This example shows how the difference operator is used on the previous two shapes to create the final combined object as seen in the SDF diagram:
precision highp float;
uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed
// Constants
#define PI 3.1415925359
//#define TWO_PI 6.2831852
#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);
}
//////////////////////////
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 / 3.0);
// 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 = intersectSDF(boxDist_0,sphereDist_0);
// Uncomment to view the box
//d = min(boxDist_0,sphereDist_0);
d2 = unionSDF(cylDist_0,cylDist_1);
d2 = unionSDF(d2,cylDist_2);
d0 = differenceSDF(d1,d2);
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(2,5,0); // Light Position
lightPos.xz *= Rotate(u_time); // Rotation
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,-6); // 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
d*= .2;
vec3 color = vec3(dif);
// Set the output color
gl_FragColor = vec4(color,1.0);
}
This example shows how the Difference operator can be used to create a shape from spheres only:
precision highp float;
uniform vec2 u_resolution; // Width & height of the shader
uniform float u_time; // Time elapsed
// Constants
#define PI 3.1415925359
//#define TWO_PI 6.2831852
#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);
}
//////////////////////////
mat2 Rotate(float a) {
float s = sin(a);
float c = cos(a);
return mat2(c,-s,s,c);
}
float GetDist(vec3 p)
{
// Rotate the whole scene
p.xz *=Rotate(u_time / 2.0);
// Spheres. xyz is position w is radius
vec4 s0 = vec4(0,1,0,1), //center big
s1 = vec4(0,1,-.65,.65), //front
s2 = vec4(0,1,0.65,.65), //back
s3 = vec4(-.65,1,0,.65), //left
s4 = vec4(.65,1,0,.65), //right
s5 = vec4(0,2,0,.65), //top
s6 = vec4(0,0,0,.65), //bottom
s7 = vec4(0,1,0,.8); //center small
float sphereDist_0 = length(p-s0.xyz) - s0.w,
sphereDist_1 = length(p-s1.xyz) - s1.w,
sphereDist_2 = length(p-s2.xyz) - s2.w,
sphereDist_3 = length(p-s3.xyz) - s3.w,
sphereDist_4 = length(p-s4.xyz) - s4.w,
sphereDist_5 = length(p-s5.xyz) - s5.w,
sphereDist_6 = length(p-s6.xyz) - s6.w,
sphereDist_7 = length(p-s7.xyz) - s7.w;
float planeDist = p.y;
float d = 0.0;
// d = min(sphereDist_0,sphereDist_1);
// d = min(d,sphereDist_2);
// d = min(d,sphereDist_3);
// d = min(d,sphereDist_4);
// d = min(d,sphereDist_5);
// d = min(d,sphereDist_6);
// d = min(d,sphereDist_7);
// d = min(d,planeDist);
d = differenceSDF(sphereDist_0,sphereDist_1);
d = differenceSDF(d,sphereDist_2);
d = differenceSDF(d,sphereDist_3);
d = differenceSDF(d,sphereDist_4);
d = differenceSDF(d,sphereDist_5);
d = differenceSDF(d,sphereDist_6);
d = differenceSDF(d,sphereDist_7);
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 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,-6); // 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
d*= .2;
vec3 color = vec3(dif);
//color += GetNormal(p);
//float color = GetLight(p);
// 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