The Ray Tracer Challenge
A major goal for me in programming has been writing my own ray tracer. As I have been learning programing, I’ve also been watching video and reading graphic programming post here and there. I came across a few post about this book called The Ray Tracer Challenge (Amazon Link: https://www.amazon.com/Ray-Tracer-Challenge-Test-Driven-Renderer/dp/1680502719) that walks you creating a ray tracer. A lot of graphics programming material is taught using C++, which has been major barrier to me. The great thing about this book is it teaches the theory of ray tracing using test cases and pieces of pseudocode, and lets you work with whichever programming language you are most comfortable with.
I decided to work in C#, since I just finished learning the basics. Also, it is a C-based language, which means it will be suitable to the task. Hopefully I’ll be able to apply my understanding to writing programs write C++, when the time comes.
Unfortunately, I have let a few weeks pass since between last working on the project and writing this post. In that time some the details of the project have faded. I am hoping writing this will refresh my understanding while at the same time providing some insight.
This is project is still a work in progress, but you can still check out what I got so far on my GitHub
GitHub Link: https://github.com/zackthomas1/RayTracerChallenge
Chapters 1 – 2: Setting up Tuples, Points and Vectors
In these opening sections, I start creating class structures that are going to be the back bone of the entire program. Starting with the tuple class.
The tuple class works as an abstract class that the vector and the point classes derive from. It holds 4 float values and methods to apply operations such as addition and multiplication.
Derived from the tuple class are the vector and point class are very similar. Points and vectors will be used constantly to determine intersections and other crucial functions. The main different is the value of the ‘w’ variable. While the two class both have (x,y,z) values that can change the vector class’s ‘w’ value will always equal 0 and the point class’s will always equal 1. This seems arbitrary at first, but later these ‘w’ values will allow for useful behavior, such as subtracting a point from a point resulting in a vector.
Once I got the point and tuple class working I then set up a class called “Save” that allows you write an image to file. The book had me use an image type called PPM. Color data is written as an integer value between 0 and 255 for each pixel’s red, green, and blue value. Its a very friendly format for programming.
At the end of chapter 2 I create a test program that simulates the path of a projectile. You input a start point and a velocity vector, along with vectors for gravity and wind vectors and the program calculates the trajectory. Cool!
Chapters 3-4: Matrices and Transforms
In order to be able to transform points and vectors we utilize matrices math.
Chapter 3 was probably the most complex in the book because it involved a lot of fundamental math. The chapter goal is to be able to find the inverse of a 4×4 matrix. However, to find this I have to find the determinate and cofactor which involves finding submatrices, which resulted in having to create both a 3×3 and 2×2 matrix class. All of this ended up in a lot of work just to find the inverse.
After implementing the Matrix4 class in chapter 3, chapter 4 is about creating a transformation matrix. The transformation matrix is a 4×4 matrix that holds all transformations (translate, rotate, and scale). It is really useful because it allows you to apply multiple transformations at once. Implementing translate and scale were straight forward. However, working on the rotations involved a lot of trigonometry.
Once I created methods for each of the different transforms, the challenge for the chapter was to rotate a point around a center axis using transformation matrices. I ended with the image you see above.
Chapter 5: Ray-Sphere Intersection
This is when I start getting into the fun stuff, actually calculating ray-object intersections and using that information to render an object.
I start by creating a “Ray” class. Ray is simply a vector with an origin. Since, the vector and point classes have already been set up it is a simple mater of defining a ray as a point and vector.
Afterwards, the next task is to set up a new abstract class called “RayObject”. All future objects such as planes, spheres, cubes, and cones are going to be derived from the RayObject class. Later this class will hold lots of import information about the object such as the material, transparency, transformations, and abstract normal and intersection methods, but for now it is bare bones.
Next, I create a Sphere class; this will be our first primitive geometry. To define geometry we must be able to find where a ray intersects it. To find the intersection point on a sphere we calculate the discriminant. If it is greater that 0 the ray intersects the sphere at two points and less than zero it misses.
To find where the hit occurs look at the t-values. The t-vales represents a point along a ray. Basically, how long one travels along a ray to get to a point. The hit is the closer of the two intersections to the origin, aka the camera position. This means that the intersection with the lesser t-value is where the ray hits the sphere.
Armed with this new knowledge I create my first render! This is a really exciting point for me. By casting a ray for every pixel on a canvas plane I am able to render a flat shaded sphere by coloring a pixel red if an intersections occurs and black if the pixel’s ray misses.
These two sections probably contain the most important fundamental theory. At this point you technically have a ray traced render. From here the rest of the book is about improving the functionality and getting more realistic renders.
Chapter 6: Phong Lighting Model
At the end of the last chapter I was able to determine if a ray hit a sphere and render a flat shaded image. However, I could not render 3D image, which is the goal. To render a 3D image I must implement a lighting model. This model will determine what color to shade a pixel when a hit occurs based on lighting and material properties. This book uses the Phong material model, which is an older non-physically accurate model, but is simple and good for beginners.
To determine the surface color a Phong shader requires four input parameter vectors. The eye vector which is the direction from the intersection point to the origin of the ray, aka the camera. The light vector which is the direction from the intersection point to the scene light. The surface normal, which a vector perpendicular to the surface representing the surface direction. Finally, the reflection vector, which is the light vector mirror across the normal.
Using these four inputs you calculate three color values representing the ambient color, diffuse color, and specular color. Ambient color is simply the material color multiplied with a chosen intensity. Diffuse color, like the ambient, multiplies the material color with a chosen intensity. However, it also takes in the angle between the light source and surface normal so that surfaces directly facing the light are illuminated more. Finally, specular color is the result of multiplying light intensity with a chosen material specular value and a shininess factor, which is based on the angle between the reflection vector and the eye vector. The closer to 0 degrees the angle is the stronger the specular highlight is. If the angle is greater than 90 degrees than there is no specular.
ambient Diffuse specular
A tricky part of this section is finding the normal for a object that has been transformed. Scaling an object non-uniformly will result in normal vectors that are not perpendicular to the surface. To solve this the book introduces a really non-intuitive concept. Basically, on conceptual level it does not mater whether the object transformed or if the incoming ray is transformed. For example, there is no difference between the object moving two feet away from the viewer or the viewer moving two feet closer to the object. This introduces the concept of object and world space. There is a lot to say about this topic which I won’t get into here. However, by applying the transpose of the inversed object transformation matrix on the incoming ray we make it appear objects have been transformed with affecting the normals.
Chapter 7: Organizing with Scene and Camera Classes
Now that everything is working technically its time for a bit of house keeping. As I experienced at the end of the last chapter, creating a scene with an object and then rendering that object to a file has a lot of steps that can be abstracted away into simpler method calls. That’s where the camera and scene structure come into play. We can encapsulate objects and lights together and along with all the methods that operate on them.
I am not going to into to many specific here since this chapter does not deal with concepts of ray tracing and instead is focused on software architecture. However, to give a quick synopses, the scene class holds all the geometry objects and lights that will be rendered along with some methods that operate on those objects. The camera class will hold an abstract representation of our view. There are attributes such as field of view, orientation, and position, which people familiarly associate with cameras. The scene class is passed into the render method within the camera class that outputs the final image.
Chapter 8: Shadows
This chapter is all about creating shadows. The ray tracer computes shadows by casting a shadow ray from each intersection to the light source. If this ray intersects something before it reaches the light then the point is in shadow and only the ambient color should contribute to the lighting. The algorithm for this takes place in four steps. First, measure the distance from the point to the light source. Second, create a ray from the point towards the light source. Third, intersect this ray with the scene. Finally, check if there was a hit that was less than the distance to the light. After some tweaking to accommodate this new shadow test I got the image below.
This doesn’t look very good. The issue seen here is known as shadow acne. This is a result of a floating point precision error. Some of the points are getting created beneath the surface of objects resulting with them intersecting the inside of the object and returning a shadow when it should be fully lit. The solution for this is just to move the point a little bit away from the surface. Once I fixed the shadow acne I got the image at the top of this section.
Chapters 9 -10: Creating Planes, Patterns
Chapter 9 I create a second primitive geometry object, the plane.
Demonstrates patterned textures
Here you just use the RayObject class and LocalIntersections and GetNormal methods that you previously defined for the Sphere to define a plane. Then you use the planes to define a cube. This cube class will really come in use later when you start to define bounding boxes in chapter 13 to improve the efficiency of your program.
Chapter 11: Reflections and Refractions
Reflections and refractions Reflection with one bounce Reflection with multiple bounces. Allowing for reflections in reflections Fresnel Effect Transparent object doesn’t produce shadows. Allows for more realistic Fresnel effect
This was personally the section that I was most excited for.
Added more types of primatives
I for some reason really struggled with defining the intersections for a cylinder. I also started to see weird bugs with my cube class when the camera was in certain positions. My suspension is that when the camera is placed just right that the Ray tracer has difficulty determining if it is hitting the top of the cube or the front and as a result can’t define the edge of the cube and lets the top edge of the cube go off to infinity. Also the Fresnel effect seemed to be not acting correctly in the sphere.
Final Thoughts
I only have a few chapters remaining that will have you create a Group class, define intersections for triangles so that you can import objects from .obj files, and finally create Constructive Solid Geometry. While I have been surprised by my ability to work through the book so far, these last few chapters seem like they may be above my skill level at the moment.
I am hopeful that I can complete all the chapters and eventually all the suggested features, such as area lights and anti-aliasing, at the end of the book. However, the idea of this project was to learn as much as I could about ray tracing. I always suspected that I was reaching past my skill level with my project.
At this point I am considering hanging up this project and moving on. I have a strong a desire to learn C++ since that is major skill to have if one is interested in computer graphics. Then after that I can move on to reading Ray Tracer In One Weekend, which can provide a different prospective on Ray Tracing. Sometimes I find it is best for the purpose of learning to move on from a problem instead of getting hype focused and obsessed with a single problem. I’ve spent about a month working on this and I feel like any longer would be counter productive to my learning. Hopefully, you can then pick up some more knowledge that will help you more effectively understand the problem.
Anyways not to leave things on a negative note I really do feel like I have gained a lot from this project. I have become a lot more comfortable with