Ray Tracing in a Weekend Part 1 - Intro and First Simple Image


Introduction

In the next few posts I’m going to document my journey through Peter Shirley’s book Ray Tracing in a Weekend and its two following volumes Ray Tracing - The Next Week and Ray Tracing - The Rest of Your Life (now available for free). I found the first book on Amazon Kindle quite a long time ago, but then I always had to put it on the backburner because I had to finish other courses first. Unity3D, VR development, edX’s Computer Graphics, IBM’s data science specialization and three Complexity Explorer courses… if I think about it – no wonder my ray tracing progress was so slow!

But the other courses are complete now (well, except for a capstone project), and I can spend some focused time on computer graphics again. I have so many ideas especially after having learned about complexity and dynamical systems - now I really need to learn how to bring things to the screen or a page before produce more notes for the ever growing pile of paper.

In the following segments I’ll collect some notes and links for future reference, as well as my code in C++ and Javascript. My C++ is a little rusty, so I may also include some general notes on C++ that are not directly related to ray tracing. I’ll also (attempt to) implement everything in Javascript for the following reasons:

  • See what’s possible and how it’s done. Eventually I want to dive deeper into Javascript graphics, WebGL, and WebVR – and this will be a good opportunity to play with it a little until I’m ready to move forward.
  • Add some interactive demos that work for (almost) anyone and don’t require compilation. Please feel free to view the source code and play with it yourself!
  • See how Javascript performance compares to the C++ version. It’s obvious who’ll win the race - but it would be fun to see how much slower it is, and how much speedup can be achieved with certain optimizations. (My PHP ray tracer that took four months to render a picture still makes me laugh if I think about it.)

The code in the first few chapters is short enough so I can include it in full length. But I will probably add excerpts once the programs are getting more involved. Please visit my git-repository for all the code I’ve typed up, or visit Peter Shirley’s book repository.

I plan to add some notes and thoughts here, but I won’t try to rewrite Peter’s book. If you want to follow along, please check out the books mentioned above. They’re excellent!

Simple Image Output with PPM

By using the Portable PixMap (PPM) format we can create images by simply writing data to a text file. No special libraries needed.

ppm-example.cpp

#include <iostream>

int main() {
  int nx = 200;
  int ny = 100;

  std::cout << "P3\n" << nx << " " << ny << "\n255\n";
  for (int j = ny-1; j >= 0; j--) {
    for (int i = 0; i < nx; i++) {
      float r = float(i) / float(nx);
      float g = float(j) / float(ny);
      float b = 0.2;

      int ir = int(255.99 * r);
      int ig = int(255.99 * g);
      int ib = int(255.99 * b);

      std::cout << ir << " " << ig << " " << ib << "\n";
    }
  }
}

Makefile


CC = g++
CFLAGS = -g
INCFLAGS = -I./


RM = /bin/rm -f
all: ppm-example

ppm-example: ppm-example.o
    $(CC) $(CFLAGS) -o ppm-example ppm-example.o

ppm-example.o: ppm-example.cpp
    $(CC) $(CFLAGS) $(INCFLAGS) -c ppm-example.cpp

clean:
    $(RM) *.o ppm-example *.ppm

run: ppm-example
    ./ppm-example > ppm-example.ppm

Simple Image Output with Javascript and 2D Canvas

The Javascript code below is almost identical with the C++ code but instead of a PPM file stream it renders each pixel in a 200x100 canvas element.

To make sure the color gradient stays in the same direction as the PPM I reversed the y-direction in the fillRect() call.

var example1 = {
  main: function() {
    var c = document.getElementById('example1');
    var ctx = c.getContext('2d');
        
    var nx = 200;
    var ny = 100;
    for (var j = ny-1; j >= 0; j--) {
      for (var i = 0; i < nx; i++) {
        var r = i / nx;
        var g = j / ny;
        var b = 0.2;
        var ir = parseInt(255.99 * r);
        var ig = parseInt(255.99 * g);
        var ib = parseInt(255.99 * b);

        ctx.fillStyle = 'rgb('+ir+','+ig+','+ib+')';
        ctx.fillRect(i, ny-j, 1, 1);
      }
    }
  }
};
window.onload = function() { example1.main(); };

Other Image Formats with stb_image.h

I love the simplicity of the PPM format, but other image formats can be written pretty easily using the stb_image.h library (git clone https://github.com/nothings/stb.git).

Here’s a sample in C++:

stb-example.cpp

#include <iostream>

#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define STB_IMAGE_WRITE_IMPLEMENTATION
#include "stb_image_write.h"

int main() {
  int x, y, n;
  // unsigned char *data = stbi_load("foo.png", &x, &y, &n, 0);
  unsigned char *data = NULL;
  const char* filename = "./stb-example.png";

  int nx = 200;
  int ny = 100;

  data = (unsigned char*) malloc(nx * ny * 3);
  if (data == NULL) {
    printf("Could not alloc image buffer.\n");
    return 0;
  } 

  for (int j = ny-1; j >= 0; j--) {
    for (int i = 0; i < nx; i++) {
      float r = float(i) / float(nx);
      float g = float(j) / float(ny);
      float b = 0.2;

      int ir = int(255.99 * r);
      int ig = int(255.99 * g);
      int ib = int(255.99 * b);

      data[((ny-j-1)*nx+i)*3+0] = ir;
      data[((ny-j-1)*nx+i)*3+1] = ig;
      data[((ny-j-1)*nx+i)*3+2] = ib;
    }
  }

  stbi_write_png(filename, nx, ny, 3, data, 0);
}

Makefile


CC = g++
CFLAGS = -g
INCFLAGS = -I./ -I./stb/


RM = /bin/rm -f
all: stb-example

stb-example: stb-example.o
    $(CC) $(CFLAGS) -o stb-example stb-example.o

stb-example.o: stb-example.cpp
    $(CC) $(CFLAGS) $(INCFLAGS) -c stb-example.cpp

clean:
    $(RM) *.o stb-example *.png

run: stb-example
    ./stb-example

Further Reading