# Should I expect differences with explicit light sampling other that the noise?

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 :

Explicit light sampling version :

Diff :

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

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