<p>TL;DR: Your $G1$ formula is wrong. </p>

<hr>

<p>Just to avoid confusion, I am assuming the isotropic version of the BRDF, the Smith microfacet model (as opposed to the V-cavity model), and the GGX microfacet distribution.</p>

<p>According to <a href="https://hal.inria.fr/hal-00996995" rel="nofollow">Heitz 2014</a>, the masking/shadowing term $G1$ is</p>

<p>$$
\chi^{+}\left(\omega_{v}\cdot\omega_{m}\right)
\frac{2}{1+\sqrt{1+\alpha_{o}^{2}\tan^{2}\theta_{v}}}
$$</p>

<p>and according to <a href="http://dx.doi.org/10.2312/EGWR/EGSR07/195-206" rel="nofollow">Walter 2007</a>, the formula is</p>

<p>$$
\chi^{+}\left(\frac{\omega_{v}\cdot\omega_{g}}{\omega_{v}\cdot\omega_{m}}\right)\frac{2}{1+\sqrt{1+\alpha^{2}\tan^{2}\theta_{v}}}
$$</p>

<p>Where $\omega_{m}$ is the microfacet normal direction (halfway vector), $\omega_{g}$ is the main (geometric) normal direction (normal), $\omega_{v}$ is the incoming or outgoing direction, $\alpha$ is the isotropic roughness parameter, and $\chi^{+}\left(a\right)$ is the positive characteristic function, or the Heaviside step function (equals one if $a&gt;0$ and zero otherwise).</p>

<p>As you can notice, the half vector $\omega_{m}$ is used only to make sure the $G1$ is zero if the geometrical configuration is forbidden. More precisely, it makes sure that the back surface of the microsurface is never visible from the $\omega_{v}$ direction on the front side of the macrosurface and vice versa (the latter case is meaningful only when also refractions are supported). If the calling code guarantees this, then you can obviously omit this parameter. That is probably the reason why they did so in Unity.</p>

<p>Your implementation, on the other hand, uses the half vector to compute the cosine of the $\omega_{v}$ direction with respect to the microfacet, which leads to computation of something else than the presented formulae.</p>

<p>If it is of any help, then this is my implementation of the $G1$ factor:</p>

<pre><code>float SmithMaskingFunctionGgx(
    const Vec3f &amp;aDir,  // the direction to compute masking for (either incoming or outgoing)
    const Vec3f &amp;aMicrofacetNormal,
    const float  aRoughnessAlpha)
{
    PG3_ASSERT_VEC3F_NORMALIZED(aDir);
    PG3_ASSERT_VEC3F_NORMALIZED(aMicrofacetNormal);
    PG3_ASSERT_FLOAT_NONNEGATIVE(aRoughnessAlpha);

    if (aMicrofacetNormal.z &lt;= 0)
        return 0.0f;

    const float cosThetaVM = Dot(aDir, aMicrofacetNormal);
    if ((aDir.z * cosThetaVM) &lt; 0.0f)
        return 0.0f; // up direction is below microfacet or vice versa

    const float roughnessAlphaSqr = aRoughnessAlpha * aRoughnessAlpha;
    const float tanThetaSqr = Geom::TanThetaSqr(aDir);
    const float root = std::sqrt(1.0f + roughnessAlphaSqr * tanThetaSqr);

    const float result = 2.0f / (1.0f + root);

    PG3_ASSERT_FLOAT_IN_RANGE(result, 0.0f, 1.0f);

    return result;
}
</code></pre>

CC BY-SA 3.0
2016-05-29T08:26:00.767Z
Thank you for your reply. I've implemented the formula you've supplied and I got identical results as with my own (when using the macrosurface normal). So it seems it's just a different form (I got it from: http://graphicrants.blogspot.nl/2013/08/specular-brdf-reference.html)

