Trying to implement the Cook-Torrance BRDF for my path tracer. Confused about how to do it though. I've seen two approaches: 1: Sample the NDF, evaluate diffuse and specular components and add them weighed by the fresnel (between microfacet normal and view direction?) 2: Based on the fresnel (again, between microfacet normal and view direction?), randomly choose to evaluate a diffuse or specular path. If diffuse, sample uniformly/cosine, and if specular, sample the NDF. Correct/weigh by fresnel factor.

Why would you use the second approach though? When roughness is 1.0, sampling the NDF should be equivalent to cosine sampling, so approach 2 just seems like a more complicated way to do achieve the same thing.


1 Answer 1


Let's take a look at what' going on here. The Monte Carlo estimator is given by: $$ \hat{I} = \sum_{i=1}^N\frac{f(X_i)}{p(X_i)} $$ The $f(x)$ (evaluation function) here can be writen as (simplified form, ignored some terms in the BRDF): $$ f(\omega) = \biggl((1 - F)K_df_{\text{Lambert}}(\omega) + FK_sf_{\text{C-T}}(\omega) \biggr)\cos\theta $$ $F$ here is the Fresnel term, which weights the direct reflected specular radiance and the radiance that penetrates to the substrate and gets reflected back. The goal of importance sampling is to approximate $f(x)$ as closely as possible to achieve maximum variance reduction. So what's with the two approaches given?

  • The first approach: since we only sample the NDF, the $p(X)$ should simply be related to $f_{\text{C-T}}$ part (specular component), which is normally good, since diffuse component is generally easy to sample.
  • The second approach: Note that we are alternating between Lambertian sampling and Cook-Torrance sampling, therefore the PDF is a weighted one: $$ p(\omega) = p_{\text{Lambert}}(\omega|\text{diffuse})p(\text{diffuse}) + p_{\text{C-T}}(\omega|\text{specular})p(\text{specular}) $$ So generally, I think the PDF of the second approach is a better approximation to the integrand. So in this way I will prefer the second approach.

Also, the first approach will always compute NDF sampling and Cook-Torrance BRDF evaluation while the second approach can avoid this. NDF sampling and Cook-Torrance BRDF evaluation are significantly (yes, they are) more complex than our simple, adorable cosine-weighted sampling and Lambertian BRDF evaluation. So this might actually save you some rendering time (but, well, this should be negligible).

Moreover, we can actually find some similar examples to discourage you from using the first approach. For example, we have a material with IOR = 1. What should happen is that since $F$ is 0, we don't even have to consider the specular lobe. Therefore, it is just a waste of CPU cycles to sample the NDF and evaluate Cook-Torrance BRDF.

So, when should you use the two approaches? My suggestion is:

  • For the first approach: When you are implementing your path tracer on GPU, or the random sampler is complex and takes time to do the state update (like, Sobol or something): 1. GPU works more efficiently when each thread has similar workload (Google: thread divergence). 2. The second approach will have an extra sample to draw from the sampler, so when the sampling process is (too) expensive, the first approach is prefered.
  • For the second approach: well you can just implement this in your code, generally. If I recall correctly, Tungsten renderer does this the same way. Do note that if the BSDF contains both reflection and transmission... I think only the second approach works.

Hope this would help. Feel free to point out anything wrong.

  • $\begingroup$ Thank you I'll probably go with the second approach then. Don't really understand this notation though: ๐‘(๐œ”)=๐‘Lambert(๐œ”|diffuse)๐‘(diffuse)+๐‘C-T(๐œ”|specular)๐‘(specular). How are they weighed? $\endgroup$
    – Ruben
    Jan 5 at 9:05
  • $\begingroup$ This is a conditional probability notation. As, for example, when you are sampling the specular lobe, you are already assuming to have a specular scattering event. So, the probability of having a specular event is $F$, which is the value taken by $p(\text{specular})$. And basically, you will sample the NDF under the condition that you have a specular event. So $p(\omega | \text{specular})$ is the PDF of your specular lobe (NDF sampling PDF). $\endgroup$ Jan 5 at 11:28

Your Answer

By clicking โ€œPost Your Answerโ€, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.