An introduction to ray tracing

Contents


What is ray tracing?

Ray tracing is a method of generating images by 'tracing' or following light rays as they are reflected and transmitted around a scene. By tracing rays, you can generate realistic reflections and simulate realistic shading.
In practice, it would be too time consuming to send all of the rays from the light sources, and then see if they enter the
eye

How is it done?

Tracing rays and calculating the colors for every pixel requires the use of vector math, and quite a bit of processing power. To provide a little background for one of the vector equations required, here is the vector equation for a line:
p = t * (p1 - p0) + p0
or
p = t * v + p0

Where p1 and p0 are two points that define the vector direction of the line, v is the vector defined by p1 - p0, and t is a scalar value that makes the point p move along the line (When t is 0, the point p will be located at p0, and when t is 1, p will be located at p1).

This vector equation of the line is the basis for ray tracing, as all of the rays are straight lines. To generate the rays, an
eye point is chosen, and for a ray is sent out from the eye through each of the on-screen pixels, to see if each intersects with any objects.

Note: The intersection test must be performed with many objects (possibly all objects in the scene), to find the closest intersection point to the 'eye' (as any intersections behind it are blocked).

A simple object intersection test

One of the easiest objects to calculate intersections for is the sphere. To calculate the intersection, you use the vector equation of the line in the vector equation for a sphere, and solve the quadratic equation for the roots (the intersection points, in terms of 't').
Here is the vector equation of a sphere:
(p - C) . (p - C) - R2 = 0

Where p is any point on the surface, C is the center of the sphere, R is the radius of the sphere, and '.' means dot product.

Using the vector equation of the line in the equation for a sphere gives:
((t * v + p0) - C) . ((t * v + p0) - C) - R2 = 0

Since we want to solve for t, we need to regroup the equation as follows:
(t * v + (p0 - C)) . (t * v + (p0 - C)) - R2 = 0

Expanding the equation and grouping terms gives the following:
t2 * (v . v) + 2 * t * (v . (p0 - C)) + ((p0 - C) . (p0 - C) - R2) = 0

From here, we can apply the quadratic equation to solve for the roots (t):
(-b +/- (b2 - 4 * a * c)0.5) / (2 * a)

Where we use a = (v . v), b = (2 * (v . (p0 - C))), and c = ((p0 - C) . (p0 - C) - R2).

To do a quick intersection test (to see if there are any non-imaginary intersections with the sphere), we check to see if (b2 - 4 * a * c) is less than epsilon. Where epsilon is some low tolerance number (see
floating point imprecisions for reasons why).

Color modeling

All colors of light can be defined with amounts of red, green, and blue components. Because of this, all calculations done for coloring will be done with an RGB triplet (red, green, blue). Multiple colors will need to be added up to calculate the color values for any given point (or pixel); if, when the colors are added up, any of them are beyond the maximum intensity, they should clipped to (cut off at) the maximum intensity.
For my purposes, I use floating point numbers in the range of 0 to 1 to represent any of the values in the RGB triplet (this is fairly standard in graphics). If any of the values calculated are below 0 or above 1, they should be set to 0 or 1 respectively.

A simple shading model

The amount of illumination on an object depends on the angle of the object with respect to the light rays coming in contact with it.

This allows us to do some very simple shading (known as
diffuse shading because of the type of lighting it models) using only the normal of the surface (at the point of intersection), and the 'light' vector (the vector from the point of intersection to the light source that is contributing to the illumination).

Since the angle is important, all we need to do is take the dot product of the 'light' vector (normalized) and the normal of the surface (also normalized). This will result in a value between -1 and 1. If the value is less than 0 (or epsilon), then the light would be illuminating the back side of the surface. For most purposes, the back side of the surface should not be visible, so the value should be set to 0.

Given the scalar value (now between 0 and 1), we can just multiply this by the RGB triplet that defines the light color. This gives the 'intensities' of the RGB components of the light hitting the surface. To calculate the color reflected from the surface, we just multiply this value by the 'diffuse' color of the surface, resulting in the color 'reflected' from that point of the object.
Note: Any given light may or may not contribute to the illumination of the point, so a shadow ray must be sent first to determine if the given light source contributes.

Shadow calculations

Floating point imprecisions

Floating point calculations have a finite amount of precision. Because of this, problems enter with 'round-off' errors, and values not exactly equaling zero (numbers which should be zero could actually be stored as some very small number). Thus, any compares of floating point values should be done with a small 'epsilon' value, instead of zero. This will eliminate some of the errors generated by using finite precision numbers. Good values for epsilon depend on both the floating point format used for the given platform and the 'size' of the data being used (it should be much smaller than the radius of any sphere, or the distance between any objects).

Floating point imprecisions will also cause artifacts in ray traced images when sending 'shadow rays', reflected rays, or transmitted rays. This problem arises when the ray that leaves the surface of the object intersects with the object at the same point (or very close to the same point) from where the new ray originated. This is due to the same problem with finite precision (discussed above), and can cause artifacts that look like small holes or speckles.

To prevent this problem, every ray which leaves the surface of an object should have the 'origin' translated by the epsilon value, in the direction of the ray. That is:
new origin = epsilon * direction + origin

Using this this method should eliminate holes and speckles in the ray traced image.

Glossary

1