I was confused about the half vector because the SIGGRAPH 2015 PBS math course specifically state the geometry function dependant on the view, light and half vectors. So this is an error in the slides?
Erwin
CC BY-SA 3.0
2016-05-29T10:01:42.137Z
@Erwin, now that you provided also the formula itself, it is much clearer. Next time do it right at the beginning, it helps. Yes, both version (mine and yours) are equivalent, but neither of them uses halfway vector for computing sine or tangent function. It uses $n\cdot v$ rather than $h\cdot v$ as you did in your implementation - that seems to be the mistake. I suspect you did the same mistake with the new implementation as well.
ivokabel
CC BY-SA 3.0
2016-05-30T15:24:04.623Z
I did use N dot V in my new implementation, that gave me identical results to the second image I've posted. But I'm still not clear on why the PBS course slides state that the halfway vector should be used (See: http://blog.selfshadow.com/publications/s2015-shading-course/hoffman/s2015_pbs_physics_math_slides.pdf, Slide 88).
Erwin
CC BY-SA 3.0
2016-05-30T21:28:30.040Z
Do I understand it correctly that using $h\cdot v$ instead of $n\cdot v$ was THE problem? Regarding the use of halfway vector in $G_1$: In fact it is used in both of the versions I posted (I made a mistake when constructing the LaTeX formula and wrote geomeotric normal into the first one, I'll fix it soon), but the point is that the halfway vector is not used to compute the cosine value (i.e. there is no $h\cdot v$ used).
ivokabel
CC BY-SA 3.0
2016-06-06T08:00:54.563Z
Yes, that was the problem. But my main question was: What IS the half vector used for, since it appears in the function definition. As far as I understand now it is only used in the check if H dot V is positive. Thank you for taking time to write the answers.
Erwin
CC BY-SA 3.0
2016-06-06T09:28:04.990Z
That is my understanding as well. Glad to help.
ivokabel
CC BY-SA 3.0
2017-11-03T09:37:25.893Z
@ivokabel How is the term $-1+ \sqrt{1+\alpha^2tan^2\theta}\over 2$ derived?
Arjan Singh
CC BY-SA 3.0
2016-05-28T14:49:53.177Z
2016-05-30T21:35:41.830Z
2016-05-30T21:35:41.830Z
ivokabel
<p>I'm trying to implement a microfacet BRDF in my raytracer but I'm running into some issues. A lot of the papers and articles I've read define the partial geometry term as a function of the view and half vectors: G1(v, h). However, when implementing this I got the following result: </p>

<p><a href="https://i.sstatic.net/VGIdx.jpg" rel="noreferrer"><img src="https://i.sstatic.net/VGIdx.jpg" alt="GGX Geometry term using the half vector"></a></p>

<p>(Bottom row is dielectric with roughness 1.0 - 0.0, Top row is metallic with roughness 1.0 - 0.0)</p>

<p>There's a weird highlight around the edges and a cut-off around n.l == 0. I couldn't really figure out where this comes from. I'm using Unity as a reference to check my renders so I checked their shader source to see what they use and from what I can tell their geometry term is not parametrized by the half vector at all! So I tried the same code but used to macro surface normal instead of the half vector and got the following result:</p>

<p><a href="https://i.sstatic.net/bnVxb.jpg" rel="noreferrer"><img src="https://i.sstatic.net/bnVxb.jpg" alt="GGX geometry term using the macro surface normal"></a></p>

<p>To my untrained eye this seems way closer to the desired result. But I have the feeling this is not correct? The majority of the articles I read use the half vector but not all of them. Is there a reason for this difference?</p>

<p>I use the following code as my geometry term:</p>

<pre><code>float RayTracer::GeometryGGX(const Vector3&amp; v, const Vector3&amp; l, const Vector3&amp; n, const Vector3&amp; h, float a)
{
    return G1GGX(v, h, a) * G1GGX(l, h, a);
}

float RayTracer::G1GGX(const Vector3&amp; v, const Vector3&amp; h, float a)
{
    float NoV = Util::Clamp01(cml::dot(v, h));
    float a2 = a * a;

    return (2.0f * NoV) / std::max(NoV + sqrt(a2 + (1.0f - a2) * NoV * NoV), 1e-7f);
}
</code></pre>

<p>And for reference, this is my normal distribution function:</p>

<pre><code>float RayTracer::DistributionGGX(const Vector3&amp; n, const Vector3&amp; h, float alpha)
{
    float alpha2 = alpha * alpha;
    float NoH = Util::Clamp01(cml::dot(n, h));
    float denom = (NoH * NoH * (alpha2 - 1.0f)) + 1.0f;
    return alpha2 / std::max((float)PI * denom * denom, 1e-7f);
}
</code></pre>

<shader><brdf><pbr><microfacet>
Correct form of the GGX geometry term
Erwin
# Correct form of the GGX geometry term

(Asked by: username_0 on 2016-05-24)

I'm trying to implement a microfacet BRDF in my raytracer but I'm running into some issues. A lot of the papers and articles I've read define the partial geometry term as a function of the view and half vectors: G1(v, h). However, when implementing this I got the following result:

[![GGX Geometry term using the half vector](https://i.sstatic.net/VGIdx.jpg)](https://i.sstatic.net/VGIdx.jpg)

(Bottom row is dielectric with roughness 1.0 - 0.0, Top row is metallic with roughness 1.0 - 0.0)

There's a weird highlight around the edges and a cut-off around n.l == 0. I couldn't really figure out where this comes from. I'm using Unity as a reference to check my renders so I checked their shader source to see what they use and from what I can tell their geometry term is not parametrized by the half vector at all! So I tried the same code but used to macro surface normal instead of the half vector and got the following result:

[![GGX geometry term using the macro surface normal](https://i.sstatic.net/bnVxb.jpg)](https://i.sstatic.net/bnVxb.jpg)

To my untrained eye this seems way closer to the desired result. But I have the feeling this is not correct? The majority of the articles I read use the half vector but not all of them. Is there a reason for this difference?

I use the following code as my geometry term:

```
float RayTracer::GeometryGGX(const Vector3& v, const Vector3& l, const Vector3& n, const Vector3& h, float a)
{
    return G1GGX(v, h, a) * G1GGX(l, h, a);
}

float RayTracer::G1GGX(const Vector3& v, const Vector3& h, float a)
{
    float NoV = Util::Clamp01(cml::dot(v, h));
    float a2 = a * a;

    return (2.0f * NoV) / std::max(NoV + sqrt(a2 + (1.0f - a2) * NoV * NoV), 1e-7f);
}

```

And for reference, this is my normal distribution function:

```
float RayTracer::DistributionGGX(const Vector3& n, const Vector3& h, float alpha)
{
    float alpha2 = alpha * alpha;
    float NoH = Util::Clamp01(cml::dot(n, h));
    float denom = (NoH * NoH * (alpha2 - 1.0f)) + 1.0f;
    return alpha2 / std::max((float)PI * denom * denom, 1e-7f);
}

```

**Tags:** shader, brdf, pbr, microfacet


---

## 1 answers

### Answer by username_1 on 2016-05-28. Score: 7 | Accepted answer

TL;DR: Your $G1$ formula is wrong.

---

Just to avoid confusion, I am assuming the isotropic version of the BRDF, the Smith microfacet model (as opposed to the V-cavity model), and the GGX microfacet distribution.

According to [Heitz 2014](https://hal.inria.fr/hal-00996995), the masking/shadowing term $G1$ is

$$
\chi^{+}\left(\omega\_{v}\cdot\omega\_{m}\right)
\frac{2}{1+\sqrt{1+\alpha\_{o}^{2}\tan^{2}\theta\_{v}}}
$$

and according to [Walter 2007](http://dx.doi.org/10.2312/EGWR/EGSR07/195-206), the formula is

$$
\chi^{+}\left(\frac{\omega\_{v}\cdot\omega\_{g}}{\omega\_{v}\cdot\omega\_{m}}\right)\frac{2}{1+\sqrt{1+\alpha^{2}\tan^{2}\theta\_{v}}}
$$

Where $\omega\_{m}$ is the microfacet normal direction (halfway vector), $\omega\_{g}$ is the main (geometric) normal direction (normal), $\omega\_{v}$ is the incoming or outgoing direction, $\alpha$ is the isotropic roughness parameter, and $\chi^{+}\left(a\right)$ is the positive characteristic function, or the Heaviside step function (equals one if $a>0$ and zero otherwise).

As you can notice, the half vector $\omega\_{m}$ is used only to make sure the $G1$ is zero if the geometrical configuration is forbidden. More precisely, it makes sure that the back surface of the microsurface is never visible from the $\omega\_{v}$ direction on the front side of the macrosurface and vice versa (the latter case is meaningful only when also refractions are supported). If the calling code guarantees this, then you can obviously omit this parameter. That is probably the reason why they did so in Unity.

Your implementation, on the other hand, uses the half vector to compute the cosine of the $\omega\_{v}$ direction with respect to the microfacet, which leads to computation of something else than the presented formulae.

If it is of any help, then this is my implementation of the $G1$ factor:

```
float SmithMaskingFunctionGgx(
    const Vec3f &aDir,  // the direction to compute masking for (either incoming or outgoing)
    const Vec3f &aMicrofacetNormal,
    const float  aRoughnessAlpha)
{
    PG3_ASSERT_VEC3F_NORMALIZED(aDir);
    PG3_ASSERT_VEC3F_NORMALIZED(aMicrofacetNormal);
    PG3_ASSERT_FLOAT_NONNEGATIVE(aRoughnessAlpha);

    if (aMicrofacetNormal.z <= 0)
        return 0.0f;

    const float cosThetaVM = Dot(aDir, aMicrofacetNormal);
    if ((aDir.z * cosThetaVM) < 0.0f)
        return 0.0f; // up direction is below microfacet or vice versa

    const float roughnessAlphaSqr = aRoughnessAlpha * aRoughnessAlpha;
    const float tanThetaSqr = Geom::TanThetaSqr(aDir);
    const float root = std::sqrt(1.0f + roughnessAlphaSqr * tanThetaSqr);

    const float result = 2.0f / (1.0f + root);

    PG3_ASSERT_FLOAT_IN_RANGE(result, 0.0f, 1.0f);

    return result;
}

```

> **Comments:**
> **username_0**: Thank you for your reply. I've implemented the formula you've supplied and I got identical results as with my own (when using the macrosurface normal). So it seems it's just a different form (I got it from: http://graphicrants.blogspot.nl/2013/08/specular-brdf-reference.html)  I was confused about the half vector because the SIGGRAPH 2015 PBS math course specifically state the geometry function dependant on the view, light and half vectors. So this is an error in the slides?  
> **username_1**: @username_0, now that you provided also the formula itself, it is much clearer. Next time do it right at the beginning, it helps. Yes, both version (mine and yours) are equivalent, but neither of them uses halfway vector for computing sine or tangent function. It uses $n\cdot v$ rather than $h\cdot v$ as you did in your implementation - that seems to be the mistake. I suspect you did the same mistake with the new implementation as well.  
> **username_0**: I did use N dot V in my new implementation, that gave me identical results to the second image I've posted. But I'm still not clear on why the PBS course slides state that the halfway vector should be used (See: http://blog.selfshadow.com/publications/s2015-shading-course/hoffman/s2015_pbs_physics_math_slides.pdf, Slide 88).  
> **username_1**: Do I understand it correctly that using $h\cdot v$ instead of $n\cdot v$ was THE problem? Regarding the use of halfway vector in $G_1$: In fact it is used in both of the versions I posted (I made a mistake when constructing the LaTeX formula and wrote geomeotric normal into the first one, I'll fix it soon), but the point is that the halfway vector is not used to compute the cosine value (i.e. there is no $h\cdot v$ used).  
> **username_0**: Yes, that was the problem. But my main question was: What IS the half vector used for, since it appears in the function definition. As far as I understand now it is only used in the check if H dot V is positive. Thank you for taking time to write the answers.  
> **username_1**: That is my understanding as well. Glad to help.  
> **username_2**: @username_1 How is the term $-1+ \sqrt{1+\alpha^2tan^2\theta}\over 2$ derived?  

---

