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.
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)
Task 1: Microfacet BRDF
Implement the BRDF evaluation function
where is the Fresnel term, is the shadowing-masking term, and is the normal distribution function (NDF). is the macro surface normal, which is always in local coordinates. 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 and , we know that only those microfacets whose normals are exactly along the half vector are able to reflect to , because the microfacets are assumed to be perfectly specular. In other words, we need to query the NDF at .
The NDF can be defined in various kinds of distribution functions. Here we adopt Beckmann distribution, which is similar to a Gaussian distribution. Specifically,
where is the roughness of the macro surface, is the angle between and the macro surface normal .
For a reasonable range of , you can usually set it between and . The smaller is, the smoother the macro surface will be. In other words, the macro surface tends to be diffuse when is large and glossy when is small.
Task 3: Fresnel Term
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
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:
where and are used together to represent indices of refraction for conductors. Both of them are
Vector3D values, recording the scalar and values at wavelengths nm (red), nm (green) and nm (blue).
This website provides a collection of refraction indices for different materials at various wavelengths. You can easily query any conductor's ( on the website) and 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.
Task 4: Importance Sampling
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 and according to some pdfs and respectively, then combine them to get the sampled microfacet normal and its pdf. Then, we can reflect the given according to the sampled normal to get the sampled light incident direction .
Based on the importance sampling theory, the more these pdfs and resemble , the less noise you will have. So, for the Beckmann NDF, we provide a good pair of pdfs and :
To sample them, we use the inversion method by integrating and inversing those pdfs. This process can be difficult, so we provide the results:
where and are two random numbers uniformly distributed within .
After getting and , you immediately have the sampled microfacet normal . Reflecting according to gives you the sampled .
Now, the tricky part: what is the pdf of sampling w.r.t. solid angle? There are two steps to get it.
Since we originally had the pdfs of sampling w.r.t. and , we first calculate the pdf of sampling w.r.t. solid angle. This is given by
Then, we calculate the final pdf of sampling w.r.t. solid angle as:
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 .
- 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 and are valid (both and should be greater than zero) at the beginning of the BRDF evaluation function
MicrofacetBSDF::f(). If not, return zero.
- Check if your sampled 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
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.daerendered with 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 , just open the .dae file and search for
- Show two images of scene
CBbunny_microfacet_cu.daerendered 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
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