<p>I'm receiving a non-energy conserving output while trying to importance sample the GGX Distributionof the Microfacet model, which is generally 3-4 times bigger than the resulting PDF</p>

<p>I'm following this blog post: <a href="https://agraphicsguy.wordpress.com/2015/11/01/sampling-microfacet-brdf/" rel="nofollow noreferrer">Sampling microfacet BRDF | Cao Jiayin Blog</a></p>

<p>And I'm sampling the $\theta $ angle with the following formula / code:</p>

<p>$$ \theta = acos(\sqrt{ \frac{1 - \epsilon}{\epsilon(\alpha^2 - 1) +1} }) $$</p>

<pre><code>float sampleTheta(float roughness) {
    float random = rnd();
    return acos(
             sqrt(              (1.0f - random) /
                    ((roughness * roughness - 1.0f) * random + 1.0f)
                 )
           );
}
</code></pre>

<p>The GGX Distribution function i'm using is:</p>

<p>$$  \frac{ \alpha^2 }{\pi((\alpha^2 - 1) cos^2\theta + 1)^2}  $$</p>

<pre><code>float MicroFacetMaterial::Distribution(vec3 normal, vec3 halfvector, float alpha) {
    float NoH = dot(normal, halfvector);
    float NoH2 = NoH * NoH;
    float alpha2 = alpha * alpha;

    float a1 = (NoH2 * (alpha2 - 1.0f) + 1.0f);

    return alpha2 / (M_PI * a1 * a1);
}
</code></pre>

<p>And finally, the blog post derived this PDF to use along with the BRDF:</p>

<p>$$ p_{h}(\theta) = \frac{2\alpha^2 \ cos\theta \ sin\theta}{((\alpha^2 - 1)cos^2\theta + 1 )^2 } \\ \ \\ \ \\ p(\theta) = \frac{p_{h}(\theta)}{ 4 (\omega o \cdot \omega h)} $$</p>

<pre><code>float get_pdf(vec3 wo, vec3 normal, vec3 halfvector, float roughness) {
    float NoH = dot(normal, halfvector);
    float NoH2 = NoH * NoH;
    float sin = sqrt(1.0f - NoH2);
    float alpha2 = roughness * roughness;

    float a1 = (NoH2 * (alpha2 - 1.0f) + 1.0f);

    float pdf_h = (2.0f * alpha2 * NoH * sin) / (a1 * a1);

    return pdf_h / (4.0f * dot(wo, halfvector));
}
</code></pre>

<p>And as a final step I'm calculating incident radiance as follow for an RGB non-recursive path tracer </p>

<pre><code>  float cosT = clampDot(newdirection, normal);
  vec3 halfVector = HalfwayVector(-ray.direction, newdirection);

  float pdf = get_pdf(-ray.direction, normal, halfVector, _roughness);

  float distribution = Distribution(normal, halfVector, _roughness);
  vec3 fresnel = FresnelColor(newdirection, halfVector, clampDot(halfVector, -ray.direction));
  float geometry = Geometry(-ray.direction, newdirection, normal, halfVector);

  float denominator = 4.0f * clampDot(normal, -ray.direction) * clampDot(normal, newdirection);

  vec3 brdf = (distribution * fresnel * geometry) / (denominator);

  mask = (albedo * brdf * cosT) / pdf;
</code></pre>

<p>The output I'm getting is a model that behaves almost like a lightsource - while debugging, I saw the Distribution term normally is up to 4 times bigger than the respective PDF, I've also decided not to post the Geometry and Fresnel term since at least from the debugging I've done those are not contributing as much as the Distribution term (as expected)</p>

<p>If anyone can confirm the above looks alright, I'll try to look for the error elsewhere</p>

CC BY-SA 3.0
2018-04-04T08:44:08.877Z
In the distribution term, you should take care that whenever cos(theta) is less or equal to 0, the distribution term should return 0 (Xi operator) http://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf
Nadir
CC BY-SA 3.0
2018-04-04T22:57:44.277Z
somehow I missed it, thanks for helping, though this doesn't really change the unfortunate output of my path tracer
Row Rebel
CC BY-SA 3.0
2018-04-07T11:17:38.433Z
I have gone a little bit deeper into your code, and have notice you are not taking into account energy conservation. When sampling microfacet normals, you need 2 terms, although the second one is usualy 2*PI*random, you need the microfacet normal to compute the fresnel term for the microfacet model. Then, you would use the half vector to compute another fresnel term and use it as 1 - newFresnelTerm to ensure energy conservation. The final formulae for the brdf should look like diffuse * (1- fresnelDiffuse) + specular * brdf, and then use it in the rendering equation
Nadir
CC BY-SA 3.0
2018-04-07T14:32:22.207Z
To be clear, the material I'm simulating has the diffuse component set to 0, I'm currently only simulating the specular part of it
I thought the above sampling code would sample the incoming direction, and that the halfvector between wo and wi is the microfacet normal, is this correct? If not I may have to re-evaluate what I wrote
Row Rebel
CC BY-SA 3.0
2018-04-07T15:15:33.153Z
Yes, that was meant for the reflected ray. I have review my microfacet implementation to check for comparsion. I have noticed that when computing the final microfacet brdf, you should check whether the denominator is 0, in which case the brdf should return 0, otherwise you could run into a indetermination of the type 0 / 0.
Nadir
CC BY-SA 3.0
2018-04-09T22:30:08.113Z
I'll be sure to fix it, though I'm currently not getting any inf values as radiance, by the way many thanks for your help
Row Rebel
<raytracing><pathtracing><brdf><importance-sampling><microfacet>
Wrong output while implementing GGX importance sampling
Row Rebel
# Wrong output while implementing GGX importance sampling

