In this assignment, you will implement path tracing to create photorealistic renderings of 3D scenes with global illumination. No starter code will be provided. Like in Assignment 1, you will compute the rendered image yourself on the CPU and then display it — only this time, you will do it using path tracing instead of rasterization.
Start by implementing a very basic ray tracer. For now, define a scene to be a list of spheres with specified centers and radii (we will change this later). Assume the camera is located at the origin and pointed along the −z direction with a fixed vertical angle of view (choose something between 40° and 90°), so that you can compute the ray o + t d for any pixel location. Intersect the ray with the scene, find the normal n of the nearest hit point, and set the pixel colour to be ½ (n + (1,1,1)), or (0,0,0) if nothing was hit. Even without doing any lighting calculations, this will allow you to see the shape of the objects in the scene. (Once you’ve computed the entire image, you will of course have to open a window to display it, and/or save it to a file.)
Modify your scene representation to allow other shapes to be included, namely, infinite planes and axis-aligned boxes. (You can also implement other shapes if you want.) A natural way to do this is to make all these shapes subclasses of an abstract “shape” class. Each concrete shape should provide its own ray intersection function that returns the nearest valid intersection of the given ray with it. Replace the second sphere with the plane y = −1 and add some boxes to the scene to verify that your new shapes are working correctly.
Add some lighting. Modify the scene representation to also have a list of light sources (assume point sources with specified intensity in RGB for now), and give each shape a diffuse albedo as an RGB triple. For each pixel, set the colour to be the radiance going towards the camera based on the Lambertian model, adding up the contribution of each visible light source.
Optional: (Correction: not optional) Add a viewing transformation that allows you to move and rotate the camera. As discussed in one of the earlier lectures, you can do this by specifying the center of projection, the view vector, and the up vector, and using them to construct an affine transformation matrix. Also add affine transformation matrices to each shape so that you can move, rotate, and scale them.
If you have been directly setting the RGB colour of the pixel to the computed radiance values, this is incorrect! The computed radiance is in “linear RGB” i.e. proportional to the amount of light energy. To encode it in values which will be displayed correctly, use the sRGB gamma-correction formula discussed in lecture 21.
Extend your ray tracer to support specular reflection and refraction, from perfectly smooth metallic and transparent surfaces respectively. You may want to implement an abstract “material” class of which all these are subclasses. Each time you hit such a surface, shoot one or two secondary ray(s) to get the incident radiance from the reflection/transmission direction (again with bias of course). Choose a maximum recursion depth so your renderer doesn’t get stuck in an infinite loop, and a sky colour for rays that go to infinity.
Finally, add path tracing to your renderer! You are only required to do the simpler version without direct/indirect splitting; just shoot at most one ray from each bounce to sample the incident radiance, and forget about light sampling or shadow rays. Add an outer loop where you trace multiple paths for each pixel and average the results.
Optional: Add direct/indirect splitting of the incident radiance, as discussed in class. This should make diffuse surfaces much less noisy. However, you will have to figure out how to turn it off for specular (metallic and transparent) surfaces, otherwise reflections and refractions of light sources may no longer work!
Set up a scene that shows off the features of your path tracer. It should demonstrate all three types of shapes, affine transformations, soft shadows, recursive reflection and refraction, as well as indirect illumination and caustics (which come for free with a path tracer). You should also put a little effort into the choice of colour scheme, lighting, and viewpoint to make it aesthetically pleasing.
Submit your assignment on Moodle before midnight on the due date. Your submission should be a zip file that contains your entire source code for the assignment, not including any binary object files or executables. The zip file should also contain a PDF report that includes various images produced by your renderer:
If there are any components of the assignment you could not fully implement, in the PDF you should also explain which ones they are and what problems you faced in doing them.
Separately, each of you will individually submit a short response in a separate form regarding how much you and your groupmate(s) contributed to the work in this assignment.