<p>I know this is a pretty old question, but I am wondering if the problem does not come from sampling the whole hemisphere of the light, instead of only the visible portion of it from the hit location's point of view (see this : schuttejoe.github.io/post/arealightsampling) ? The visible solid angle is only 2*PI*(1 - cos(theta)) instead of 2*PI.</p>

CC BY-SA 4.0
2021-05-23T13:00:26.533Z
2021-05-24T19:34:00.800Z
2021-05-24T19:34:00.800Z
Ewaren
<p>I am trying to understand why my explicit light sampling implemetation does not produce the same results as my naive path tracer. Here are my results :</p>

<p>Naive Version :</p>

<p><a href="https://i.sstatic.net/B7O0q.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/B7O0q.jpg" alt="Naive version"></a></p>

<p>Explicit light sampling version :</p>

<p><a href="https://i.sstatic.net/MSHoh.jpg" rel="nofollow noreferrer"><img src="https://i.sstatic.net/MSHoh.jpg" alt="enter image description here"></a></p>

<p>Diff :</p>

<p><a href="https://i.sstatic.net/LZxDB.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/LZxDB.png" alt="Diff with tweaked contrast"></a></p>

<p>It's not that different, but the top and back planes have a stronger "glow" in the explicit light version. The shadow also seems softer in the explicit light version.</p>

<p>What I made for the explicit light sampling, based on PBRT and <a href="https://computergraphics.stackexchange.com/questions/5152/progressive-path-tracing-with-explicit-light-sampling">this</a> answer :</p>

<pre><code>vec3 pathTrace( const Ray&amp; ray, int bounces, const std::vector&lt; std::pair&lt; Plane, Material &gt; &gt;&amp; planes, const std::vector&lt; std::pair&lt; Sphere, Material &gt; &gt;&amp; spheres, bool firstBounce = false )
{
    if( bounces == 0 )
        return vec3( 0.0 );

    Hit hit = sceneIntersects( ray, planes, spheres );
    if( !hit._any )
        return vec3( 0.0 );

    // Cast a new ray from the hit position to a random direction on the hemisphere
    Ray secondRay( hit._position, sampleHemisphereUniform( hit._normal ) );

    vec3 radiance = pathTrace( secondRay, bounces - 1, planes, spheres );

    vec3 brdf = hit._material._albedo / Pi;
    double pdf = 1.0 / ( 2.0 * Pi );

#ifdef NAIVE
    return hit._material._emissive + radiance * brdf * dot( hit._normal, secondRay._direction ) / pdf;
#else
    // Remove the emission since its taken into account in the directLighting part
    vec3 indirectLighting = radiance * brdf * dot( hit._normal, secondRay._direction ) / pdf;

    if( firstBounce &amp;&amp; hit._isLight )
    {
        // Unlesss we hit a light at first bounce, to avoid black lights
        indirectLighting += hit._material._emissive;
    }

    vec3 directLighting;

    // For testing purposes, the sphere area light is hardcoded
    const vec3 lightPosition( 0.0, 8.0, -2.5 );
    const double lightRadius = 1.1;
    const vec3 lightEmission( 4.0 );

    // Sample a random point on an hemisphere oriented to the point instead of a sphere
    vec3 lightSample = sampleHemisphereUniform( normalize( hit._position - lightPosition ) );
    lightSample *= lightRadius;
    lightSample += lightPosition;

    vec3 lightDir = normalize( lightSample - hit._position );

    // Cast a ray to the light
    Hit hitLight = sceneIntersects( Ray( hit._position, lightDir ), planes, spheres );
    if( !hit._isLight &amp;&amp; hitLight._any &amp;&amp; hitLight._isLight )
    {
        // Use the area form of the rendering equation
        double G = dot( hit._normal, lightDir ) * dot( hitLight._normal, -lightDir ) / length2( hitLight._position - hit._position );

        // The light is sampled using an hemisphere, so the area is the area of an hemisphere
        double lightArea = 2.0 * Pi * 1.1 * 1.1;
        double lightPdf = 1.0 / lightArea;

        directLighting = lightEmission * brdf * G / lightPdf;
    }

    return directLighting + indirectLighting;
#endif
}
</code></pre>

<p><a href="https://computergraphics.stackexchange.com/questions/4288/path-weight-for-direct-light-sampling">This</a> answer says that because I am sampling twice, I should divide my twice the number of samples, but it gives me even more different results so I'm not sure if I to do that or not. Anyway I test both every time.</p>

