Both of these images were rendered using 256 samples/pixel, 4 samples/light, and max ray depth equal to 7. It took about 2 minutes each.

## Introduction

In this part, you're going to implement the Microfacet model. Specifically, isotropic rough conductors that only reflect. You don't have to implement refractions here.

After finishing, you'll be able to render the following scenes:

• CBbunny_microfacet_cu.dae (copper)
• CBdragon_microfacet_au.dae (gold)
• CBspheres_microfacet_al_ag.dae (aluminum and silver)
• bunny_microfacet_cu.dae (copper)

Implement the BRDF evaluation function MicrofacetBSDF::f() (not F() function!):

$f=\frac{F(\omega_i) * G(\omega_o, \omega_i) * D(h)}{4 * (n\cdot\omega_o) * (n\cdot\omega_i)},$

where $F$ is the Fresnel term, $G$ is the shadowing-masking term, and $D$ is the normal distribution function (NDF). $n$ is the macro surface normal, which is always $(0,0,1)$ in local coordinates. $h$ is the half vector as before.

We have some code pre-written for these functions. So, in this step, you just have to call them correctly. You will need to modify them in the following steps.

## Task 2: Normal Distribution Function (NDF)

Implement the normal distribution function (NDF) MicrofacetBSDF::D(). The pre-implemented code for this function is fake, and needs to be overwritten.

The NDF defines how the microfacets' normals are distributed. Given the light incident and outgoing directions $\omega_i$ and $\omega_o$, we know that only those microfacets whose normals are exactly along the half vector $h$ are able to reflect $\omega_i$ to $\omega_o$, because the microfacets are assumed to be perfectly specular. In other words, we need to query the NDF at $h$.

The NDF can be defined in various kinds of distribution functions. Here we adopt Beckmann distribution, which is similar to a Gaussian distribution. Specifically,

$D(h)=\frac{e^{-\frac{\tan^2\theta_h}{\alpha^2}}}{\pi\alpha^2\cos^4\theta_h},$

where $\alpha$ is the roughness of the macro surface, $\theta_h$ is the angle between $h$ and the macro surface normal $n$.

For a reasonable range of $\alpha$, you can usually set it between $0.005$ and $0.5$. The smaller $\alpha$ is, the smoother the macro surface will be. In other words, the macro surface tends to be diffuse when $\alpha$ is large and glossy when $\alpha$ is small.

Implement the Fresnel term MicrofacetBSDF::F(). The pre-implemented code for this function needs to be overwritten.

First of all, you cannot use the Fresnel term or the Schlick's approximation from previous parts! Those are for air-dielectric interfaces but here we need air-conductor. Moreover, air-dielectric Fresnel terms are not wavelength-dependent, which means that you can calculate the Fresnel term, then multiply some color with it. However, the air-conductor Fresnel term is wavelength-dependent, which means that it contains color information, thus has type Spectrum here.

Theoretically, to be 100% accurate, we need to calculate the Fresnel term for every possible wavelength, then convert the sampled spectrum to an RGB color. But that'll be too complicated. So, here we make a simplification. We calculate the Fresnel terms for R, G, B channels respectively, assuming that each channel has a fixed wavelength.

To calculate the air-conductor Fresnel term, you can use the following approximation:

$F=\frac{R_s+R_p}{2},$

$R_s=\frac{(\eta^2+k^2)-2\eta\cos\theta_i+\cos^2\theta_i}{(\eta^2+k^2)+2\eta\cos\theta_i+\cos^2\theta_i},$

$R_p=\frac{(\eta^2+k^2)\cos^2\theta_i-2\eta\cos\theta_i+1}{(\eta^2+k^2)\cos^2\theta_i+2\eta\cos\theta_i+1}.$

where $\eta$ and $k$ are used together to represent indices of refraction for conductors. Both of them are Spectrum values, recording the scalar $\eta$ and $k$ values at wavelengths $614$ nm (red), $549$ nm (green) and $466$ nm (blue).

This website provides a collection of refraction indices for different materials at various wavelengths. You can easily query any conductor's $\eta$ ($n$ on the website) and $k$ values at our fixed wavelengths, and replace them in our .dae file to get your own metal. You'll find it useful for one of the deliverables of this part.

