<p>I'm making a program that illuminates a sphere using an environment map. For each fragment in the sphere, I compute a many random directions in the hemisphere along the surface normal (no importance sampling), and then sample the environment map. Then I evaluate the GGX BRDF for each sample.</p>
<p>The problem I have is that it looks too dark, and I'm not sure why.</p>
<p><a href="https://i.sstatic.net/Nxoy9.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/Nxoy9.jpg" alt="enter image description here" /></a></p>
<p>This is for a metallic sphere with low roughness. For such a low roughness, it could make sense that the GGX lobe is so narrow that we are missing it. But things don't improve with higher roughness.</p>
<p>When I increase the roughness it goes completely dark:</p>
<p><a href="https://i.sstatic.net/5FxOI.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/5FxOI.png" alt="enter image description here" /></a></p>
<p>If I multiply the resulting color by 10 I can tell there is something:</p>
<p><a href="https://i.sstatic.net/ynC7W.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/ynC7W.png" alt="enter image description here" /></a></p>
<p>This is my main shader function:</p>
<pre><code>vec3 bruteForce()
{
    vec3 N = normalize(v_normal);
    vec3 V = normalize(u_camPos - v_pos);
    float NoV = max(0.0001, dot(N, V));

    vec3 F0 = mix(vec3(0.04), u_albedo, u_metallic);
    vec3 F = fresnelSchlick(NoV, F0);
    vec3 k_spec = F;
    vec3 k_diff = (1 - k_spec) * (1 - u_metallic);

    vec3 specular = vec3(0.0, 0.0, 0.0);
    for(uint iSample = 0u; iSample &lt; u_numSamples; iSample++)
    {
        uvec3 seedUInt = pcg_uvec3_uvec3(uvec3(gl_FragCoord.x, gl_FragCoord.y, iSample));
        vec2 seed2 = vec2(makeFloat01(seedUInt.x), makeFloat01(seedUInt.y));
        vec3 L = uniformSample(seed2, N);
        vec3 H = normalize(V + L);
        vec3 env = texture(u_envTex, L).rgb;

        float NoL = max(0.0001, dot(N, L));
        float NoH = max(0.0001, dot(N, H));
        float NoH2 = NoH * NoH;
        float rough4 = u_rough2*u_rough2;
        float q = NoH2 * (rough4 - 1.0) + 1.0;
        float D = rough4 / (PI * q*q);
        float G = ggx_G_smith(NoV, NoL, u_rough2);
        vec3 fr = F * G * D / (4 * NoV * NoL);
        specular += fr * env * NoL;
    }
    specular /= float(u_numSamples);

    return specular;
}
</code></pre>
<p>Here are the other helper functions:</p>
<pre><code>vec3 fresnelSchlick(float NoV, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0);
}

float ggx_G(float NoV, float rough2)
{
  float rough4 = rough2 * rough2;
  return 2.0 * (NoV) /
    (NoV + sqrt(rough4 + (1.0 - rough4) * NoV*NoV));
}

// geometry term
float ggx_G_smith(float NoV, float NoL, float rough2)
{
  return ggx_G(NoV, rough2) * ggx_G(NoL, rough2);
}

// this is a hash function for generating pseudo-random numbers. Taken from here: http://jcgt.org/published/0009/03/02/
uvec3 pcg_uvec3_uvec3(uvec3 v)
{
    v = v * 1664525u + 1013904223u;
    v.x += v.y*v.z;
    v.y += v.z*v.x;
    v.z += v.x*v.y;
    v = v ^ (v&gt;&gt;16u);
    v.x += v.y*v.z;
    v.y += v.z*v.x;
    v.z += v.x*v.y;
    return v;
}

// Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
// https://stackoverflow.com/a/17479300/1754322
float makeFloat01( uint m ) {
    const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
    const uint ieeeOne      = 0x3F800000u; // 1.0 in IEEE binary32

    m &amp;= ieeeMantissa;                     // Keep only mantissa bits (fractional part)
    m |= ieeeOne;                          // Add fractional part to 1.0

    float  f = uintBitsToFloat( m );       // Range [1:2]
    return f - 1.0;                        // Range [0:1]
}
</code></pre>
<p>Can you help me find the problem please?</p>