(Asked by: username_0 on 2018-03-29)

I'm receiving a non-energy conserving output while trying to importance sample the GGX Distributionof the Microfacet model, which is generally 3-4 times bigger than the resulting PDF

I'm following this blog post: [Sampling microfacet BRDF | Cao Jiayin Blog](https://agraphicsguy.wordpress.com/2015/11/01/sampling-microfacet-brdf/)

And I'm sampling the $\theta $ angle with the following formula / code:

$$ \theta = acos(\sqrt{ \frac{1 - \epsilon}{\epsilon(\alpha^2 - 1) +1} }) $$

```
float sampleTheta(float roughness) {
    float random = rnd();
    return acos(
             sqrt(              (1.0f - random) /
                    ((roughness * roughness - 1.0f) * random + 1.0f)
                 )
           );
}

```

The GGX Distribution function i'm using is:

$$ \frac{ \alpha^2 }{\pi((\alpha^2 - 1) cos^2\theta + 1)^2} $$

```
float MicroFacetMaterial::Distribution(vec3 normal, vec3 halfvector, float alpha) {
    float NoH = dot(normal, halfvector);
    float NoH2 = NoH * NoH;
    float alpha2 = alpha * alpha;

    float a1 = (NoH2 * (alpha2 - 1.0f) + 1.0f);

    return alpha2 / (M_PI * a1 * a1);
}

```

And finally, the blog post derived this PDF to use along with the BRDF:

$$ p\_{h}(\theta) = \frac{2\alpha^2 \ cos\theta \ sin\theta}{((\alpha^2 - 1)cos^2\theta + 1 )^2 } \\ \ \\ \ \\ p(\theta) = \frac{p\_{h}(\theta)}{ 4 (\omega o \cdot \omega h)} $$

```
float get_pdf(vec3 wo, vec3 normal, vec3 halfvector, float roughness) {
    float NoH = dot(normal, halfvector);
    float NoH2 = NoH * NoH;
    float sin = sqrt(1.0f - NoH2);
    float alpha2 = roughness * roughness;

    float a1 = (NoH2 * (alpha2 - 1.0f) + 1.0f);

    float pdf_h = (2.0f * alpha2 * NoH * sin) / (a1 * a1);

    return pdf_h / (4.0f * dot(wo, halfvector));
}

```

And as a final step I'm calculating incident radiance as follow for an RGB non-recursive path tracer

```
  float cosT = clampDot(newdirection, normal);
  vec3 halfVector = HalfwayVector(-ray.direction, newdirection);

  float pdf = get_pdf(-ray.direction, normal, halfVector, _roughness);

  float distribution = Distribution(normal, halfVector, _roughness);
  vec3 fresnel = FresnelColor(newdirection, halfVector, clampDot(halfVector, -ray.direction));
  float geometry = Geometry(-ray.direction, newdirection, normal, halfVector);

  float denominator = 4.0f * clampDot(normal, -ray.direction) * clampDot(normal, newdirection);

  vec3 brdf = (distribution * fresnel * geometry) / (denominator);

  mask = (albedo * brdf * cosT) / pdf;

```

The output I'm getting is a model that behaves almost like a lightsource - while debugging, I saw the Distribution term normally is up to 4 times bigger than the respective PDF, I've also decided not to post the Geometry and Fresnel term since at least from the debugging I've done those are not contributing as much as the Distribution term (as expected)

If anyone can confirm the above looks alright, I'll try to look for the error elsewhere

**Tags:** raytracing, pathtracing, brdf, importance-sampling, microfacet

> **Comments:**
> **username_1**: In the distribution term, you should take care that whenever cos(theta) is less or equal to 0, the distribution term should return 0 (Xi operator) http://www.graphics.cornell.edu/~bjw/microfacetbsdf.pdf  
> **username_0**: somehow I missed it, thanks for helping, though this doesn't really change the unfortunate output of my path tracer  
> **username_1**: I have gone a little bit deeper into your code, and have notice you are not taking into account energy conservation. When sampling microfacet normals, you need 2 terms, although the second one is usualy 2*PI*random, you need the microfacet normal to compute the fresnel term for the microfacet model. Then, you would use the half vector to compute another fresnel term and use it as 1 - newFresnelTerm to ensure energy conservation. The final formulae for the brdf should look like diffuse * (1- fresnelDiffuse) + specular * brdf, and then use it in the rendering equation  
> **username_0**: To be clear, the material I'm simulating has the diffuse component set to 0, I'm currently only simulating the specular part of it I thought the above sampling code would sample the incoming direction, and that the halfvector between wo and wi is the microfacet normal, is this correct? If not I may have to re-evaluate what I wrote  
> **username_1**: Yes, that was meant for the reflected ray. I have review my microfacet implementation to check for comparsion. I have noticed that when computing the final microfacet brdf, you should check whether the denominator is 0, in which case the brdf should return 0, otherwise you could run into a indetermination of the type 0 / 0.  
> **username_0**: I'll be sure to fix it, though I'm currently not getting any inf values as radiance, by the way many thanks for your help  

---

## 0 answers

