I've been working on a new game lately, with a nice pseudo-3D effect
for the display. After a while, it dawned on me that what I was doing
there essentially amounted to voxels. Which was strange, because
while I had read about voxels before, my interest in the topic was
academic at best. But now the connection was made, I decided to take
a closer look, just to know what possibilities I might be overlooking.
But first, what exactly are voxels?
You know what pixels are, right? On the computer, an ordinary two-
dimensional image is made of little dots arranged in a grid. Whenever
you want to see what the image represents, you simply draw each dot
on the screen, row after row, making sure the dots in each row line
up vertically. Well, voxels are exactly the same thing, except in 3D.
Which brings up an obvious question: short of an actual volumetric display, how do you present a three-dimensional grid of dots
to a viewer? Why, via 3D projection of course! And as I
demonstrated in a previous article, that's really easy to do
for the basic case.
var TwoPointFive = {
Camera: function (x, y, z, f) {
this.x = x || 0;
this.y = y || 0;
this.z = z || 0;
this.f = f || 200;
},
project: function (x, y, z, camera) {
x -= camera.x;
y -= camera.y;
z -= camera.z;
var scale = camera.f / z;
var px = x * scale;
var py = y * scale;
return [px, py, scale];
}
}
With that in place, all we have to do is go over each layer in turn
and plot the coordinates of each voxel on screen.
for (var z = DEPTH - 1; z >=0; z++) {
for (var x = 0; x < WIDTH; x++) {
for (var y = 0; y < HEIGHT; y++) {
var p = TwoPointFive.project(x, y, z, camera);
// Determine the voxel's color somehow.
var color = getColor(x, y, z);
// Paint your voxel here.
}
}
}
Here, I make sure to start from the back and come towards the camera,
a.k.a. painter's algorithm. A more popular option nowadays
is to generate a cube around each voxel and let OpenGL worry about
the rendering; but then, that would take care of the 3D projection
as well. As it is, I simply paint each voxel as a square, scaled
according to distance from the camera.
Wait... isn't it cheating to use a flat image in a discussion of
volumetric pixels? That's more like... a texture! No, really, it is.
It's also the same basic principle; with voxels, we simply have layer
after layer of data, instead of just one. Which presents a problem.
Believe it or not, this coarse scene contains as many voxels as the
one before. (Though mostly obtained by mirroring.) The square-cube law ensures that the amount of data required increases fast with
resolution. Luckily, there are ways to mitigate this problem.
This is an ordinary heightmap, except rendered with voxels instead of
polygons. The height of each data point is encoded in its shade of
gray. It works because we're only interested in the surface — and as
a bonus, we can still use the same value to color the voxel. (Under
realistic conditions, a color map would be in order.) Note how I've
drawn the voxels taller than they are wide in order to hide the seams,
and it's still not perfect. I guess there's a reason polygons are
more popular.
What's the point of using voxels, then? Nowadays, they are mostly
used as a stylistic choice, as they give games a very distinctive
appearance. They also naturally enable fully modifiable environments,
something rather difficult to implement in a polygonal world.
Otherwise, polygons are better studied and directly supported by the
commodity GPUs built into almost any computer.
Back around 1997, however, before 3D acceleration was ubiquitous,
voxels were enjoying considerable attention. Quite a few famous games
used voxels for some or all of their graphics, Blade Runner (1997) being my
favorite example. See, voxels are easy to understand, and require
very simple calculations. But the comparative difficulty of animating
voxel objects, together with the march of technology, relegated them
to just another tool in the toolbox. Use wisely.