CS 184: Computer Graphics and Imaging, Fall 2020

Project 3-2: PathTracer

Mae Wang, CS184-adu



Overview

Previously in PathTracer [Part 1], we were able to implement a ray-tracing algorithm rendering technique that can realistically, and quickly, render images with physical lighting.

Here, in PathTracer [Part 2], we will add on additional features to our program that will allow for us to render objects of different BSDF (bidirectional scattering distribution function), such as mirror, glass, and metal (isotropic rough conductors) materials using mathematical models for reflection, refraction, and the BRDF evaluation function (involving the Fresnel term, shadowing-masking term, and normal distribution function (NDF)). In my opinion, the most interesting feature of isotropic rough conductors is the fact that we can use the microfacet model to create all sorts of metals through a dynamic approach. It is very fascinating to see the few variables needed for such drastic changes in material are α (Roughness factor), η (Refractive index = old index of refraction/new index of refraction), and k (Extinction coefficient) in fixed R, G, B channels. This implies much about the similar properties of isotropic rough conductors, which brings in the new question about whether other materials (like gasses and liquids) can be dynamically programmed in a similar manner. Overall, it was very enriching to be able to have a strong sense and control over what can be rendered in a .dae file.



Section I: Mirror and Glass Materials


Below are rendered images of "CBspheres.dae" with a samples-per-pixel rate of 256 and samples-per-light of 4. These rendered images display the result and visual differences of increasing max_ray_depth (maximum number of bounces for sampled rays) with max_ray_depth set to 0, 1, 2, 3, 4, 5, and 100 respectively. On the left of the Cornell box is a mirror sphere and on the right is a glass sphere.



max_ray_depth = 0; no bounce
max_ray_depth = 1; 1 bounce
max_ray_depth = 2; 2 bounces
max_ray_depth = 3; 3 bounces
max_ray_depth = 4; 4 bounces
max_ray_depth = 5; 5 bounces


max_ray_depth = 100; 100 bounces


Although it is hard to see, the image generally gets brighter as bounces increases. Also, it is good to note that due to the Russian Roulette kill probability, most rays will be killed before reaching its max bounce (it is hardly possible that a ray lasted 100 bounces in the 100-bounce render!) Overall, this rendered image appears to have mostly converged to its ideal visual state at the 4-bounce. Below is a GIF of the very small changes seen in larger bounces. Notice the brightness difference!

GIF displaying differences from 2 to 100 bounces


In general, this section contains a lot of mathematics and is quite straightforward. There are times when I wrote the equations wrong in code, which are hard to find and debug sometimes! It is suggested to code these long equations in blocks/variables to avoid needless errors.



Section II: Microfacet Material


Below is "CBdragon_microfacet_au.dae" rendered with varying α (= 0.005, 0.05, 0.25, and 0.5), where α is the roughness factor of the material. The images below are rendered with a samples-per-pixel rate of 1024, samples-per-light of 1, and max-ray-depth of 5 (5 bounces max).



α = 0.005
α = 0.05
α = 0.25
α = 0.5


With a small α, we will tend to see a glossier surface, while a bigger α will give us a more diffuse surface (much resembles a rough surface). At α = 0.005, we see the red, blue, and black of the dragon to appear very solid and saturated. This is because rays intersecting a part of the dragon tend to create new rays moving in similar directions, as seen below:

Direction of rays on glossy material


Interestingly, while glossy items tend appear glaringly bright sometimes, this dragon's orientation (rough micro bumps) implies the rays tend to hit the walls/leave the scene more than it hits the light. Thus, this dragon appears to be a bit darker than a higher α value.

As α increases, we will tend to see the intersecting rays diffuse in all directions. With this, rays now have an unbiased way of reaching towards the light source. This results in a much brighter dragon! Furthermore, the red, blue, and black on the dragon naturally averages out better and creates softer colored shadows for the dragon. Below is for diffuse materials:

Direction of rays on diffuse material


On a side note, when α is small, bright specks appear on some pixels, implying the ray tracing algorithm is gathering too much light/color than needed for our average color. Since glossy implies a "biased" direction for a ray, it is interesting to see how roughness points out the intrinsic problem of pathtracing.

Below is the same rendered dragon as above, but the material turned from gold (Au) to mercury (Hg). This is done by changing the η (Refractive index = old index of refraction/new index of refraction), and k (Extinction coefficient) in fixed R, G, B channels of wavelengths 614 nm (red), 549 nm (green) and 466 nm (blue). Below are the values for mercury:

Eta and k for mercury


"CBdragon_microfacet_au.dae" rendered as mercury; [Au --> Hg]


Below is "CBbunny_microfacet_cu.dae" rendered using cosine hemisphere sampling (left) and importance sampling (right) with microfacet BRDF for the Beckmann distribution. The images below are rendered with a samples-per-pixel rate of 64, samples-per-light of 1, and max-ray-depth of 5.

Bunny with cosine hemisphere sampling
Bunny with importance sampling


On the left, cosine hemisphere sampling may have been good for diffuse BSDF, but not for microfacet BRSD. Our distrubution is simply not a diffuse one! We can see that, although similar, there are a lot of black noise on the bunny. This is the same issue presented for cosine hemisphere sampling generally, where sampling uniformly around tends to sample uninteresting areas, resulting in black pixels. Thus, its only solution is to sample more, which would be too costly. However, with our new importance sampling, we can now converge faster, giving us a better result, which can be seen on the right.

As one would expect, with our new importance sampling, we are able to render nicely without many black patches on the bunny. Here, we used importance sampling in respect to the Beckmann NDF, which we can find the inversion of the pdfs and , ultimately giving us sampled angles of θh (angle in respect to the z-axis) and ϕh (angle in respect to the x- and y-axis). With these, we can easily get the sampled microfacet normal h and reflect our input ray about it. Through derivation of our pdfs, we can also find the pdf of obtaining our new ray. ​​

Once again, this section requires a lot of mathematics and a careful translation into code! While straightforward, one problem I encountered was swapping ϕh and θh, giving me the wrong sampled microfacet normal h! Looking carefully at the equations made me realize that ϕh ranged from (0 to 2π) and θh ranged from (0 to π), which was the opposite of the symbol's usual symbol convention! Be careful!