DEV Community

Csilla Lukacs
Csilla Lukacs

Posted on

4 Things I learned from Ray Tracing in One Weekend

 Recently, I followed the Ray Tracing in One Weekend book to implement my own ray tracer in C++. I considered following the Rust version, but settled on C++ for two reasons:
1) I have some experience with C++ and
2) Lots of things I read about Rust seem to compare it to C++, so if I want to understand Rust, I might as well get a deeper understanding of C++.

Here's a selection of things I learned.

1. CMake, Clang, GCC, Clang++, G++

By far the hardest part of the project was setting up the environment. I opened my editor of choice, VSCode. I installed the "C/C++ Extension Pack". I followed the steps the extension suggested and created my first cpp file. I ran it by clicking a triangle shaped button provided by the extension. And... it didn't work. I pasted the error messages into ChatGPT. It insisted that I was using a C compiler, when I should be using a C++ compiler. I am not! You are too! And so we went on. Finally, I had to admit that my tasks.json file (generated by the extension, mind you), did indeed reference clang instead of clang++. I think at this point I gave up on the extension, and decided to compile my code using the terminal.

The book makes some references to CMake, so I thought that maybe I'd have to use it, and a brief conversation with AI seemed to confirm this, suggesting that if I want to scale up my project beyond "one file", I'm better off using CMake. Nevertheless, I decided to see how far I could go with clang++. I wanted to really appreciate the need for CMake before trying it. It turned out to be unnecessary this time. My project consists of a main.cpp and many header files, all inside one folder, clang++ was able to handle this perfectly well.

2. build/inOneWeekend > image.ppm

I was of course dimly aware of the > operator, and I'm certain I've used it before, but I never considered extending it to programs I wrote myself. If I wanted to save something to a file, I would have to start with googling "open files in "... Turns out, you can just write to std::cout and then decide to redirect that to a file of your choosing when running the program. This also means that if I want to save image1, image2, image3, ... I don't have to recompile my program just to change the file name. (Yes, ok, I could also do that with command line arguments, but... how does that work again? argsomething...?)

3. std::flush

This is the third thing I'm highlighting that is not strictly speaking related to ray tracing. Didn't I do this to understand something about computer graphics? Maybe my point is that learning doesn't happen the way we assume it does. Maybe a book about ray tracing teaches you how to not litter your console output with

Scanlines remaining: 400
Scanlines remaining: 399
Scanlines remaining: 398
...
Enter fullscreen mode Exit fullscreen mode

Well, this is how:

for (int j = 0; j < image_height; j++) {
    std::clog << "\rScanlines remaining: " << (image_height - j) << ' ' << std::flush;
    for (int i = 0; i < image_width; i++) {
        // ...
    }
}
Enter fullscreen mode Exit fullscreen mode

4. What you will get wrong

Since all the code for the ray tracer is available in the book, I gave myself two rules:

  1. I will not copy and paste any code.
  2. If AI code completion suggests the correct code (with the same variable names as in the book!), I am allowed to press tab.

This meant that I still read every single line of code and thought about what it meant, but I could still breeze through the really really really obvious bits.
Still, I made two mistakes that resulted in a different image than what I was supposed to get:

Here, the top image was the desired result, and my code resulted in the bottom image.

Similar story below:

What I should have gotten

What I got

Want to guess? Ok I'll spoil it for you. The first one was literally a sign error, in that I added a minus sign where there shouldn't have been one, when calculating whether a ray intersects a certain sphere or not.

What about the second one? This one was more annoying, because I learned something from it that I would have preferred not to learn.
Here's my code:

if (rec.mat->scatter(r, rec, attenuation, scattered)) {
    return attenuation * ray_color(scattered, depth-1, world);
return color(0,0,0);
} 
Enter fullscreen mode Exit fullscreen mode

Here's the correct code:

if (rec.mat->scatter(r, rec, attenuation, scattered)) {
    return attenuation * ray_color(scattered, depth-1, world);
}  
return color(0,0,0);
Enter fullscreen mode Exit fullscreen mode

I don't blame you if it isn't obvious at first glance! It wasn't obvious to me either, and I blame my preference for not using braces around if blocks that are just one line. Which I must now admit is WRONG. If I was consistent about using braces everywhere, I would surely have noticed that just because return color(0,0,0) is unindented, it is not preceded by a closing brace, and therefore must still be part of the if block.

Ok that's almost 1000 words, I have more notes but they will have to go in part 2 because I'm tired of writing now!!!

Top comments (0)