Feb 11, 2016
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.
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.
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.
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.
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
255, setting the pixel to the closer value.
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.
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.
This is what happened when I used both Floyd-Steinberg and Atkinson dithering at the same time and had them share an error buffer.
I settled on Floyd-Steinberg. Atkinson is also nice, and I might revisit it once I have textures on the spheres.
With dithering in place, I needed better lighting. Again, more awesome-looking bugs on the road to success.
I implemented a single point light, since that will work for the scenes I want to render in the end. Also, shadows.
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.
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.
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.
I wrote a great piece of tooling that made things fairly enjoyable. I’ll probably write another post about it and feedback loops. ↩