This is a path tracer I wrote from scratch in C++ for my Advanced Modelling, Rendering and Animation assignment.
I used SDL to present the pixels to the user and I based the code in a ray tracer I wrote some months ago (here is the post). You can download the code (svn://quasar.dnsalias.com/eleni/path_tracer/), the report, the Linux 32 bit binary, the Linux 64 bit binary, the Win32 executable, the MacOSX binary and the README file with instructions on how to run the program. Below are some shots and explanations:
Requirement 1: Many rays per pixels using stratification and jittering:
The pixel is divided recursively to 4 subpixels according to the desired number of rays per pixel (this number is passed as a parameter and is rounded to a power of four). Then the center of each sub-pixel is randomly moved in an area between the boundaries of the sub-pixel. Below, we can see some images produced for different numbers of pixels:
32 and 64 rays per pixel respectively
Requirement 2: Use of spherical, triangular & quadrilateral area light sources:
The path tracer (that was still a Monte Carlo ray tracer in this stage) supports spheres, sphere flakes, planes, meshes of quads and meshes of triangles. From those objects, the spheres, the triangle – meshes and the quad – meshes can be used also as area lights. Instead of sending a ray to a point light source and test if our object of interest is in shadow or not, now we use a random point in the surface of the area light. In case of the sphere area lights, points are generated in the bounding cube until one is found to be inside the sphere (rejection sampling). In case of the triangular meshes, a triangle of the mesh is chosen randomly. Then, a point inside this triangle is chosen by calculating random α, β and γ barycentric coordinates between 0 and 1. If α, β and γ have sum greater than one we reject the point (rejection sampling) otherwise we calculate its Cartesian coordinates. In case of quadrilateral meshes, a quad of the mesh is chosen at random and then a random point in that quad is generated. In order to generate a random point in a quad, a random linear combination of 2 vectors of 2 consecutive edges of the quad is used. In the following 3 pictures a scene is lit firstly by using a sphere area light, then by using a quad area light and then using a triangular mesh (teapot) area light:
Requirement 3: Sampling the Lambertian and the Phong BRDFs:
In case of specular surfaces the reflected ray directions form a lobe whereas in case of diffuse surfaces they are scattered in every direction in the hemisphere above the surface. In each case, a random vector was chosen inside the lobe or the hemisphere respectively and the probability to continue tracing or stop was calculated by evaluating the BRDF. In the pictures below, we can see the difference in the reflections of a sphere when sampling the Phong BRDF with various (decremented) specular exponents. We can observe that as the specular exponent is decreased the cone of directions is getting larger:
Requirement 4: BRDF evaluation:
The formulas for the Phong and Lambertian models were used to evaluate the BRDF in case of specular and diffuse surfaces respectively. Case of diffuse interaction: . Case of specular interaction: .
Requirement 5: Put it all together to form paths that sample all the integrals:
The path tracer sends many rays per pixel using stratification and jittering. Then, it traces each ray by calling recursively the shade function. In this function it samples each BRDF to decide if the tracing will continue or stop after a specific intersection.
Requirement 6: Russian roulette (unbiased path termination):
The method of Russian roulette was used to implement importance sampling. First, we decide if the next interaction will be diffuse or specular and second, we decide if the tracing will continue or not (unbiased path termination). In the first case, a random number was chosen between 1 and sum of the means of diffuse kd and the specular ks and if it was larger than ks the next interaction was diffuse else it was specular.
In the second case (unbiased path termination), the dot product of the randomly generated vector inside the lobe or the hemisphere (see previous requirement) with the normal of the surface was calculated and compared to the probability evaluated by the BRDF. If it was less or equal to the BRDF value the tracing continued otherwise it stopped.
Requirement 7: Scene creation:
Different scenes were created 🙂 Here are some of them:
Skylight images: a huge hemisphere was used as area light sources to give the impression of skylight. The following scenes were rendered:
Cornell Box: The Cornell Box was rendered with different numbers of rays per pixel:
I added also some features to improve the result… e.g. I used gamma-correction to improve the appearance of the colors and a shell script that transforms .OBJ scenes to my scene format to avoid extra pain… 😛 (the teapot for example was generated using a low-polygon version of the Utah teapot…)