Rendering with Voxel Raytracing
Conventional rendering has us send a lot of triangles to the gpu, and if we want to render a few kilometers of meter cubes, that turns out on one hand to be a lot of triangles, and on another hand it is not obvious how to update the triangles. This is presumably a solved problem in graphics programing, but I have barely any knowledge on graphics programming. Sending all the triangles is too many triangles, but just sending the visible ones means that an update might be an insert in the middle of a triangle strip, and that seems difficult to handle memory-localitywise.
On the other hand, a voxel landscape does seem like something that should be relatively straightforward to render. After poking around a bit I found this nice paper on "A Fast Voxel Traversal Algorithm for Ray Tracing", which outlines an algorithm by which we can perform ray tracing on a voxel environment using minimal computation. It should allow us to send the raw voxel data to the gpu, so updates are just changes to the existing matrix. It is also pretty straightforward to implement!
The Math
Define a ray as parameterized on with being the origin position and being a vector representing direction of movement in . Looking first at the x axis, we can easily find the parameterizations of where - that is, where crosses from one voxel to another along the x-axis. This is given by:
This math works for all dimensions . We can then find the list of parameterizations of where , that is, where crosses from one voxel to another along any axis by merging all of into a single list. This list can be iterated through by finding the next smallest value in that set:
And with that, we have all the tools necessary to raytrace through a voxel world, by iterating over until we either find a voxel at or escape the bounds of the world.

The Implementation
Lets start to implement the above, defining a function vec4 raytrace(vec3 u, vec3 v) that returns the color of a pixel based on the path a ray of light would travel from a given location in a given direction.
First, we define , which will always hold the next-smallest value for each of the three dimensions
float tMaxOf(
/** position coordinate */
float u,
/** direction vector coordinate */
float v,
/** floored coordinate */
float c
) {
return (c - u + (v > 0.0f ? 1.0f : 0.0f)) / v;
}
vec4 raytrace(vec3 u, vec3 v) {
vec3 voxel = floor(u);
vec3 tmax = vec3(
tMaxOf(u.x, direction.x, voxel.x),
tMaxOf(u.y, direction.y, voxel.y),
tMaxOf(u.z, direction.z, voxel.z));
...
}
Then, continuing to solve for , we can define :
vec3 delta = sign(v) / v;
We also need to know what direction each integer change of voxel coordinates will go in, using vec3 step = sign(v), and then have all the tools necessary to infinitely iterate over the ray:
vec4 raytrace(vec3 origin, vec3 direction) {
vec3 voxel = floor(origin);
vec3 step = sign(direction);
vec3 delta = sign(direction) / direction;
vec3 tmax = vec3(
tMaxOf(u.x, direction.x, voxel.x),
tMaxOf(u.y, direction.y, voxel.y),
tMaxOf(u.z, direction.z, voxel.z));
while(true) {
if(tmax.x <= tmax.y && tmax.x <= tmax.z) {
// if t_{x_i} is less than t_{y_j} and t_{z_k}, move in the x direction
voxel.x += step.x;
tmax.x += delta.x;
} else if(tmax.y <= tmax.z) {
// if t_{y_i} is less than t_{z_j} move in the y direction
voxel.y += step.y;
tmax.y += delta.y;
} else {
voxel.z += step.z;
tmax.z += delta.z;
}
}
}
Then we just need to return early if we start in a voxel, cap the number of iterations to prevent infinite loops, keep track of which voxel face we are traversing through at each step, and return the background color (call it white for now, although we will get into the sky coloring later) if no voxels are intersected with or a distinct color based on the face that was interacted with. In this first example, we can just distribute voxels in a linear matrix every 8 blocks - and by capping the iterations to 1000, this should be rendering about 250 cubes in each direction, or a total of 15.625 million.
TODO
- Why is it all messed up when looking up?? I was expecting the math to work out the same for each dimension, so clearly the fact looking closely parallel to the z direction is odd
- How to calculate where in the voxel the ray intersects it
- terrain map
- fractalize it: multiple different levels of voxel look up
References
- "A Fast Voxel Traversal Algorithm for Ray Tracing"
- 'Cubes' game implementation of above paper
- WebGL2 Reference Guide
- WebGL2 Fundamentals
- The whole site is great, although this example in particular packed a lot of value
- Wiki for Spherical coordinate system
- Lil geometry refresher