Object Lights and Path Tracing

The final assignment is a wrap-up and conclusion for the semester. This iteration involves object lights, spheres, and meshes that are also illuminating. Also, a path tracer with a various set of renderer parameters is another output of the assignment.
Remember that our ray tracers support for spheres and meshes. Now, with a careful and smart selection of samples around these objects, we can sample points on them, just like we were selecting points on the area lights. Therefore an eye-appealing render requires many samples, as expected.


With this diffuse lighting, there is nothing special about this. Actually, this is achievable with what we had so far.


Here is the same scene with glossy objects. 

Now, this area light is quite different from what we had so far. This one is actually a light mesh. In the implementation, I created another class, which extends both Light and Object. Thus, it had to implement an illuminate function, and it also has intersection tests. Here, one has to be careful to check whether a primary ray hits a light object. If that's the case, the radiance of the light should be returned without any attenuation.
To get balanced samples on this light mesh, I calculated the CDF so that triangles with larger areas have a higher chance of selection. The cross product of two vectors that define two edges of the triangle would have length twice as much of its signed area. If you take the absolute value and divide it by two, you would get the positive triangle area. By running another tally of total area, then I divided each area with the total area so that all values are clamped between 0 and 1. By now, we would have a PDF. To convert it to a CDF, we will iterate over that PDF, and by adding each value in each iteration, we can find the CDF. Once CDF is found, we can pick a random number, lookup where it belongs in the CDF, and then sample an arbitrary point on that triangle.


Thus, it is possible to render such area lights that are actually meshes.


Another type of object light is the sphere light. Unlike the triangle counterpart, sphere lights do not require much precalculation. We create an orthonormal basis, and using that orthonormal basis, we sample a point that is visible from the object to the sphere. While the lectures are mainly on sampling directions, up to now, I sampled points on lights; thus, I had to use an intersection to find the light sample on the sphere. 


The spherical lights are quite useful since one can also use transformations and make them ellipsoids:


The subtleties in the implementation revolve around handling the transformations. Other than that, I pretty much followed the lecture notes.

The most fun part of this assignment was implementing a path tracer. The straightforward version is quite noisy, and the good thing is it is quite useful to see whether the noise reduction techniques work. One can compare the baseline method with another feature to observe how the noisy image improves. 


The image above shows the version in which no extra features are applied. The rays will continue to bounce, thus form a path, until they left the scene (no intersection), or the maximum recursion depth is reached.


With importance sampling, we choose the directions of the bounce so that we select ray directions that are likely to have a meaningful contribution. This involves changing the way sampling works so that it will likely to choose samples that are closer to the hit normal. One has to update the probability as well if that is the case: basically, probability of sampling, in this case, is PI/cos (theta), instead of  2PI.


Russian roulette is an unbiased way of terminating the rays. For each ray, we generate a random number and check against the criteria. If the requirements are satisfied, we kill that ray and return zero contribution. I used the angle between the normal and the created bounce direction as the criterion. Judging from the results, I feel that the path's throughput criteria yield better results. Nevertheless, it is still an excellent opportunity to test and implement such a feature.


The next event estimation adds lights' contributions as well. Thus, it is the method that dramatically reduces the noise in the images. I simply added whatever light contributions that I calculated so far to come up with this result, so there is nothing fancy involved. The following are some combinations of next event estimation, Russian roulette, and importance sampling:

Next Event Estimation - Importance Sampling 

Importance Sampling - Russian Roulette

Next Event Estimation - Russian Roulette

Next Event Estimation - Russian Roulette - Importance Sampling

Path tracing with glass object:
Default path tracing

Russian Roulette

Importance Sampling

Importance Sampling - Russian Roulette

Next Event Estimation

Next Event Estimation - Russian Roulette

Next Event Estimation - Importance Sampling - Russian Roulette

Next Event Estimation - Importance Sampling

Discussion
While implementing these features, I stumbled upon some bugs that resulted in wrong renders. Here, I will share them and discuss their causes and remedies.

In this image, the intersection of a primary light with the sphere did not return the radiance of the sphere light. This can happen when sampling points on the sphere and somehow still considering shadow rays. It also happens when you do not return the radiance.

Tonemapping operator completely fails when there are NaN values. One has to be careful, if there is a division around, there is a possibility of dividing by zero.

Failing to correctly weigh the probability of accepting/rejecting a sample in the Russian roulette method yields such biased results. The bias is apparent on the top and bottom parts of the sphere objects.

Keeping a recursion depth independent of the Russian roulette results in problems with dielectric objects.

This scene had a maximum recursion depth of 3. When I prematurely cut the path short due to hitting an object light, I had to increase the recursion depth to 5,6, to get a viable result.

This was the most subtle and the most stubborn one. Here, it is near impossible to figure out what went wrong at first. The image looks grainy as if it is an old postcard. And it does not help to reduce sample count and resolution so that one can track every pixel since everything seems to be working fine regardless. However, when I run the ray tracer in the single-threaded mode, I do not get the grains in the image. Here, the problem is that the random number generation is not thread-safe. I use OpenMP to use multithreading, and it requires a re-entrant random number generator, which I did not have under MSVC++. There are third-party solutions, such as Tina's Random Number Generator. However, they require tedious installation procedures. Instead, I used "#pragme omp critical" directive before I used my random number generators. This effectively made the code single-threaded for the most part, but the images are correct now. Such a hit increased the rendering times, i.e., the glass path tracing scenes required around 200 seconds. Due to some memory leak issues, I could not get past 574 samples per pixel in Veach-Ajar scene. Here's how it looks with that many samples:


After getting rid of the memory leaks, the memory usage went down from a whopping 29 GB to 420MB, which keeps constant over the course of the rendering process. Here's the final result, with 6400 samples per pixel:



Developing a raytracer has been a fun and wild ride, which I enjoyed every moment of. Implementation and debugging of the assignments were not only technically demanding, but they also required an iron will. With all the lessons learned, although I barely scratched the surface in this vast topic, now I feel like I am better equipped to tackle harder problems. I would like to have this moment to thank our excellent instructor, Assoc. Prof. Ahmet Oğuz Akyüz, who made super complicated topics crystal clear. I would also like to thank my classmates, whose quality questions, answers, and discussions around the concepts were inspiring and thought-provoking.


Comments