CC BY-SA 4.0
2020-11-23T16:41:58.600Z
Do you need to divide by the probablity of taking the sample? In the uniform case it would be divide by 1/2pi?
Peter
CC BY-SA 4.0
2020-11-23T18:57:53.317Z
@Peter Thanks for your comment. Why do I have to divide by 1/pi? I know that with importance sampling you need to divide by the PDF (probability distribution function), but I thought with uniform sampling you don't have to?
user1754322
CC BY-SA 4.0
2020-11-24T15:13:34.180Z
So far your code you have taken random samples and divided by the number of samples. If you think about what this is doing, you are getting an approximation of the average light from each ray hitting the point. However you don't want the ligh of just one ray you want the total light from all directions. Luckily we know that in discrete maths TOTAL = AVERAGE * COUNT, it is also true here but we are collecting over the area of the hemisphere, 2*PI. So to get the total light in you need to multiply your approxmate average by 2*PI. Same as dividing by 1 / (2*PI).
Peter
CC BY-SA 4.0
2020-11-24T15:17:17.190Z
Another way to think of it: you are right that in importance sampling you need to divide by the PDF, and uniform sampling is just a special case of importance sampling where every sample is equally important. You would still need to divide by the PDF.
Peter
CC BY-SA 4.0
2020-11-25T13:28:13.433Z
Did you look at?: While accumulating specular, the last statement  computes the average from the samples, but it "looks" like some of the samples are effectively zero from the line max(0.0001, dot(N,L)), so the average has to high of a weight...and suggests that many of the random samples are not useful. (since the dot product is near or below zero).
pmw1234
CC BY-SA 4.0
2020-11-25T13:54:57.983Z
@pmw1234 Thanks for your comment. The samples are generated inside the hemisphere in the direction of N. So that "max" doesn't have any effect (the dot product won't have negative values). I forgot to remove it when I copy-pasted from my importance sampling implementation.
user1754322
<glsl><lighting><brdf><ggx>
IBL looks too dark with uniform sampling
user1754322
# IBL looks too dark with uniform sampling

(Asked by: username_0 on 2020-11-17)

I'm making a program that illuminates a sphere using an environment map. For each fragment in the sphere, I compute a many random directions in the hemisphere along the surface normal (no importance sampling), and then sample the environment map. Then I evaluate the GGX BRDF for each sample.

The problem I have is that it looks too dark, and I'm not sure why.

