A few months ago I heard about a book named The Ray Tracer Challenge by Jamis Buck. The premise of the book is that it would teach you how to write a ray tracer from scratch. The unique approach of the book (and one I have never come across in any other book) is that it would do this via a test driven approach - the book would provide the test cases and their inputs and outputs, and you fill in the blanks in your language of choice.
I think I've noted in the past that if a book comes with exercises or practice questions, I skip them, either due to time constraints or not being in front of a computer when I read (I prefer my tech books to be old school paper). This was a bit of a concern as clearly I was going to have to do such exercises, a fear that turned out to be unfounded. Being fun and having a goal makes all the difference!
I bought the book on the 31st May and it sat on my desk for a few weeks whilst I tried to wrap up the next build of WebCopy. On the 2nd July I made my first first commit and I've spent almost all my free time up until the start of August working through it. I originally intended to blog a sort of mini-series as I progressed, but that went out of the window after the first commit - I was utterly enthralled by the challenge and didn't stop for such mundane tasks as writing blog posts! Even tearing away enough free time to write this review has been challenging, although I mostly wrote it whilst waiting for scenes to render and animations build.
As I noted, The Ray Tracer Challenge is unique to all other books I've read in the past. Like others, it is broken up into chapters and sections, each devoted to teaching you how to do something, be it calculate a shadow or rotate a point in space. Where it diverges is that for each feature there are a number of tests, provided in Gherkin syntax. You implement those tests, they pass, and bit by bit your ray tracer takes shape, almost without you realising.
However, Jamis doesn't just throw test cases at you and expect you to do all the work, he describes how the topic works and provides pseudo code so you can implement the tricky aspects of your tracer. Being well versed in algebra and maths is probably a bonus but is by no means required. I got through the book mostly without issue and when I did have a problem, Jamis has a forum where you can ask questions which I found to be very helpful.
The book is printed in full colour with lots of images and diagrams. A download containing the Cucumber specification files is also available so you don't even need to manually type in all the specifications. All in all, it is an impressive package.
I've spent many years writing tests using either MSTest or nUnit and other similar test frameworks. Cucumber is not your average test framework. I freely admit I spent the first two weeks fighting how SpecFlow (a .NET Core implementation of Cucumber) works but once I started working with it instead of against it I found it is perfect for this type of book.
I mentioned that Jamis takes you through the things you need step by step, firstly by introducing you to points, vectors and colour, before moving onto writing to a PPM file (a text based image format) so you can visualise your output. Although the book recommends you then use an external tool for viewing the files, I swiftly made my own before switching to a GUI so I could tinker scenes as I go. After this he describes matrices, those magical grids of numbers which can make a shape rotate on any axis, grow or shrink, move and more. I have to confess it took me a while to think of 3D objects as existing at 0, 0, 0 and having a matrix define where they are positioned in world space. It definitely makes the maths easier!
With the data structures out of the way, Jamis takes you though ray intersection with spheres, leading to your first rendering of a flat sphere, immediately followed by realistic lighting and shading. After this he shows you how to construct a scene using nothing but spheres, before then adding shadows to make it more believable. And, as you won't get very far with just spheres, he introduces planes as the second primitive shape.
Next up is patterns, implementing a variety of basic patterns such as stripes, checkers, gradient and rings. Then he introduces reflection and refraction. With this in place you can generate some really impressive visuals, although it takes a good understanding of light so you can set up materials appropriately.
More than half the book is covered now, and that is the last of the rendering techniques. The remainder of the book adds more primitive shapes - cubes, cylinders and cones. It takes a quick segue into into grouping shapes, something incredibly helpful - I made a right mess of trying to create table by individually positing and sizing cubes, instead of constructing a group and then positioning it as a single action. This then leads to triangles and how to read WaveFront OBJ files, before moving into smooth triangles so you can really make your teapot shine!
The penultimate chapter covers Constructive Solid Geometry which I found particularly impressive. Instead of making an expensive model from thousands of triangles, you take a few primitive shapes and combine them using set operations. This lets you create, for example, a hollowed cube with rounded edges and other such shapes that would otherwise require complex models. I especially liked shifting my camera so I was rendering from inside that shape!
The final chapter is all about additional ideas to try, such as different lights, texture mapping and more. No guidance here, you're on your own implementing these!
Finally, there is an appendix which describes, in YAML, the scene used to render the book cover. Jamis provides a number of other demonstration scenes in the same format on his forums. The YAML format is very easy to understand and putting together your own reader to load these scenes isn't too taxing. When I started this book I wondered if I would manage to get to the point where I could render that cover, and it came together so easily I'm still amazed.
Although it seems like the book jumps from subject to subject as you progress, it makes sense in the context of the tracer by gradually introducing the more difficult concepts and techniques.
If you're interested in rendering 3D graphics then this could well be the book for you. Even if you aren't interested so much in the rendering but working in a 3D space then this may be helpful.
You may also enjoy the book if you simply want a fun challenge where you don't copy and paste code, but build something fully functional from scratch.
You don't need to be a maths wizard to implement the ray tracer, Jamis provides pretty much everything you need as you go along. With that said, I did struggle sometimes - trying to position things in 3D space was a case of "enter random values until it looks good"!
As the book provides all test cases as Cucumber specifications, I think if you don't have access to this in your own development environment this will be a significant burden - I would not have like to try and implement these as "pure" MSTest or nUnit tests.
I am very glad I bought this book - as I noted I have never read anything quite like it. The way it was presented means I was never truly floundering, and it quietly taught me without me realising. I've always had trouble reading linear algebra but I've noticed that I'm now finding it easier to read some formulae. I've still got a way to go with that sort of stuff, but I'm definitely improving.
I did have some problems - several times I didn't notice that the result of a square root was supposed to be negated and wondering why my tests were failing. On occasion I simply didn't read instructions properly and implemented something almost, but not quite, right. And of course, I spent a lot of time trying to make SpecFlow fit how I have traditionally tested. But it was pretty much all user error... I didn't spot any mistakes in the book or run into insurmountable issues. I do wish there were a few more reference YAML files though, that would mean I could compare images I generate with those in the book to make sure I really had things working correctly, not to mentioning actually defining correct camera and lighting properties is more challenging that you might think.
In short, I found this book fun. Really, really fun. It reminded me why I became a programmer in the first place. It also had the unexpected side effect of making me realise that I've been burning out for a while now doing the same old same old... I think I'll be making some changes around here soon.
I also can't stress enough how awesome the test driven format of the book is. It was quite something to implement a few tests, and just like that you have another amazing piece of rendering functionality.
Even though I've reached the end of book, this isn't the end of the story. At the time of writing this review, Jamis has also published 3 bonus chapters on the forums, one for creating bounding boxes, another for soft shadows and the third for texture mapping. I really can't wait to get that last one implemented! Some of the (low poly!) OBJ files I've tested also reference external materials and supporting these should be straightforward enough.
As I wrap up this blog post, I've just ironed out some issues with my Bounding Volume Hierarchies implementation and now render speed of a model comprised of 23000 triangles at 1920x1080 has gone from 1 hour 20 minutes (already greatly improved by some other optimisations) to 12 minutes. Perfect!
I committed all my source to a Git repository and I plan on making this available once I've finished tiding up the code.
I'm still trying to find my voice with these reviews so if you have any comments please let me know!
Below are some screen shots of my journey through the book. I wish I'd thought to take more of the earlier steps, something to keep in mind for next time.
- 2019-08-11 - First published
- 2020-11-22 - Updated formatting