Implement the BRDF sampling function MicrofacetBSDF::sample_f(). The pre-implemented code is cosine hemisphere sampling, which is perfect for importance sampling diffuse BRDFs, but not suitable for Beckmann distribution. Your task here is to importance sample the microfacet BRDF according to the shape of the Beckmann NDF. Thus, this function must be overwritten.

The general idea is that, you can sample $\theta_h$ and $\phi_h$ according to some pdfs $p_\theta$ and $p_\phi$ respectively, then combine them to get the sampled microfacet normal $h$ and its pdf. Then, we can reflect the given $w_o$ according to the sampled normal $h$ to get the sampled light incident direction $\omega_i$.

Based on the importance sampling theory, the more these pdfs $p_\theta$ and $p_\phi$ resemble $D(h)$, the less noise you will have. So, for the Beckmann NDF, we provide a good pair of pdfs $p_\theta$ and $p_\phi$:

$p_\theta(\theta_h)=\frac{2\sin\theta_h}{\alpha^2\cos^3\theta_h}e^{-\tan^2\theta_h/\alpha^2},$

$p_\phi(\phi_h)=\frac{1}{2\pi}.$

To sample them, we use the inversion method by integrating and inversing those pdfs. This process can be difficult, so we provide the results:

$\theta_h=\arctan \sqrt{-\alpha^2\ln(1-r_1)},$

$\phi_h=2\pi r_2,$

where $r_1$ and $r_2$ are two random numbers uniformly distributed within $[0, 1)$.

After getting $\theta$ and $\phi$, you immediately have the sampled microfacet normal $h$. Reflecting $\omega_o$ according to $h$ gives you the sampled $\omega_i$.

Now, the tricky part: what is the pdf of sampling $\omega_i$ w.r.t. solid angle? There are two steps to get it.

Since we originally had the pdfs of sampling $h$ w.r.t. $\theta$ and $\phi$, we first calculate the pdf of sampling $h$ w.r.t. solid angle. This is given by

$p_\omega(h)=\frac{p_\theta(\theta_h)\cdot p_\phi(\phi_h)}{\sin(\theta_h)}.$

Then, we calculate the final pdf of sampling $\omega_i$ w.r.t. solid angle as:

$p_\omega(\omega_i)=\frac{p_\omega(h)}{4 (\omega_i\cdot h)},$

This website provides a very good derivation of these pdfs.

Some implementation notes:

• You should fill in the sampled direction *wi and the corresponding *pdf, and return the sampled BRDF value same as before (not just the NDF value!).
• You can use sampler.get_sample() to get two uniform random numbers within $[0, 1)$.
• Although the cosine hemisphere sampling method provided by default here is inefficient, it is still correct! It'll take a long time to converge to a noise-free result, but you can use it to verify your importance sampling.
• Check if $\omega_i$ and $\omega_o$ are valid (both $n \cdot \omega_i$ and $n \cdot \omega_o$ should be greater than zero) at the beginning of the BRDF evaluation function MicrofacetBSDF::f(). If not, return zero.
• Check if your sampled $\omega_i$ is valid. If not, return zero pdf and zero BRDF.
• If you have any black spots or regions, check for numerical errors! Is your pdf zero or negative? Are you dividing by zero such that you get NaNs or Infs? Do you have negative values under a square root?

## This Part's Writeup

• Show a sequence of 4 images of scene CBdragon_microfacet_au.dae rendered with $\alpha$ set to 0.005, 0.05, 0.25 and 0.5. The other settings should be at least 128 samples per pixel and 1 samples per light. The number of bounces should be at least 5. Describe the differences between different images. Note that, to change the $\alpha$, just open the .dae file and search for microfacet.
• Show two images of scene CBbunny_microfacet_cu.dae rendered using cosine hemisphere sampling (default) and your importance sampling. The sampling rate should be fixed at 64 samples per pixel and 1 samples per light. The number of bounces should be at least 5. Briefly discuss their difference.
• Show at least one image with some other conductor material, replacing eta and k. Note that you should look up values for real data rather than modifying them arbitrarily. Tell us what kind of material your parameters correspond to