<p>I was also wondering if it was because 1 bounce of the naive path tracer is not equal to one bounce of the explicit light sampling version (The first bounce of naive will only output color when it hits a light, while the explicit light version will output direct lighting), so I tested with 30 bounces, and bounces - 1, and got almost no difference. Same thing with samples. I am just not able to get the light sampling version with the naive version, and to me it's not a convergence time issue, since the naive version doesn't change much after a 3 min render (other than the noise, of course), so I'm pretty sure the images converges to a different result. </p>

<p>I don't know if I need multiple importance sampling here, I'd say no because I am not trying to cast a light ray based on the brdf (for now) but I'm again not sure.</p>

<p>I'm also not sure if I can sample the sphere using an hemisphere. I did that because I had the hemisphere sampling code ready. But intuitively it seems good to me, because the spherical light would have twice of the samples occluded, which is taken into account when dividing by the pdf of an hemisphere instead of the pdf of a sphere.</p>

CC BY-SA 4.0
2019-03-30T17:38:45.533Z
I gave it a quick look and I don't see any checks that account for double dipping the light source contribution. Like the answer said, since you are sampling light sources explicitly now, you either need to divide by 2 if you are double dipping or simply just ignore the contribution when you hit a light source by random sampling the hemisphere
gallickgunner
CC BY-SA 4.0
2019-03-30T18:07:15.730Z
I only add the light emission if the ray hits a light on the first bounce. Otherwise, the light emission only comes from the direct lighting part.
Aulaulz
CC BY-SA 4.0
2019-03-30T23:01:49.013Z
okay, btw did you took both images at the same samples? cuz then it wouldn't be a fair comparison. You said something about 30 bounces and `bounces-1` I don't understand why `-1` The NEE version is substantially higher (in image quality) So you should be comparing something like a 4k sample naive image with a maybe 100 sample NEE image.
gallickgunner
CC BY-SA 4.0
2019-04-01T20:30:17.937Z
I tested it with 10k samples with the naive version and 400 with the explicit one. Don't mind the -1 thing, it was just a test.
Aulaulz
CC BY-SA 4.0
2021-04-26T15:50:07.093Z
I know this is a pretty old question, but I am wondering if the problem does not come from sampling the whole hemisphere of the light, instead of only the visible portion of it from the hit location's point of view (see this : https://schuttejoe.github.io/post/arealightsampling/) ? The visible solid angle is only 2*PI*(1 - cos(theta)) instead of 2*PI.
Ewaren
CC BY-SA 4.0
2021-05-22T07:26:45.417Z
@Ewaren I took a look into my old projects folder and tried what you said. You were exactly right. Tbh I wasn't expecting to find an answer one day, thank you so much!
Aulaulz
CC BY-SA 4.0
2021-05-23T13:00:19.790Z
@Aulaulz that's great ! I'll post it as an answer then, happy to be able to help you even though I'm pretty late :D
Ewaren
CC BY-SA 4.0
2021-06-16T01:16:39.197Z
No worries. You just motivated me to get back to my pathtracer haha!
Aulaulz
<pathtracing>
Should I expect differences with explicit light sampling other that the noise?
Aulaulz
# Should I expect differences with explicit light sampling other that the noise?

(Asked by: username_0 on 2019-03-30)

I am trying to understand why my explicit light sampling implemetation does not produce the same results as my naive path tracer. Here are my results :

Naive Version :

