(possible duplicate)

I'm trying to implement multiple importance sampling, as described in the PBRT book (no bouncing yet, I want to make MIS work first).

What my problem is, that while the BRDF sampling looks okay, the light sampling is much dimmer (yet according to the pictures in the book they should look the same in intensity).

sampling the BRDF sampling the light

I uploaded the (updated) compilable code (VS 2015). The relevant parts can be found in multipleimportance.frag (SampleLightsExplicit) and pbr_common.head (ConeSampleSphere).

(note that I don't apply the MIS weights yet)

for (int k = 0; k < numLights; ++k) {
    SceneObject light = objects.data[k];

    center = vec3(light.toworld[3].xyz);
    radius = light.toworld[0][0];

    // importance sample light (xyz = direction, w = PDF)
    sample1 = ConeSampleSphere(p, center, radius, pixel, seed + float(k));

    // cast shadow ray
    vis = Visibility(p, sample1.xyz, k);
    ndotl = vis * max(0.0, dot(sample1.xyz, n));

    // evaluate BRDF
    fd = vec3(0.0); //obj.color.rgb * ONE_OVER_PI;
    fs = CookTorrance_General(sample1.xyz, v, n, vec3(0.04), roughness);

    sum += (light.color.rgb * (fd + fs) * ndotl) / sample1.w;

My question is why light sampling looks so dim? Did I misunderstand something?


Looking through the code I noticed some inconsistencies which probably originated from a typo in one of the older SIGGRAPH course notes (can't tell which one tho).

The PDF of the GGX distribution is:

PDF = D(h) * dot(n, h)

While some implementations (including UE4) incorrectly write it as

PDF = (D(h) * dot(n, h)) / (4 * dot(v, h))

which essentially means that they include the Cook-Torrance denominator into this variable (ndotl cancels out). Dividing with the correct PDF cancels out D in the Cook-Torrance model, which I forgot to account for, while the 1 / (4 * dot(v, h)) term goes into the Vis_Schlick function.

So regarding intensity the second picture is the "correct" one. The dimness is still a problem tho. Changing the Fresnel F0 to 0.11 (as it supposedly is in the book) and applying the MIS weights, the picture looks a lot more promising, except for the topmost plate which should almost explicitly reflect the light sources:

multiple importance sampling

So this is almost okay now. I'm gonna look deeper into the MIS part to see why it makes those highlights disappear.

  • $\begingroup$ Are you accounting for the probability of choosing a light? $\endgroup$
    – Hubble
    Jan 29, 2020 at 20:08
  • $\begingroup$ I'm not 'choosing' a light (in the above code), I accumulate the contribution of all lights. Also, this is reproable if I use just one light only. $\endgroup$
    – Asylum
    Jan 30, 2020 at 13:04
  • $\begingroup$ I saw your code, you are not dividing by the bsdf_pdf and missing the ndotl factor while computing your bsdf sample. Also post the code for both the calculations here. Not everyone's gonna download the whole project. $\endgroup$ Feb 1, 2020 at 6:44
  • $\begingroup$ because the denominator of the Cook-Torrance BRDF cancels it out. I found some other errors in the code tho, I update the post with my findings. $\endgroup$
    – Asylum
    Feb 1, 2020 at 10:32

1 Answer 1


Shoot me... After the fixes I posted above, I started debugging Mitsuba to see whether it does anything differently on a large scale. Of course it didn't; my code is completely correct, the dimness is due to this macro def:

#define EPSILON   1e-5

This is used in divisions to avoid dividing with zero. Unfortunately, it accumulates to a large value during the calculations. Changing it to 1e-9 fixes everything:


For future generations I leave the source code up (just remember to change this value).


I found another mistake in my calculations, but it isn't apparent in this scene. Microsurface-based BRDFs are usually sampled according to the microfacet normal distribution function (D(h, a), in this case GGX).

Don't forget however, that you also have a shadowing-masking function (G(l, v, a)) which has to be evaluated according to the chosen microfacet distribution. This means that I can't just mix-and-match f.e. GGX sampling and the Smith-Schlick evaluation; I have to use the Smith-GGX solution:

float G_Smith_GGX(float ndotl, float ndotv, float roughness)
    float a = roughness * roughness;
    float a2 = a * a;

    float lambda_l = sqrt(a2 + (1.0 - a2) * ndotl * ndotl);
    float lambda_v = sqrt(a2 + (1.0 - a2) * ndotv * ndotv);

    return (2.0 * ndotl * ndotv) / (ndotv * lambda_l + ndotl * lambda_v);

Which explains the UE4 approach, as:

vec3 F = F_Schlick(F0, ldoth);
float G = G_Smith_GGX(ndotl, ndotv, roughness);

// PDF = (D(h) * dot(n, h)) / (4 * dot(v, h))
return (F * G * vdoth) / (ndotv * ndoth + EPSILON);

See this article for the derivation.

  • $\begingroup$ Changing this value shouldn't really affect the behavior of your algorithm though. I think you should look into how you handle PDFs of zero. In fact, changing the epsilon in Mitsuba doesn't break MIS so your problem probably lies somewhere else. $\endgroup$
    – Hubble
    Feb 4, 2020 at 0:50
  • $\begingroup$ simply put, I don't (have to) handle them :) Since I'm using the power heuristic, the MIS weight could be Inf or NaN only when both PDFs are zero (which is unlikely). Also I don't use Dirac deltas (yet). $\endgroup$
    – Asylum
    Feb 4, 2020 at 11:43
  • $\begingroup$ Epsilon values are a fact of life under a floating-point regime, but I think any geometric problem should be able to justify a value based on the propagation of some measurement uncertainty in a system. Why 1e-9? Even something based on FLT_EPSILON (2^-23), or even HALF_EPSILON (2^-10), as a kind of intrinsic 'uncertainty', should yield something physically consistent when measurement error is propagated through a physical system. $\endgroup$
    – Brett Hale
    Feb 27, 2020 at 13:45
  • $\begingroup$ I know that may come across as incredibly tedious, but I've been going through a lot of floating-point code and trying to quantify errors and failures in the domain of graphics. I mean, even the typical implementation of the inner product is subject to catastrophic cancellation... Otherwise, +1 for the care and feeding of your physically-based renderer! $\endgroup$
    – Brett Hale
    Feb 27, 2020 at 13:50
  • $\begingroup$ I can't choose an arbitrarly small epsilon. For example the Smith-GGX function above tends to return zero for example at the silhouette of a sphere, which then messes up everything. Same thing happens with very small epsilon values. 1e-9 is "so far so good". $\endgroup$
    – Asylum
    Mar 5, 2020 at 14:17

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.