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.