Algorithmic Art: Raymarching
Short after discovering how to create artwork through a function, I started to learn more advanced tricks like raymarching. It was incredible to see that one can implement a 3d engine in a few lines of code starting from basic primitives like vectors and 3d points.
The principle is still the same, we define a function, that given the x, y coordinates and time, returns the colour of that pixel. The function is independently evaluated for every pixel. This allows the GPU to use massive parallelization to compute the final image.
The function in raymarching is a bit more complex. It starts by representing the world as a distance field. Every point in the 3d space has a value which is the distance to the nearest element. For example, here's how we define the distance to a sphere and a floor:
// give a point returns the distance from the sphere
float obj_sphere(in vec3 p) {
return length(p - origin) - radius;
}
// given a point returns the distance from the floor object
float obj_floor(in vec3 p)
{
return p.y+10.0;
}
// given a point returns the distance to the nearest object
float distance_to_obj(in vec3 p)
{
return min(obj_sphere(p), obj_floor(p));
} The function then defines a vector originating from the camera position towards the pixel on the screen and then further into the 3d world. This allows us to find out what the pixel is looking at and how far away it is from the camera. The raymarching algorithm then iteratively "marches" along this ray getting closer and closer to the object. The distance field helps us determine the size of each step we take along the ray. When the value reaches 0, we've hit the object.
// Raymarching loop
const float max_dist = 100.0; // Max depth
float d = 0.02; // initial step
vec3 p;
float distance_from_camera = 1.0;
for (int i=0; i<64; i++) {
if ((abs(d) < .001) || (distance_from_camera > max_dist)) break;
distance_from_camera += d;
p = camera_pos + rayDirection*distance_from_camera;
d = distance_to_obj(p);
} Once we know what object the pixel is looking at, we can compute its color based on its properties and lighting. In this example, I implemented a simple checkerboard pattern for the floor:
// procedural definition of Floor Color (checkerboard)
vec3 floor_color(in vec3 p)
{
if ((fract(p.z * 0.2) > 0.5) ^^ (fract(p.x * 0.2) > 0.5))
return vec3(1,0,0); // red
else
return vec3(1,1,1); // white
} The shader also includes basic lighting effects using the surface normal and camera position to create a Phong lighting model, giving the scene depth and dimensionality.