This shader was created for the Shadertoy Competition 2017. The shader is a tutorial about raymarching distance fields (using a ray marcher in Shadertoy).
The Shadertoy Competition 2017 was an event that lasted three weeks, during which you had to create a new shader every week. The subject of the third week was “The Shader Professor” – we were asked to make a shader that explained an idea, a paper or a technique.
I created the shader above, a tutorial that teaches how to render a 3D scene in Shadertoy using distance fields.
Note that a shader is not the ideal medium to explain things, so you can find much better tutorials about raymarching elsewhere on the internet. See, for example, the links in the “Further reading” section below.
Nevertheless, I think that this shader looks good and is interesting (and meta) enough to write down its transcription (see below). I won the challenge of the third week and ended fifth overall.
Raymarching Distance Fields
In this tutorial, you will learn how to render a 3D scene in Shadertoy using distance fields.
As an example, we will create this black-and-white scene of three spheres on a plane.
Create a ray
First, we create a ray.
The ray origin (ro
) will be at (0,0,1)
.
In code:
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec3 ro = vec3(0, 0, 1);
Code language: GLSL (glsl)
Now, we place a virtual screen in the scene.
It is located at the origin and has dimensions of aspect_ratio x 1.
We compute the ray direction (rd
) for each pixel (fragCoord.xy
) of our virtual screen.
In code:
void mainImage(out vec4 fragColor, in vec2 fragCoord) {
vec3 ro = vec3(0, 0, 1);
vec2 q = (fragCoord.xy - .5 * iResolution.xy ) / iResolution.y;
vec3 rd = normalize(vec3(q, 0.) - ro);
Code language: GLSL (glsl)
Distance fields
A distance field is used to find the intersection of our ray (ro, rd
) and the spheres and plane of the scene.
A distance field is a function that gives an estimate (a lower bound) of the distance to the closest surface at any point in space.
The distance function for a sphere is the distance to the center of the sphere minus the radius of the sphere.
The code for a sphere located at (-1,0,-5)
:
float map(vec3 p) {
float d = distance(p, vec3(-1, 0, -5)) - 1.;
Code language: GLSL (glsl)
We combine different distance functions by taking the minimum value of these functions.
In code:
float map(vec3 p) {
float d = distance(p, vec3(-1, 0, -5)) - 1.;
d = min(d, distance(p, vec3(2, 0, -3)) - 1.);
d = min(d, distance(p, vec3(-2, 0, -2)) - 1.);
Code language: GLSL (glsl)
The total distance function for this scene (including the plane) is given by:
float map(vec3 p) {
float d = distance(p, vec3(-1, 0, -5)) - 1.;
d = min(d, distance(p, vec3(2, 0, -3)) - 1.);
d = min(d, distance(p, vec3(-2, 0, -2)) - 1.);
d = min(d, p.y + 1.);
return d;
}
Code language: GLSL (glsl)
Now we can march the scene from ro
in direction rd
.
Each step size is given by the distance field.
We stop the march when we find an intersection:
float h, t = 1.;
for (int i = 0; i < 256; i++) {
h = map(ro + rd * t);
t += h;
if (h < 0.01) break;
}
Code language: GLSL (glsl)
Lighting a ray
Now that we have found the intersection
(p = ro + rd * t
) for our ray, we can give the scene some lighting.
To apply diffuse lighting, we have to calculate the normal of shading point p
.
The normal can be calculated by taking the central differences on the distance field:
vec3 calcNormal(in vec3 p) {
vec2 e = vec2(1.0, -1.0) * 0.0005;
return normalize(
e.xyy * map(p + e.xyy) +
e.yyx * map(p + e.yyx) +
e.yxy * map(p + e.yxy) +
e.xxx * map(p + e.xxx));
}
Code language: GLSL (glsl)
We calculate the diffuse lighting for a point light at position (0,2,0)
.
In code:
if (h < 0.01) {
vec3 p = ro + rd * t;
vec3 normal = calcNormal(p);
vec3 light = vec3(0, 2, 0);
float dif = clamp(dot(normal, normalize(light - p)), 0., 1.);
dif *= 5. / dot(light - p, light - p);
fragColor = vec4(vec3(pow(dif, 0.4545)), 1);
}
Code language: GLSL (glsl)
And we are done!
Adding ambient occlusion, (fake) reflections, soft shadows, fog, ambient lighting and specular lighting is left as an exercise for the reader.
Full source code
You can find (the full source of) the fragment shader on Shadertoy:
- Tutorial shader: https://www.shadertoy.com/view/4dSfRc
- The shader studied in the tutorial: https://www.shadertoy.com/view/4dSBz3
Further reading
- Ray Marching For Dummies!, a YouTube video by The Art of Code.
- Ray Marching and Signed Distance Functions by Jamie Wong.
- Íñigo Quílez’s tutorials on computer graphics, raymarching, SDFs, fractals, demoscene, etc.
- Shadertoy Unofficial, Fabrice’s blog with advice about Shadertoy GLSL programming and compatibility issues.
Similar posts
If you like this post, you may also like one of my other posts:
- Woman
- Human Document
- Tokyo – breakdown of a webgl fragment shader
- Escher and the Droste effect
- Image-Based Lighting