Satcaster

I’ve been working on a 1-bit raycaster for the past six months. It’s a learning project, aiming to teach me more about C++ (originally), math, computer graphics, and hardware. Eventually, I’d like to build a display that can hang on a wall and display images of random groups of planets and moons.

The code is on GitHub.

Liftoff

I’d be remiss not to mention my inspiration. I really wanted to do something with flip-dots since seeing this. When I saw this dithered staircase work from @lorenschmidt on Twitter, I knew I had to do something with dithering. I’ve also drawn a lot of inspiration from things @katierosepipkin has done.

I started in C++, with which I had almost no experience. I quickly got frustrated by how difficult it was for me to get anything done. Previously I had used Ruby, Javascript, a little Python and some Java, and C++ was a pretty big departure from those languages. I grew discouraged; I just wanted to see something on the screen, not wrestle with pointers!

So I switched languages

I chose Python. It shouldn’t be horribly slow, and I should be able to get off the ground much more quickly with it than with C++.

Soon I could render a couple of spheres as shadeless white discs.

multiple spheres

While I enjoyed working with Python, it was pretty slow. I’m sure I could’ve tuned it up a bit, but it took about 15 seconds to render an image. In the throes of slow code woes, I wrote this:

If I can’t get the performance to a level that I’m happy with, I’ll probably rewrite it in C++ or Rust or something.

Guess what I did.

I switched back to C++

I wrestled those pointers. At times I felt like I really knew what I was doing. The render time went from 15 seconds with Python to about a tenth of a second! Then I’d notice I was making rookie mistakes like it was amateur hour or something. I was learning a new language and still accomplishing a lot1, though, so I felt pretty good about the overall state of things.

Once I started to get my footing and feel productive in C++, I added some basic shading.

basic shading

Dithering

Next was a big step toward the final asthetic I wanted for the images. The first pass I took at dithering just compared each pixel to a random number (per pixel) between 0 and 255, setting the pixel to the closer value.

random dithering

This was great! Things were really starting to come together. Random threshold dithering ends up being pretty noisy, though, which isn’t the look I’m going for. I implemented a few better-looking, more sophisticated dithering algorithms. If you’re not familiar with the process of dithering, Wikipedia has a good introduction.

simple error diffusion

Possibly my favorite part of this project so far has been the bugs I’ve encountered. For example, this is what happened when I reset the error buffer instead of letting it carry over each line.

moire bug

This is what happened when I used both Floyd-Steinberg and Atkinson dithering at the same time and had them share an error buffer.

shared error buffer

I settled on Floyd-Steinberg. Atkinson is also nice, and I might revisit it once I have textures on the spheres.

floyd-steinberg dithering

Lighting

With dithering in place, I needed better lighting. Again, more awesome-looking bugs on the road to success.

melting planets

dust trails

I implemented a single point light, since that will work for the scenes I want to render in the end. Also, shadows.

lighting

Getting Rusty

Though I was making progress, I felt frustrated a lot while working with C++. I got pretty burnt out, to the point that I had to stop working on it for a month even though I really wanted to keep making progress. One of the worst frustrations was that I could compile my code, and it seemed to make sense, but it would segfault when I ran it with no indication of what caused the issue. I made a lot of rookie mistakes with pointers, but I was just learning. I found the C++ environment to be unforgiving as I tried to figure things out.

Throughout the project, I had been hearing more and more about this language called Rust. From their homepage:

Rust is a systems programming language that runs blazingly fast, prevents segfaults, and guarantees thread safety.

Blazingly fast and no more segfaults? I need this. I watched some talks and read some articles. I had to try it. I first wrote a Brainf*ck interpreter in Rust, to get a feel for it.

Writing that interpreter was fun! More fun than I’d had with programming in a while. I was really excited about Rust. The compiler is incredible. I get a concise, helpful error message when I do something that would’ve caused C++ to segfault. It even suggests corrections for typos! Catching things at compile time like this is so much better than having to sort out why things crashed at run time. I might write more about my experiences with Rust, but in short: I’m loving it.

After the interpreter, I began rewriting the Satcaster in Rust. It’s been dreamy. Having the compiler there to help me out allows me to be more confident making changes than I ever was with C++. I’ve been more productive because of this confidence. I’m now using SDL2 bindings to open a window and animate a moon orbiting around a planet, and the light follows your mouse.

orbit

I was able to port some of the code without changing it too much, so it’s not like I was starting from scratch, but I went from zero to that gif in about five hours over the course of two days.

Onward

I’ve learned a lot in the last six months on this project, and I’d like to shift focus to the other goal I set out with: having a physical display on my wall. The one thing that I’m still missing on the software side is textures. I want the spheres look like planets, not ping pong balls. I’m going to swap the custom rendering code I’ve written for OpenGL (the journey was more important than the code I end up with). I’ll render a grayscale image with OpenGL, then dither it to produce the final output. This will make texturing a lot easier.

I’m certainly not going to stop learning things. Building the physical display will have lots of challenges and lessons. I haven’t used OpenGL much, so I’ll need to learn that. I’ve still got a lot to learn about Rust, but I’ve also got a compiler that’s helping me along the way.


  1. I wrote a great piece of tooling that made things fairly enjoyable. I’ll probably write another post about it and feedback loops.