In this class, you will use the DIRT (Dartmouth Introductory Ray Tracer) framework to complete assignments. Dirt is a small collection of code files that allow you to easily deal with vectors and matrices, images, input/output and more. For the next assignments, we will provide an updated version of the Dirt base code with new tasks to complete.
In this assignment, you will learn the basics of ray tracing by implementing different components of a simple ray tracer. This assignment comes with multiple subtasks. Please make sure to start early.
You can obtain the basecode for this assignment by using the following git command:
$ git clone https://gitlab.cs.dartmouth.edu/cs77-fa17/dirt_basecode.git
You can also use a visual tool like SourceTree to clone the repository; the URL will be the same as in the command above.
Setting up a C++ compiler and building the base code
Linux / Mac OS X
Begin by installing the CMake build system on your system. On Mac OS X, you will also need to install a reasonably up-to-date version of XCode along with the command line tools. On Linux, any reasonably recent version of GCC or Clang will work. Navigate to the basecode folder, create a build directory and start cmake-gui, like so:
$ cd path-to-dirt
$ mkdir build
$ cd build
$ cmake-gui ..
Windows / Visual Studio 2015
Begin by installing Visual Studio 2015 and a reasonably recent (≥ 3.x) version of CMake. For "Where is the source code", enter the root folder of the base code (the folder containing CMakeLists.txt). Create a subfolder called build inside the base code folder and enter its path in "Where to build the binaries". Hit Configure and proceed through the screens as shown below. After configuring is complete, hit Generate. After generating completes, you should find a .sln file inside the build folder you created earlier. Double-click it to open it with Visual Studio.
Pressing Ctrl+Shift+B will compile the base code and the assignments. The resulting executables are placed in the Release or Debug directories of the build folder. Unless you are tracking down a bug, make sure to select the Release configuration at the top of the screen; it runs several magnitudes faster than the debug version.
You can check your answers for many subtasks in this assignment using 01_testXXX.cpp, which are three programs that test example input and expected output data. These tests are provided to help you find silly mistakes; they are not automatic grading. These files can only test a small subset of inputs and cannot fully verify your solution. A Result correct! messsage does not guarantee you will receive full points.
Also provided in this assignment is a command line raytracing program, 01_raytrace.cpp. Take a look inside the scenes/01_raytrace folder. The 01_raytrace.cpp program can parse the JSON scene files provided with this assignment. At the end of this assignment, if you've implemented everything correctly, you should get the same results as the *_ref.png images.
You can run the raytracer on a scene by running a command like this on the command prompt:
Task 1: Transforms
Take a look at transform.h and implement all of the empty transformation methods (marked with a TODO).
Implement the empty transformation method for transforming points.
Implement the empty transformation method for transforming vectors.
Implement the multiplication operator for Transform * Ray3f.
Implement the empty transformation method for Normal3f. You should look in the textbook to figure out how to properly transform a surface normal.
Check your answers using 01_test1_transform.cpp
Hint: A ray consists of a point and a vector that are transformed separately. You can reuse the methods you implemented for the last assignment.
Task 2: Ray generation
Look at camera.h and implement the missing generateRay function for a perspective camera.
To make verification easier, we provide code to generate a ray image in 01_test2_rayimage.cpp. The code will convert the directions computed by generateRay to RGB colors and save out an image. This image will be saved to scenes/01_raytrace/rayimage.png. If you have implemented generateRay correctly, you should get an image like this:
We provide this image in scenes/01_raytrace/rayimage_ref.png
Hint: In the local coordinate system, the ray always starts at position (0, 0, 0)
Hint: The inputs u/v are in range [0, 1]. In the local coordinate system, the x component of the ray direction should range between [-width/2, width/2] and the y component between [-height/2, height/2].
Hint: Don't forget to transform the ray from the local coordinate system to the global coordinate system. Use the operator you implemented for task 1 to apply the transform.
Task 3: Surface Intersection
Look at sphere.cpp. Implement the missing intersect method. The method should check whether the ray passed to the method intersects the sphere, and fill in the its structure with information about the intersection if it does. Otherwise, you only need to return false.
Use 01_test3_intersection.cpp to check your solution.
Hint: Don't forget to first transform the ray to the local coordinate system of the sphere. Use m_xform.inverse() to obtain a transformation from the global to the local coordinate system.
Hint: If you get stuck, take a look at quad.cpp. We provide an implementation of intersect for this primitive, and you can use it as a reference for how the method should behave and what result values it should compute.
Task 4: Ray tracing
Open scene.cpp and implement the radiance method. This method should call the intersect method you just implemented. If there was an intersection, you should call the shade method of the material that was intersected; otherwise, you should return the background color.
Finally, in scene.cpp, implement the raytrace method. This is the heart of the ray tracer: It will loop through every pixel in the image and shoot one or more rays through it. There are two parts to this task.
Subtask a) Simple raytracing
Loop through every pixel in the image. Use the generateRay method you implemented earlier to generate a single ray through the center of each pixel. Use the radiance method to trace the ray and retrieve the result color.
Run the 01_raytrace program on the 01_sphere.json scene. If you haven't implemented Task 5 yet, you should get an image like this:
This image is provided in scenes/01_raytrace/task_4a_ref.png.
Subtask b) Raytracing with anti-aliasing
Note how the edges of the sphere in the above image display "jaggies" instead of a smooth outline. To solve this, you will implement anti-aliasing in this task.
Same as before, loop through every pixel in the image. Instead of shooting only one ray through each pixel, trace m_imageSamples * m_imageSamples rays; distribute them in a regular grid within each pixel. For m_imageSamples = 1, you should get the same result as before.
Run the 01_raytrace program on the 01_sphere_aa.json scene. If you haven't implemented Task 5 yet, you should get an image like this:
This image is provided in scenes/01_raytrace/task_4b_ref.png.
Compare the two images and note how smooth the outline of the sphere looks now. Much better!
Task 5: Shading
In this task, you will implement one of the core methods of your raytracer: Surface shading. The method you should implement is shade, located in material.cpp.
This is a complex method, which should do multiple things:
- Loop through all lights in the scene and accumulate light from each one. Hint: Use scene.getLights() to get a list of all lights in the scene
- For each light, compute the material response at the shading point, as described in the lecture. Hint: The material response has multiple terms: The light intensity, the inverse squared distance to the light, the dot product of the surface normal and the direction towards the light source, the diffuse color kd of the material, and the Blinn-Phong term times the specular color ks of the material. Hint: Leave out Blinn-Phong at the beginning and only do diffuse shading. Verify that it works correctly by running the 03_plane.json scene and comparing it with the reference. If it is correct, implement Blinn-Phong shading too, and verify it using the 04_balls.json scene. Hint: Don't forget that the dot product can get negative. Use max(dotProduct, 0) to get correct results. Hint: Don't forget to normalize the light direction before doing the dot product
- Implement shadowing. Before computing the material response, check if the light source is visible from the intersection point. To do this, create a new ray (a shadow ray) starting at the shading point, with direction towards the light source. Call the scene intersect method. If the ray intersects an object, there is an occluder between the intersection point and the light source; you should set the material response for that light to zero (since the light is shadowed). Hint: Be very careful with the mint, maxt of the shadow ray. mint should be slightly larger than 0 to avoid shadow acne. maxt should be slightly smaller than the distance to the light source (occluders behind the light source don't affect the lighting).
- After looping through all lights and accumulating the material response for each one, the method should compute the reflection component. Check whether the reflection color kr is zero. If it is not, create a reflection ray. This ray should start at the intersection point like the shadow rays, but its direction should be the direction of the incoming ray reflected by the surface normal, as described in the lecture. Trace the reflection ray using scene.radiance. Hint: Don't forget to multiply the result of scene.radiance by the reflection color.
Task 5a: Refraction
You need to complete this task only if you are taking the class for graduate credit (i.e. COSC177). If you are taking the class for undergraduate credit (i.e. COSC77), you are welcome to solve this task, but don't need to in order to obtain full points.
Implement the refraction component in the material shade method. After computing the reflection component, check whether the refraction color kt is zero. If it is not, create a refraction ray. This ray should start at the intersection point like the reflection ray, but its direction should be computed by Snell's law of refraction, using the refractive index ior. Trace the refraction ray using scene.radiance. Don't forget to multiply the result of scene.radiance by the refraction color.
- A useful resource for computing the refraction ray are Wikipedia (in particular the vector form).
- To compute the refraction ray, you need to compute the ratio of two indices of refraction: The refractive index of air (which is 1), and the refractive index of the material (which is specified by ior). The numerator and denominator of the ratio depend on whether the ray hit the surface from the inside or the outside. If the ray hit the surface from the outside, you should use r = 1/ior; otherwise, you should use r = ior/1.
- You can check the sign of the dot product of the ray direction and the surface normal to determine whether the ray intersected the surface from the inside or the outside
- Don't forget to normalize your vectors before using them in the refraction formula
Verify that your implementation is correct by running the raytracer on 07_refr.json and comparing it to the reference image.
Task 6: Interesting Scene
Make your own interesting scene and submit a rendering (or several). For example, you could plot mathematical shapes using spheres as shown below, do interesting plays with shadows or set up interesting lighting with multiple point lights. Be creative! The best renderings will be shown during the lecture.
Image Credit: Henrik Wann Jensen
What to hand in
You should submit a zip file containing
- The source code with your changes (the entire src folder)
- Images of all provided scenes as produced by your raytracer. After you finish the assignment, run 01_raytracer on all scenes in scenes/01_raytrace, and add the images to the zip.
- An html file named firstname-lastname-report.html using this template. Follow the instructions in the HTML on what to include. Report any problems you've experienced or any further comments you want to add.
Do not submit binaries. Do not include your build folder.