So this is a small thing I've been working on over the past few days. The basic Idea at the start was: Could I program a graphics engine that could render a simple 3D scene using only the limited amount of memory, processing power and program space available on my trusty old Atmega1284p? Now, even though it is obviously impossible to render complex 3D models on an AVR, I still wanted to be able to render a scene with multiple objects in it.
However, the fact that I won't be able to use any real 3D models meant that I would need to restrict myself to a few, mathematically easy to define shapes as the "building blocks" and construct my scene out of these, as if I was building with Lego. The first thing that immediately came to my mind was a voxel engine. Basically, the entire scene would consist of a regular grid of cubes (voxels) that could each have a different color or even full texture, that I could then use to build a more complex scene. I already had experience with creating such an engine, so I went with this Idea. Theoretically, however, it should be possible to render objects other then cubes, such as spheres. However, the more different objects that can be rendered, the longer it will take to render the scene, so I decided to stick with only cubes for now.
Now, I obviously needed an algorithm to actually render the scene. Now, I know that Rasterisation would be way too resource intensive and too difficult to implement on a Microcontroller. So I decided to go with ray marching.
Basically, for each pixel that needs to be rendered, the renderer "shoots" a ray/vector out from the position of the camera at an angle that is calculated from the position of the pixel in the image, which then moves forward in steps until it hits a voxel, at which point the color of the pixel is set to the color of the voxel. Or you could calculate the exact coordinates at which the ray intersects whatever side of the voxel it hit and sample a texture at those coordinates, which is what I ended up doing.
Now, normally, Rasterisation is much faster then ray marching, however, ray marching becomes extremely efficient if your entire scene consists only of axis-aligned cubes of the same size. It also uses barely any RAM (less then 1KB), which leaves a lot of free memory for storing the scene and textures for the voxels, which makes it perfect for running on an AVR.
So I went to work. And after three days of testing and debugging and optimizing all the code, I created a program, less then 1000 lines in length, which uses a ray marcher to render a pre-defined scene and save the final result to an SD card in the form of a bitmap image. I spent a lot of time optimizing the code as much as possible, but I still expected the render to take at least an hour on such a slow processor, even at a resolution of 160 by 128 pixels. So you can imagine how surprised I was when it finished in 20 seconds!
Yeah, turns out you can render things quite quickly with this.
As for what the render actually looks like, well, here's on more thing: I suck at making textures, so I borrowed some from a video game I used to play a lot, and...
I basically made Minecraft for AVR microcontrollers.
But interestingly enough, you could almost make a game out of this. There are variables in place to move and rotate the camera. All you'd need to do to make this a playable game is to slap a color LCD onto an AVR, add buttons for controls, and....find a way to make this take at most half a second to render. Yeah, I'm still working on that last part. I'm sure a faster Microcontroller could probably do it, but I'm not aware of any faster AVRs that I could easily try this with.
For the time being, however, I quickly edited the code to render 40 separate frames, with the camera's Y-rotation being increased by 9 degrees before each new frame. This only took a little less then 10 minutes, and afterwards I edited all the frames together into a gif:
So that's pretty neat. However, you might think that the image is still pretty pixelated because of the low resolution. However, rendering a frame at a higher resolution doesn't really take longer then a few minutes either, so here's the scene in all its glory:
However, at this resolution you might begin to notice some minor glitches, especially at the corners of blocks. That mostly has to do with the fact that I optimized the algorithm for speed, and not for precision. I believe the glitches are mostly caused by the renderer's use of fixed-point arithmetic. However, these glitches become less and less noticeable at lower resolutions, which is also what the renderer is optimized for due to the low-resolutions of most SPI LCD screens (I do not currently own one of those, which is why I'm rendering to an SD card, but I might buy one soon).
If you want to try this for yourself or maybe render your own scenes, I put the source code up on pastebin. It's only two files:
there's a few settings you can modify at the beginning of the main function. One of them is called stepResolution. This basically defines the minimum length of the steps a ray can take as 1 / stepResolution. So a higher value will remove more of the glitches in the render at the cost of performance. Though setting this to anything higher then 20 without switching to floating point arithmetic, which you can do by adding the line '#define FLOATING_POINT' at the beginning of the file, will cause the render to fail horribly. You can also change the size of the world, but increasing its size will also increase RAM usage, so keep that in mind. Oh. Speaking of RAM, you WILL need an AtMega1284p for this. The compiled program is 35KB and probably uses almost all of the AtMega1284p's 16KB of RAM. So something like an AtMega32 will simply not cut it.