Ray Tracer§

The above demo is the ray tracer running in real time.


Coordinate System§

In order to generate the orthonormal bases for the camera coordinate system, we require the following points given in world coordinates:

  • $E(x, y, z)$ - the camera position,
  • $L(x, y, z)$ - the point that the camera will look towards,
  • $\vec{u}$ - the vector denoting the upward direction for the camera.

Then, the bases for camera coordinates are given by

$$ \begin{align*} \vec{w} &= ||E - L||,\\ \vec{u} &= ||\vec{u} \times \vec{w}||,\\ \vec{v} &= \vec{w} \times \vec{u}. \end{align*} $$

We say that $\vec{u}$ is the “up” direction and $\vec{v}$ is the “right” direction.

Focal Plane§

In order to generate the focal plane, we require the two additional parameters:

  • $\theta_y$ - the vertical component of the field of view,
  • $f$ - the focal distance of the camera.

The part of the focal plane within the viewing frustum has a height $H$ given by

$$ H = 2f \tan\left(\theta_y\right). $$

In ray tracing we wish to subdivide the viewable focal plane into pixels in a one-to-one correspondance with the viewport. Here, we make the assumption that the pixels of the viewport are uniform squares, and we let $V_w$ and $V_h$ denote the width and height of the viewport in pixels, respectively.

The pixel width is given by

$$ p_w = H / V_h. $$

From this, we can get the width of the viewable portion of the focal plane by multiplying the pixel width by viewport width in pixels,

$$ W = p_w \cdot V_w. $$

Let $I_c$ denote the middle of the focal plane,

$$ I_c = E + f\cdot||L - E||. $$

The origin of the focal plane, or its bottom left corner, is given by

$$ \mathcal{O} = I_c - \frac{f}{2}\left(\vec{u} + \vec{v}\right). $$

Finally, we can represent the centre of a pixel by

$$ I(x, y) = \mathcal{O} + \vec{u}\cdot(x + 0.5) + \vec{v}\cdot(y + 0.5). $$


Rather than emitting a ray from a light source and hoping that it will hit our focal plane, we can instead emit a ray from each pixel and see how it interacts with the scene to get its color; i.e. we trace the ray backwards. This is a simple but very powerful optimization.

A ray can be written as

$$ \vec{R}(t) = \mathfrak{O} + \vec{d}\cdot t, $$

where $\mathfrak{O}$ is the origin of the ray and $\vec{d}$ is a vector denoting the direction of the ray; here it is helpful to think of $t$ as time.

A primary ray is a ray that is emitted from the pixel, it can be given by the function

$$ \vec{R}(x, y, t) = I(x, y) + \vec{d}\cdot t, $$

where $\vec{d}$ is the direction of the ray given by

$$ \vec{d} = ||I(x, y) - E||. $$

Ray-Object Intersection§

To get the color of a ray, we must detect if it hits off an object in the scene.

We use the Hit(ray, min, max) function on the scene to find the first intersection the ray and the scene, where

  • ray is a ray given by $\vec{R}(t) = \mathfrak{O} + \vec{d} \cdot t$,
  • min is the minimum $t$ value in the ray equation,
  • max is the maximum $t$ value.

The min and max restrict us to a certain line segment along the path of ray this is useful for objects that are not transmittant, i.e. light does not pass through the object (we will not consider transmittance for this ray tracer).

The Hit function returns a Record object which holds the material properties ($k_a, k_d, k_s, k_m$), the time, point, and normal at the intersection between the ray and surface.

A given ray need not intersect an object, in this case we return an empty record. Otherwise, a ray can intersect an object multiple times given its geometry, as such we want to get the record at the smallest time value between min and max.

Hit(ray, min, max)
    time = max

    for object in scene
        # Find time of intersection of this ray with this particular object
        intersection = object.Intersect(ray, min, max)

        # Keep record of this object if intersection time is
        # greater than min but smaller than current time
        if intersection > min and intersection < time
            time = intersection
            point = ray.origin + ray.direction * time

            record = Record(
                material = object.material,
                time = time,
                point = point,
                normal = object.NormalAtPoint(point)

    if time != max
        return record

    return EmptyRecord


To compute the color of the ray, we use a recursive function ComputeColor(ray, min, max, depth), where ray, min, max mean the same as they do in Hit() and depth is the maximum recursive depth.

Because light rays can theoretically reflect infinitely many times, we want to terminate the recursion after a certain number of reflections.

to be completed.