[![enter image description here](https://i.sstatic.net/Nxoy9.jpg)](https://i.sstatic.net/Nxoy9.jpg)

This is for a metallic sphere with low roughness. For such a low roughness, it could make sense that the GGX lobe is so narrow that we are missing it. But things don't improve with higher roughness.

When I increase the roughness it goes completely dark:

[![enter image description here](https://i.sstatic.net/5FxOI.png)](https://i.sstatic.net/5FxOI.png)

If I multiply the resulting color by 10 I can tell there is something:

[![enter image description here](https://i.sstatic.net/ynC7W.png)](https://i.sstatic.net/ynC7W.png)

This is my main shader function:

```
vec3 bruteForce()
{
    vec3 N = normalize(v_normal);
    vec3 V = normalize(u_camPos - v_pos);
    float NoV = max(0.0001, dot(N, V));

    vec3 F0 = mix(vec3(0.04), u_albedo, u_metallic);
    vec3 F = fresnelSchlick(NoV, F0);
    vec3 k_spec = F;
    vec3 k_diff = (1 - k_spec) * (1 - u_metallic);

    vec3 specular = vec3(0.0, 0.0, 0.0);
    for(uint iSample = 0u; iSample < u_numSamples; iSample++)
    {
        uvec3 seedUInt = pcg_uvec3_uvec3(uvec3(gl_FragCoord.x, gl_FragCoord.y, iSample));
        vec2 seed2 = vec2(makeFloat01(seedUInt.x), makeFloat01(seedUInt.y));
        vec3 L = uniformSample(seed2, N);
        vec3 H = normalize(V + L);
        vec3 env = texture(u_envTex, L).rgb;

        float NoL = max(0.0001, dot(N, L));
        float NoH = max(0.0001, dot(N, H));
        float NoH2 = NoH * NoH;
        float rough4 = u_rough2*u_rough2;
        float q = NoH2 * (rough4 - 1.0) + 1.0;
        float D = rough4 / (PI * q*q);
        float G = ggx_G_smith(NoV, NoL, u_rough2);
        vec3 fr = F * G * D / (4 * NoV * NoL);
        specular += fr * env * NoL;
    }
    specular /= float(u_numSamples);

    return specular;
}

```

Here are the other helper functions:

```
vec3 fresnelSchlick(float NoV, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(1.0 - NoV, 5.0);
}

float ggx_G(float NoV, float rough2)
{
  float rough4 = rough2 * rough2;
  return 2.0 * (NoV) /
    (NoV + sqrt(rough4 + (1.0 - rough4) * NoV*NoV));
}

// geometry term
float ggx_G_smith(float NoV, float NoL, float rough2)
{
  return ggx_G(NoV, rough2) * ggx_G(NoL, rough2);
}

// this is a hash function for generating pseudo-random numbers. Taken from here: http://jcgt.org/published/0009/03/02/
uvec3 pcg_uvec3_uvec3(uvec3 v)
{
    v = v * 1664525u + 1013904223u;
    v.x += v.y*v.z;
    v.y += v.z*v.x;
    v.z += v.x*v.y;
    v = v ^ (v>>16u);
    v.x += v.y*v.z;
    v.y += v.z*v.x;
    v.z += v.x*v.y;
    return v;
}

// Construct a float with half-open range [0:1] using low 23 bits.
// All zeroes yields 0.0, all ones yields the next smallest representable value below 1.0.
// https://stackoverflow.com/a/17479300/1754322
float makeFloat01( uint m ) {
    const uint ieeeMantissa = 0x007FFFFFu; // binary32 mantissa bitmask
    const uint ieeeOne      = 0x3F800000u; // 1.0 in IEEE binary32

    m &= ieeeMantissa;                     // Keep only mantissa bits (fractional part)
    m |= ieeeOne;                          // Add fractional part to 1.0

    float  f = uintBitsToFloat( m );       // Range [1:2]
    return f - 1.0;                        // Range [0:1]
}

```

Can you help me find the problem please?

**Tags:** glsl, lighting, brdf, ggx

> **Comments:**
> **username_1**: Do you need to divide by the probablity of taking the sample? In the uniform case it would be divide by 1/2pi?  
> **username_0**: @username_1 Thanks for your comment. Why do I have to divide by 1/pi? I know that with importance sampling you need to divide by the PDF (probability distribution function), but I thought with uniform sampling you don't have to?  
> **username_1**: So far your code you have taken random samples and divided by the number of samples. If you think about what this is doing, you are getting an approximation of the average light from each ray hitting the point. However you don't want the ligh of just one ray you want the total light from all directions. Luckily we know that in discrete maths TOTAL = AVERAGE * COUNT, it is also true here but we are collecting over the area of the hemisphere, 2*PI. So to get the total light in you need to multiply your approxmate average by 2*PI. Same as dividing by 1 / (2*PI).  
> **username_1**: Another way to think of it: you are right that in importance sampling you need to divide by the PDF, and uniform sampling is just a special case of importance sampling where every sample is equally important. You would still need to divide by the PDF.  
> **username_2**: Did you look at?: While accumulating specular, the last statement  computes the average from the samples, but it "looks" like some of the samples are effectively zero from the line max(0.0001, dot(N,L)), so the average has to high of a weight...and suggests that many of the random samples are not useful. (since the dot product is near or below zero).  
> **username_0**: @username_2 Thanks for your comment. The samples are generated inside the hemisphere in the direction of N. So that "max" doesn't have any effect (the dot product won't have negative values). I forgot to remove it when I copy-pasted from my importance sampling implementation.  

---

## 0 answers

