C++ Ray Tracer

This is a ray tracer I wrote in C++ for CS 488. It has several advanced features such as adaptive anti-aliasing, uniform space partitioning and several distributed ray tracing techniques.

Below is a description of each objective for the course project, as well as a few images for each one.

Objective 1: Cylinder and Torus

Torus and cylinder were implemented as extra primitives. A torus is created by specifying the radius of its "tube". Both are set to standard sizes and can then be transformed, as with all other primitives.

Torus intersections are computed as the result of a quartic equation. Cylinder intersections are done by intersecting with an infinite cylinder then checking against the cylinder height.

The second image shows the cylinder primitive on the left, and a basic mesh of a cylinder on the right.

Objective 2: Reflection

Specular reflection was added by recursively casting a ray from the point of intersection with an object. A maximum recursive depth configurable as command-line input is used to terminate.

The final image shows a scene in a box of mirrors with increasing maximum depth. The depth begins with 0 (no reflection) and goes to 6.

Objective 3: Transparency and Refraction

Transparency is also implemented recursively, by casting a ray from the point of intersection with a transparent object. The transmitted ray is perturbed according to snell's law, giving us the refractive effect.

In the second image, the sphere is far enough away from the background that the refraction has inverted the transmitted image.

Objective 4: Texture Mapping

Texture mapping was implemented for individual polygons as well as for spheres. Textures can either be functions (operating on a percentage of the width and height) or a png image.

The last image has the right wall set recursively to the ray traced image itself.

Objective 5: Adaptive Supersampling

Adaptive antialiasing was implemented as sort of two-pass approach. First, rays are cast to each corner of each pixel (just like a basic image with one more row and one more column). Then for each pixel, we check to see if any of the corners of the pixel are significantly different from the others.

If there is too much variation in the corners then the pixel is divided into four new pixels, and antialiasing is performed recursively. The final pixel colour is the average of all its sub-pixels.

The first image has no antialiasing. The second shows which pixels were selected for antialiasing, and the third is the result of antialiasing with maximum depth 3.

Objective 6: Uniform Grid Acceleration

Uniform grid acceleration was implemented by finding the outermost points of any model, and then dividing a cube covering those points into equal portions. It performs best on images with many uniformly spaced models.

The first image took 1 minute and 52 seconds to render without the grid, and 1 minute and 11 seconds to render with the grid.

The second image (spheres arranged in a 20x20x20 grid) took 2 minutes and 34 seconds to render without the grid at a resolution of 256x256. Using the uniform grid, this image rendered in 512x512 resolution in only 2.5 seconds.

Objective 7: Soft shadows

Area lights are represented as spheres by giving them a radius. The area lights were not given a model because the primary goal was soft shadows.

To generate soft shadows, N shadow rays are sent to uniformly sampled points on the surface of the area light. Each shadow ray then contributes equally to the shadow of the point.

The second image uses 20 shadow rays per pixel, and the last image uses 100 shadow rays per pixel.

Objective 8: Phong Shading and Normal Interpolation

Phong shading for triangular meshes was implemented by interpolating normals across vertices. Normals can either be specified in .obj format or can be inferred. A vertex normal is inferred by averaging the normals of all of the faces it is adjacent to.

The first two images show a mesh of a gourd with inferred normals.

The second image shows a mesh of Suzanne with normals specified for each vertex on each face.

Objective 9: Glossy Reflection

Glossy reflection was implemented by casting many perturbed reflective rays. The amount of perturbation is relative to the phong exponent of a material. A very high phong exponent results in a mirror surface, while a lower phong exponent results in a blurry reflection.

The first image shows the floor as a mirror, and the second as a blurry reflective surface. The final image shows three spheres side by side with varying phong exponents.

Objective 10: Unique Scene

My final image shows a room with a magnifying glass looking at an ant. There is also a shiny throne to the right, and a monkey's head on the wall.

The rendering time without antialiasing was about 3 hours using 6 threads on gl11. There are quite a few glossy surfaces reflecting off one another, so the rendering time was greatly increased. Because of this and the amount of texture mapping used, antialiasing this image would take an estimated 30 hours, so that is omitted from the final image.

The magnifying glass was created using a torus, cylinder and a sphere. The sphere was flattened and used for the lens. The throne was created using a series of cylinders and cubes. The ant model was found for free online at turbosquid.com.

Extra: Concurrency

Concurrent processing was implemented as well. The image shows rendering time for a sample image as the number of threads increases (on a 6 core machine). In general, this was able to reduce rendering time by more than 66%.