Context: I am attempting to implement Trowbridge–Reitz (GGX) based on Microfacet Models for Refraction through Rough Surfaces in a pathtracer. I use numerical integration to check if the pdfs integrate to 1 (I convert solid angle to spherical coordinates using the jacobian and integrate that):

It is stated that the probability of generating a given microfacet normal for GGX is p_m function

Where m is the microfacet normal and n is the macrofacet normal (which I set to (0, 0, 1) for the sake of testing). Note that || on the dot product isn't needed since D(m) < 0 if the dot product is below zero due to the positive characteristic function (X+) on all the given microfacet distributions including GGX (see below). GGX microfacet distribution

As expected when I integrate p_m(m) it indeed does integrate to 1 as you would expect. integration of p_m = 1

The problems arise when I try to integrate the pdf of the sampled direction (o). Which I should be able to do by using the jacobian.

p_o = p_m(m) * || omega_w_h / omega_w_o ||

The paper states that for ideal reflection the jacobian is 1/(4|o dot h|)

ideal reflection section of paper

So I reasoned that this would be true: integral of p_o can be done by modifying integral above with jacobian

The reason why I'm assuming the first equality is true is due to:

"Let us assume that for any given incident and outgoing directions, there is at most one microsurface normal that scatters energy from i to o, and that we can compute that normal as h(i, o), which we call the half-direction". As well as the equation above (38).

I would like to point out that I am only doing ideal reflection, so no refraction.

The problem is when I attempt to integrate the expression in the image above. It does not integrate to 1 or a consistent value.

To make the distribution integrate to 1 I have to get rid of the positive characteristic function in D(m). Which results in p_m(m) integrating to 0 but p_o(o) integrating to 1.

Why does removing the positive characteristic function have this result and why doesn't p_o(o) integrate to 1 when it remains?

Also as a side note, I find that using

reflection formula in paper

to calculate o given h and i leads to sampled distribution not matching up with the pdf (I use a chi-squared test of the binned values to verify this) compared to not using the absolute value (which matches up to most formulas online). Note that for my testing I generate i in the top hemisphere and pointing away from the surface.


1 Answer 1


Looks like the boundary of the integration changes to positive m.n and so you drop the Heaviside fnc to reflect that. (Actually a sample for m.n<0 is not a valid sample, you ain't taking its value at all but just discard it.. meaning in code it's an if() before the actual sampling routine).

Ie. something like this : enter image description here

The 2|i.m|m-i is actually 2(i.m)m-i (Walter is writing it like that in ie. Notes on the Ward BRDF).

  • $\begingroup$ Hello, I'm not quite sure what you mean by "boundary of the integration change[s] to positive m.n and so you drop the Heaviside f[u]nc[tion] to reflect that". I thought the boundary of integration with the microfacet normal is positive m.n due to the Heaviside function and presumably when we drop the Heaviside function when integrating the output (rather than the microfacet normal) direction m.n can be < 0 which works for some reason. Also rather than having an if statement I just set the pdf to 0 (with the microfacet normal which has the Heaviside function) which should function the same. $\endgroup$ Commented Jan 11, 2023 at 1:07
  • $\begingroup$ I have added a simple example to the answer. The Heaviside fnc is a constant after all and can be put outside the integral while changing the boundaries. $\endgroup$ Commented Jan 18, 2023 at 19:17
  • 1
    $\begingroup$ I understand you can drop the Heaviside function to integrate into 1. As for why it doesn't integrate to 1 with the Heaviside function: It turns out the mapping from m to o is not one-to-one since both 2(i . m) m - i and 2(i . -m) -m are valid and for the jacobian to work you need to take into consideration both. So in practice, this looks like if (m . n < 0) { m = -m }. After this change, the function integrates into one (with the Heaviside function) as you would expect. $\endgroup$ Commented Jan 21, 2023 at 2:59

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.