Last August, I got it into my head that a gyroscope could give you intuitive control over three-axis rotation, and I wanted to see it work. I figured I'd try three axes of motion too, and for some days I built up nice flight controls and theorized about direct manipulation.
To test motion I needed an environment to move around inside. So I built a sparse grid of cubes in three axes, and spent ages just flying and tweaking until it felt really good. At the time, I concluded the controls were too clumsy to offer anything but forward motion if you had three axes of rotation, so for a long time Degrees was going to be an outdoor space flight game.
Then one day, I got in.
The cubic grid made these cool visual patterns, kind of like the moiré you see in layered chain-link fences. Technically that's aliasing, and aliasing is usually bad because it's a visual distraction and an accident. But when it's not an accident, aliasing can be interesting and unique. For example, when guitar effects manhandle the signal, roundoff error gets exaggerated and you get a harder sound. So I welcomed aliasing from 3D patterns as the basis of the game's visual style. Everything would be cubes: Voxels.
I spent a few weeks creating arbitrary level geometry in tightly-packed spaces and making it draw quickly. I started with a 3D grid of color values and derived the surfaces. Since every square points one of six directions, there were lots of unique optimizations to try. This established a virtuous cycle where I'd decide I wanted the visuals to improve in some direction, then I'd code it up and see how fast I could make it, and therefore I'd have an idea of the technical limits of that style. The game would get prettier, then faster, then prettier again.
Random voxel data for testing surface generation.
The game needed moving objects next, and they had to be voxel objects. But I needed to do something special, because they needed to stay in rectilinear alignment with the level, or else it would just look messy. They needed to rotate but still be part of the voxel grid. Besides, I wanted more aliasing: Just like a rotating object in a pixelated game creates stairstep patterns at its edges, a rotating object in a voxel game should create those patterns all over its surface. So I needed to build these shapes once and draw them for just one frame.
For basically all of the fall, I spent a day or two every week working out a method of translating a rotated mesh of surface triangles into a solid voxel grid. The core idea is scan conversion, which GPUs do in 2D with every triangle on the screen. But I needed it done in 3D. After weeks of trying and failing to fit the whole problem in my mind at once, I gave up. Instead I learned to build things bottom-up, in this case from 1D to 2D to 3D, with careful planning about what each inner step needed the outer step to set up for it.
A sample shape rasterized in 2D, but not yet in 3D
The voxel rasterization code is long and complex, but it really works. It's only a tiny part of the running time, even compared to the time to create the surface geometry for those voxels. But I'm most proud of realizing a new kind of animation I haven't seen anywhere else. Objects in the world look really interesting in just the way I hoped they would. Success with real-time voxel rasterization is probably the reason I've stuck with this project all year. Actually, this has to be when I decided I was really making this game and not messing around.
When I had the solid grid produced, I could derive renderable surfaces just like I was doing with levels. But now that had to be fast too. I learned a lot about C optimization, in particular, how to inspect the assembler to make better decisions in high-level code. (Did you know there's a big difference between C macros and inline functions when you care which variables get stored in ARM's limited registers?)
The player's ship, rasterized at an arbitrary rotation
The shapes looked cool, but the squares are flat and the lighting is uniform, so they lacked detail. To add detail on a per-voxel basis, I tried out ambient occlusion shadowing. You see it used to great effect in Minecraft, a game I love dearly and another great use case of visual aliasing. Of course to test shadowing I needed more interesting shapes in levels, so I threw together some perlin noise shapes like asteroids and caves, and stratified them with rainbow colors. Ambient occlusion is a winner.
The ship, asteroids, and a perlin noise object with fancy shadowing
Coloring and shadowing the voxels in real time became an interesting problem too, and part of the solution was to have a single palette texture with color values on the rows and shadow values on the columns. This made the pixel shader really cheap, and it had the additional effect of making lighting effects nonlinear. That is, the amount of light on a spot doesn't have to multiply into the color and adjust it relative to pure black. Instead, the white and black points can be anything. I played with that and made a bunch of palettes that are essentially photo-filtered versions of the original. Now I've got a bunch of different looks I can swap out in the game in real time. So by this time the visual style was feeling pretty well developed.
A moody palette with a vignette effect
At some point along the way I created a starfield behind the rest of the level, but I had the idea to draw it as two layers. The blue layer moves just like a background would, but the purple layer lags behind according to how you're rotating. The point of this was to make the controls feel more solid: Unlike a joystick, tilt controls have no spring to bring you back to center. By expressing your instantaneous rotation implicitly as a vector all over your peripheral vision, I thought it would be a strong hint to your sensorimotor system. The rotation controls seem more learnable this way.
The starfield, showing that the player is rolling to the left
The cave structures I built as sample levels were interesting enough that I wanted to fly around inside them, but my controls were built for forward motion in graceful arcs. So I built a new scheme of indoor controls, and the closest relative to it is Descent. If you played Descent, you remember the huge list of controls you'd have to configure. Each direction on each axis was an independent function. And I loved that, but I never quite stopped tripping over my fingers. But on an iPad, steering can work as a three-axis aggregate via the gyroscope, and thrust can work as a three-axis aggregate with one thumb on the screen.
In a cave. Indoor controls actually switch to first-person…
I decided it was time to build indoor levels, and that meant I'd need a level editor, and I've been working toward that goal ever since starting this blog.