[![Naive version](https://i.sstatic.net/B7O0q.jpg)](https://i.sstatic.net/B7O0q.jpg)

Explicit light sampling version :

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

Diff :

[![Diff with tweaked contrast](https://i.sstatic.net/LZxDB.png)](https://i.sstatic.net/LZxDB.png)

It's not that different, but the top and back planes have a stronger "glow" in the explicit light version. The shadow also seems softer in the explicit light version.

What I made for the explicit light sampling, based on PBRT and [this](https://computergraphics.stackexchange.com/questions/5152/progressive-path-tracing-with-explicit-light-sampling) answer :

```
vec3 pathTrace( const Ray& ray, int bounces, const std::vector< std::pair< Plane, Material > >& planes, const std::vector< std::pair< Sphere, Material > >& spheres, bool firstBounce = false )
{
    if( bounces == 0 )
        return vec3( 0.0 );

    Hit hit = sceneIntersects( ray, planes, spheres );
    if( !hit._any )
        return vec3( 0.0 );

    // Cast a new ray from the hit position to a random direction on the hemisphere
    Ray secondRay( hit._position, sampleHemisphereUniform( hit._normal ) );

    vec3 radiance = pathTrace( secondRay, bounces - 1, planes, spheres );

    vec3 brdf = hit._material._albedo / Pi;
    double pdf = 1.0 / ( 2.0 * Pi );

#ifdef NAIVE
    return hit._material._emissive + radiance * brdf * dot( hit._normal, secondRay._direction ) / pdf;
#else
    // Remove the emission since its taken into account in the directLighting part
    vec3 indirectLighting = radiance * brdf * dot( hit._normal, secondRay._direction ) / pdf;

    if( firstBounce && hit._isLight )
    {
        // Unlesss we hit a light at first bounce, to avoid black lights
        indirectLighting += hit._material._emissive;
    }

    vec3 directLighting;

    // For testing purposes, the sphere area light is hardcoded
    const vec3 lightPosition( 0.0, 8.0, -2.5 );
    const double lightRadius = 1.1;
    const vec3 lightEmission( 4.0 );

    // Sample a random point on an hemisphere oriented to the point instead of a sphere
    vec3 lightSample = sampleHemisphereUniform( normalize( hit._position - lightPosition ) );
    lightSample *= lightRadius;
    lightSample += lightPosition;

    vec3 lightDir = normalize( lightSample - hit._position );

    // Cast a ray to the light
    Hit hitLight = sceneIntersects( Ray( hit._position, lightDir ), planes, spheres );
    if( !hit._isLight && hitLight._any && hitLight._isLight )
    {
        // Use the area form of the rendering equation
        double G = dot( hit._normal, lightDir ) * dot( hitLight._normal, -lightDir ) / length2( hitLight._position - hit._position );

        // The light is sampled using an hemisphere, so the area is the area of an hemisphere
        double lightArea = 2.0 * Pi * 1.1 * 1.1;
        double lightPdf = 1.0 / lightArea;

        directLighting = lightEmission * brdf * G / lightPdf;
    }

    return directLighting + indirectLighting;
#endif
}

```

[This](https://computergraphics.stackexchange.com/questions/4288/path-weight-for-direct-light-sampling) answer says that because I am sampling twice, I should divide my twice the number of samples, but it gives me even more different results so I'm not sure if I to do that or not. Anyway I test both every time.

I was also wondering if it was because 1 bounce of the naive path tracer is not equal to one bounce of the explicit light sampling version (The first bounce of naive will only output color when it hits a light, while the explicit light version will output direct lighting), so I tested with 30 bounces, and bounces - 1, and got almost no difference. Same thing with samples. I am just not able to get the light sampling version with the naive version, and to me it's not a convergence time issue, since the naive version doesn't change much after a 3 min render (other than the noise, of course), so I'm pretty sure the images converges to a different result.

I don't know if I need multiple importance sampling here, I'd say no because I am not trying to cast a light ray based on the brdf (for now) but I'm again not sure.

I'm also not sure if I can sample the sphere using an hemisphere. I did that because I had the hemisphere sampling code ready. But intuitively it seems good to me, because the spherical light would have twice of the samples occluded, which is taken into account when dividing by the pdf of an hemisphere instead of the pdf of a sphere.

**Tags:** pathtracing

> **Comments:**
> **username_1**: I gave it a quick look and I don't see any checks that account for double dipping the light source contribution. Like the answer said, since you are sampling light sources explicitly now, you either need to divide by 2 if you are double dipping or simply just ignore the contribution when you hit a light source by random sampling the hemisphere  
> **username_0**: I only add the light emission if the ray hits a light on the first bounce. Otherwise, the light emission only comes from the direct lighting part.  
> **username_1**: okay, btw did you took both images at the same samples? cuz then it wouldn't be a fair comparison. You said something about 30 bounces and `bounces-1` I don't understand why `-1` The NEE version is substantially higher (in image quality) So you should be comparing something like a 4k sample naive image with a maybe 100 sample NEE image.  
> **username_0**: I tested it with 10k samples with the naive version and 400 with the explicit one. Don't mind the -1 thing, it was just a test.  
> **username_2**: I know this is a pretty old question, but I am wondering if the problem does not come from sampling the whole hemisphere of the light, instead of only the visible portion of it from the hit location's point of view (see this : https://schuttejoe.github.io/post/arealightsampling/) ? The visible solid angle is only 2*PI*(1 - cos(theta)) instead of 2*PI.  
> **username_0**: @username_2 I took a look into my old projects folder and tried what you said. You were exactly right. Tbh I wasn't expecting to find an answer one day, thank you so much!  
> **username_2**: @username_0 that's great ! I'll post it as an answer then, happy to be able to help you even though I'm pretty late :D  
> **username_0**: No worries. You just motivated me to get back to my pathtracer haha!  

---

## 1 answers

### Answer by username_2 on 2021-05-23. Score: 4 | Accepted answer

I know this is a pretty old question, but I am wondering if the problem does not come from sampling the whole hemisphere of the light, instead of only the visible portion of it from the hit location's point of view (see this : schuttejoe.github.io/post/arealightsampling) ? The visible solid angle is only 2\*PI\*(1 - cos(theta)) instead of 2\*PI.


---

