2
$\begingroup$

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

Explicit light sampling version :

enter image description here

Diff :

Diff with tweaked contrast

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 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 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.

$\endgroup$
8
  • $\begingroup$ 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 $\endgroup$ Commented Mar 30, 2019 at 17:38
  • 3
    $\begingroup$ 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. $\endgroup$
    – Ewaren
    Commented Apr 26, 2021 at 15:50
  • 1
    $\begingroup$ @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! $\endgroup$
    – Aulaulz
    Commented May 22, 2021 at 7:26
  • 1
    $\begingroup$ @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 $\endgroup$
    – Ewaren
    Commented May 23, 2021 at 13:00
  • 1
    $\begingroup$ No worries. You just motivated me to get back to my pathtracer haha! $\endgroup$
    – Aulaulz
    Commented Jun 16, 2021 at 1:16

1 Answer 1

4
$\begingroup$

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.

$\endgroup$

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.