The Ray Tracer Challenge

6 Jan 2026

Tags: ocaml, graphics, testing

For the last few years I've spent the run up to the festive break working on something graphical, and this year whilst I was a little late to start, I decided to have a go at the Ray Tracer Challenge book by Jamis Buck. This book provides a language-neutral guide to building a classic old-school ray tracer, an art-form that scratches the nostalgic itch for me, as I have vague memories of trying to use POV-ray back in the early 90s at uni, back before even Pixar was using the technique to make films.

The book builds you up from first principles, first gently introducing you to the maths needed, though without explaining why, just how, to keep things moving implementation wise (it provides links to articles on the how for you to chase in your own time). The first couple of chapters you don't get any pretty pictures out, but it is here that the book does a wonderful trick: it makes you write unit tests for every tiny step. Make a tuple to hold a point: write a test or two, change it to also hold a vector, write another unit test. Now add matrices, and we have a few more tests, then multiply matrices and add tests for those. Whilst this might sound like drudge work to some, it actually means you get little sense of achievement on every page when you get some tests to pass, rather than making you wait until you're several chapters in and you make your first scene:

A screenshot of a window showing a red sphere on a blue background. the shading is just a depth map of the sphere, with edges darker than the centre.

I actually jazzed this up, as the book just has you do a flat projection of a sphere, but I thought it was more interesting to make a depth map from it to convince myself we had 3D objects early on.

However, once the first few chapters of building up the fundamentals are out the way, you start to progress at a reasonable clip - though still adding tests at every step. It wasn't long before we added lighting using Phong shading, which is a simple model that looks reasonably good quickly, and for me has a very nostalgic look and feel to it.

A screenshot of an orange sphere on a black background this time with an obvious light source off screen with shading on the lower right side and a specular highlight on the upper left. It would have felt quite at home in the early 90s.

I chose to do the challenge in OCaml, which is a language I enjoy using, but haven't had much call to use this last year outside of hobby things like Claudius. On one hand, this meant I was using a language I was familiar with, but it also is one goes slightly contrary to the methods the book takes, which forced me to make some fun design decisions on how to translate things when it uses polymorphic types for different shape types or patterns. Nothing earth shattering, but in the past I've not needed to make those decisions, so it was good practice to take programming patterns I'm familiar with in imperative languages and be forced to find the idiomatic way to tackle it in OCaml.

Another screenshot of the lit orange sphere, but now it has a belt of multicoloured smaller spheres surrounding it.

Above you can see we start to add multiple objects and have a way to specify their location. This is where my experience doing things like Tiny Code Christmas and Genuary paid off, as generating patters and colours with a few well placed trig functions is somewhat second nature to me now :)

To render the graphics out I'm actually using TSDL, and OCaml wrapper for the multi-platform SDL graphics library. Thankfully I was able to crib from my work on Claudius to get this up and running, as otherwise the one slight let down to the book is that it has you generate your output in the somewhat obscure PBM format, which is a text file based image format. Preview on the mac will apparently display these files, but otherwise you need to find a tool to convert them. Given the book specifies no language for you to use, there isn't much it can do here I guess, but it is the one rough point in the proceedings.

My SDL based output viewer was good as it also let me "animate" the output, with the lighting source actually rotating around the scene, albeit slowly as each frame takes ten seconds to draw currently in its unoptimised state. Still, when you start to add shadows, that sort of "animation" really helps bring the scenes alive a little.

A screenshot of a ray traced orange sphere with a ring an smaller rainbow coloured spheres around it. The scene is lit from the top left, and yo can see that the smaller spheres cast a shadow on the larger one, and the large one is shading the far side ring spheres.

Even at the later chapters, where we're adding shadows, or when we introduce an infinite plane over which to float your objects like we have below, you're still adding unit tests for every little step. This means that with just one exception I think every time I went to render something to the screen, it just worked. Which is just as well, as there's a lot of places it can go wrong as you get to the later stages.

A screenshot of a ray traced scene of a large orange sphere with a series of rainbow coloured spheres surrounding it. Below the spheres is a neutral grey ground plane with the shadow of the spheres cast upon it.

But it's here that I think the test drive approach really helps: you never really feel swamped by the complexity of ray tracing. Because as the reader you're going one unit test at a time, every step feels quite small. It reminds me of the book Code by Charles Petzold, which I read over twenty years ago now, which pulls a similar trick. It walks you through how a computer works starting with just a simple relay switch, which then it uses to build a logic element, then a gate, then an adder, etc. etc. until you arrive a few hundred pages later at a full working CPU that is based entirely on relays. But because each increment is build on the previous one with no reference to the broader context, you're elegantly guided through something increadibly complex without being made aware of that complexity until you get to the end and look back. Buck does a similar trick in this book.

A screenshot of a ray traced scene of a large sphere with a ring of smaller spheres around it floating over an infinite plane. The large sphere has alternating light and dark orange stripes on it, the smaller spheres form a rainbow of bright colours around it, and the ground is a light and dark grey checker board pattern.

The test driven approach also made it an excellent thing to work on over a festive period. Because each step was small and self contained within a unit test, it was easy to do ten minutes here and there and never feel you were leaving mental state that would need to be rebuilt. I could just chip away at it as time allowed. I only managed to get half the way through the book by the end of the festive break, but by the end I was starting to make some classic Ray Tracer images from my childhood with reflections and the ubiquitous grid texture. All achieved with just a few minutes here and there each day.

A screenshot of a raytraced scene of a large orange striped sphere surrounded by a ring of smaller, rainbow coloured, spheres, all floating over a checkerboard patterned infinite plane. You can see the shadow cast by the spheres on the plane, and you can now see the plane and spheres all reflect each other. In the lower right though the purple bit of the rainbow is being reflected a bit much and is eye catching in a weird way.

The main point of this is that the Ray Tracer Challenge book is by far and away the best introduction to Test Driven Development, and at no point does it state it is trying to do this, or try to convince you that unit testing is great. I just keeps saying "this way to pretty pictures!" and before you know it you have some nice graphics and just under 200 unit tests. It's really quite an amazing achievement.

So if you ever want a way to introduce a developer to the art of testing, this is a great first book for that. It also is a great book for making pretty pictures if you're into that sort of thing :)