GM Shaders Mini: Raymarching
Howdy folks,
Today we're covering "raymarching" or specifically "sphere-assisted ray marching" which is a powerful raycasting algorithm. It's frequently used by shader enthusiasts because it's relatively easy to work with and can add extra effects like soft shadows, and glow as a byproduct.
Here's a 3D burger shader I made using a raymarcher in a pixel (fragment) shader:
By the end of this tutorial, you should be able to render your own 3D objects using shaders. Let's get into how it works!
Distance Fields
To begin we need a "signed distance field" or "SDF". This is a function that defines the shape of the object we are trying to render. The function should return the distance to the nearest surface of the object at any given point (negative if inside the object).
The simplest example is that of a sphere:
float sphere_distance(vec3 p)
{
return length(p) - 1.0;
}
This function defines the shape of a sphere with a radius of 1 unit. If for example length(p) == 4.0
then we are 3 units away from the nearest point on the sphere. If length(p) == 0.0
then we know we're 1 unit inside the sphere.
What if we want two spheres? Well the nearest sphere is simply the minimum of the two sphere's distances fields:
float distance_field(vec3 p)
{
return min(length(p)-1.0, length(p+2.0)-2.0);
}
This will combine the sphere with a radius of 1 with a sphere at (2, 2, 2) and a radius of 2.
Here's a visual example of a 2D distance field (raymarching works just as well in 2D):
Here you can see the distance field visualized. The red sections are inside the objects and the blue sections are outside. Getting darker shows the distance to the nearest surface.
The distance at the pink point is represented by the black circle, which intersects perfectly with the nearest surface.
Raymarching
Now imagine we want to find the nearest surface in a particular direction. Our distance field only tells us how far the nearest surface is but not where. We know that we can safely step within the distance field radius, without hitting anything, so we can just move our sample point to that radius and check the distance field there. If we repeat this maybe 100 times, we can get really close to the surface.
If we stepped further than the distance we might clip into the object, which can produce artifacts so let's avoid that.
Here's what my raymarcher code looks like:
vec4 raymarch(vec3 pos, vec3 dir)
{
float d = 0.0; // Starting distance
for(int i = 0; i<100; i++) // Step 100 times
{
float step_dist = distance_field(pos + dir * d); //Check distance field
d += step_dist; //March forward
}
return vec4(pos + dir * d, d); //Return intersection point and distance
}
As an extra optimization, we can stop the loop early when we get really close to the surface or beyond a maximum distance:
#define EPS 0.01
#define MAX 50.0
if (step_dist<EPS || d>MAX) break; //Stop loop at intersection or max distance
The last line goes behind the "d +=..." line.
And that's it! Now you can start rendering your distance field in 3D!
Once you have the depth, you can test your raymarcher like so:
gl_FragColor = vec4(vec3(1.0) / depth, 1.0);
Which simply makes pixels near the camera brighter and fade with distance.
You might be wondering how to calculate the ray direction:
vec3 dir = normalize(vec3(pixel.xy - 0.5*resolution.xy, resolution.y));
This can then be rotated with a view matrix as needed.
Here's a working demo on ShaderToy with some extra features.
SDF Tricks
Now that we have the basics covered, I can show you some other cool things you can do with SDFs!
Firstly, you can invert any shape by simply making it negative:
float inverted_sphere = 30.0 - length(p);
Then, instead of using the minimum to combine distance fields, you can actually use the maximum, which will model the intersecting solids between the two fields, allowing you to cut holes in objects.
I'll link Inigo Quilez's website which has a bunch of great examples and different shape functions.
The other neat thing you can do with SDFs is repetition. You can easily repeat an SDF infinitely like so:
float spheres = length(mod(p, 8.0) - 4.0) - 0.5;
This tiles the sphere infinitely every 8 units apart.
I mentioned soft shadows and glow effects, but that goes a bit beyond the scope of this tutorial today. If you're interested, the example I linked above shows how this works along with getting the surface normal and coloring models.
Conclusion
Hopefully, this made the process behind raymarching a little less mysterious. It's quite a difficult topic to write on because raymarching and SDFs can be used in so many different ways. If you're ready to go deeper, look up Inigo Quilez's work! He's one of the pioneers of this technology and helped me understand it 7+ years ago.
Now is a great time to learn this process. It won't be going away anytime soon!
If you find these tutorials useful please consider buying me a chicken tender. My entire tutorial series is funded by the support of people like you.
Thank you!