Back Original

tinyrenderer-rs

Kshitij Lauria

This page has an interactive wasm demo of the renderer!

To teach myself how 3D graphics works, I wrote a single-threaded, pure CPU software renderer in Rust. It’s a rasterizer, not a ray tracer (I’ve written some path tracers elsewhere). It has some cool features, like shadow mapping and some shader effects. I began by following Dmitry Sokolov’s tinyrenderer course and took it from there.

Computers are really fast

Computers are now fast enough that you can have fully 3D rendered graphics at interactive speeds with just a single core and no special tricks. My code is optimized for readability and simplicity. It does almost no tricky optimizations, and certainly no low-level black magic. All I did was somewhat sane performance-oriented design of the data: everything is a struct or a vector, no pointer-chasing, try to access the pixels sequentially. That’s it.

It’s doing a really expensive thing to cast shadows: it renders the entire scene from the perspective of the light in order to figure out what’s in the light vs. in shadow. Here’s what this looks like:

On the left is the actual scene being rendered. On the right is a depth map of what the light sees. For every pixel in the scene, we ask the question: after transforming this to the light’s point of view, is this the closest item to the light? In other words, is something blocking the light from reaching this pixel?

It’s a lot of math but makes for nice, realistic, dramatic shadows. All this in just one thread, just CPU. I enjoyed writing this in Rust; the types made it easy to catch bugs, there was a nice library for the 4x4 matrix operations I needed, and I didn’t have to compromise on performance to get the nice tooling and language features I like.

Oh and you can compile Rust to wasm. Here’s a live demo!

Hold F for first-person mode, WASD to move, mouse to look around. Press 1 to cycle through some wireframe views, and 5 to see the split screen with the flashlight’s perspective. Press Esc to release your mouse. Be careful with unmuting the music; it’s my janky implementation of bytebeat.

On a phone it has some janky touchscreen controls. Try it out!

It was surprisingly little code to get the wasm build working, for two reasons:

  1. It’s single-threaded CPU, so no threads and no GPU! Those are tricky in wasm.
  2. I designed the code with an imperative shell and functional core. So the vast majority of the codebase just does math and logic.

For more screenshots of the renderer as I worked on it, see the repo README. The wasm shim is in a separate repo so I could keep LLM code separate from my organic original.