Through this project I learned how to implement a ray tracer that can perform such tasks as casting eye rays, shadow rays, reflection rays, and transmission rays. The primary ability needed by the ray tracer is the ray cast function, which sends out a given ray p(t) = e + td into the scene and returns the first object intersected by the ray and the time at which the intersection occurs.
I started the project with writing a intersection test on spheres. In order to tell if it works fine, I set the pixel to white color when intersection occurs. After making sure it was right, I drew the pixel by using the returned t value(as in p(t) = e + td) as the red component in color, ex return Color3(t / 50, 0, 0). Results see below(looking from different angle).
After that, things became clear. Write intersection test for every kind of geometry, calculate pixel color according to material on intersected object, normal vector, light direction and so on. Compute the recursive rays to get color from specular reflection and refraction. For dielectrics, Fresnel equations are implemented to approximate the effect.
During the project I ran into several issues which weren’t a big deal but very hard to track down, such as transforming normal vector from object space to world space and the other way around. In the scenes that don’t have scaled object, everything looks pretty, recursive rays work perfect. When I tested with a scene that has scale on objects, horrible things happened – misplaced shadows and highlights everywhere. And I had to check through all the intersection test and recursive rays and color calculations to locate the mistake – transform matrix need special treatment when used on scaled object. The other thing that bugged me for a while and made me wanna hit the wall with my head is the floating point precision. As project deadline approaching, everything rendered fine except for a black circle around the crystal sphere. I looked though the code again and again and again but still couldn’t figure out why.
Then I was bored and change the ray origin in intersection test from ray.origin + ray.direction * 0.0001 to ray.origin + ray.direction * 0.0000001, and the black circle disappeared…YEAH!! But when I changed all the ray origins the same way in reflection and refraction, the black circle came back…so I kept some rays starting from ray.origin + ray.direction * 0.0001 and some rays starting closer…and I was speechless…>_<
Don’t mess up with normal vectors.
Think before using a numeric type in every calculation, how is it going to be used.
Programming Language Used: C++
Input: Several scenes make up of various geometries, a loader, and an OpenGL renderer that approximates the lighting of the scene. Starter code to move the camera and save screenshots.
Output: Produce an image created by raytracing the scene by implementing the Raytracer::raytrace function.
Here’re some screenshots(all have recursive rays depth of 4):
Intersection test on different geometries and diffuse, texture, and reflection color calculation.