Ray Tracing in a Weekend Part 3 - The Ray Class


The Ray Class

In part 2 I added a vec3 class to help with 3-dimensional vector calculations.

This chapter moves it one step further by introducing a new ray class. A ray can be thought of as a function

\[ p(t) = A + t * B \]

where p is a 3D position along a line, A is the ray origin, B is the ray direction, and t is the ray parameter which moves p(t) along the ray. If t is positive you get the points in front of the origin A, also called a half-line or half-ray.

I’ll only include new and modified parts of the code. Please check my git-repository at https://github.com/celeph/ray-tracing-in-a-weekend for complete example code with makefiles.

The ray class is a literal implementation of the equation in 3D vectors:

ray.h

#ifndef RAYH
#define RAYH
#include "vec3.h"

class ray {
public:
  vec3 A;
  vec3 B;

  ray() {}
  ray(const vec3& a, const vec3& b) { A = a; B = b; }

  vec3 origin() const { return A; }
  vec3 direction() const { return B; }
  vec3 point_at_parameter(float t) const { return A + t * B; }
};
#endif

The new loop moves the ray across the entire image and determines a color at each pixel. The color function only returns a gradient based on the y-position at this time.

The size of the image is set to 4 x 2, where the origin is set into the center of image (0,0,0), the lower left corner is at (-2,-1,-1), the upper right corner is at (2,1,-1). The z-coordinate is set at -1 into the screen to respect the convention of a right-handed coordinate system. The screen is 1 unit apart from the origin (eye or camera). Positive values for z are behind the origin.

ray-example.cpp

#include <iostream>
#include "vec3.h"
#include "ray.h"

vec3 color(const ray& r) {
  vec3 unit_direction = unit_vector(r.direction());
  float t = 0.5f * (unit_direction.y() + 1.0f);
  return (1.0f - t) * vec3(1.0f, 1.0f, 1.0f) + t * vec3(0.5f, 0.7f, 1.0f);
}

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

  std::cout << "P3\n" << nx << " " << ny << "\n255\n";
  
  vec3 lower_left_corner(-2.0, -1.0, -1.0);
  vec3 horizontal(4.0, 0.0, 0.0);
  vec3 vertical(0.0, 2.0, 0.0);
  vec3 origin(0.0, 0.0, 0.0);

  for (int j = ny-1; j >= 0; j--) {
    for (int i = 0; i < nx; i++) {
      float u = float(i) / float(nx);
      float v = float(j) / float(ny);

      ray r(origin, lower_left_corner + u * horizontal + v * vertical);
      vec3 col = color(r);

      int ir = int(255.99 * col[0]);
      int ig = int(255.99 * col[1]);
      int ib = int(255.99 * col[2]);

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

Javascript and 2D Canvas

Here’s also the same example in Javascript.

class ray {
  constructor(a,b) { this.A = a; this.B = b; }

  origin() { return this.A; }
  direction() { return this.B; }
  point_at_parameter(t) { return A + t * B; }
}

var example = {
  color: function(r) {
    var unit_direction = r.direction().unit_vector();
    var t = 0.5 * (unit_direction.y + 1.0);
    return (new vec3(1.0, 1.0, 1.0))
      .mul(1.0 - t)
      .add( 
        (new vec3(0.5, 0.7, 1.0)).mul(t) 
      );
  },
  main: function() {
    var c = document.getElementById('example');
    var ctx = c.getContext('2d');
        
    var nx = 200;
    var ny = 100;

    var lower_left_corner = new vec3(-2.0, -1.0, -1.0);
    var horizontal = new vec3(4.0, 0.0, 0.0);
    var vertical = new vec3(0.0, 2.0, 0.0);
    var origin = new vec3(0.0, 0.0, 0.0);

    for (var j = ny-1; j >= 0; j--) {
      for (var i = 0; i < nx; i++) {
        var u = parseFloat(i) / parseFloat(nx);
        var v = parseFloat(j) / parseFloat(ny);

        var r = new ray(origin, lower_left_corner.add(horizontal.mul(u)).add(vertical.mul(v)));
        var col = this.color(r);
        col = col.mul(255.99);
        
        ctx.fillStyle = 'rgb('+parseInt(col.r)+','+parseInt(col.g)+','+parseInt(col.b)+')';
        ctx.fillRect(i, ny-j, 1, 1);
      }
    }
  }
};
window.onload = function() { example.main(); };