I'm currently working working on a unbiased path tracer. To test how accurate it is. I created a scene in my path tracer and LuxRender then compare the rendered image. I found that the shadow in the image that my path tracer rendered is too dark. Why? Any possible reason?

Also, I have implemented Gamma 2.2 in my renderer. So it won't be the issue of gamma correction. And, recursive path tracing IS enabled. both image are rendered in global illumination.

Here are the rendering result from LuxRender and my own renderer.(Please ignore the back triangle(light source) in Lux. It's how Lux behaves.)

---- Edit ----
Edit: Here are the trace() function(get radiance)

float4 Renderer::trace(Ray ray, Scene* scene)
    const int BOUNCE_DEPTH = getBounceDepth();
    //Russian roulette: starting at depth 5
    const float FACTOR_CUT_OFF = 1e-5;

    float4 factor(1.0f,1.0f,1.0f,1.0f);
    float4 renderedColor;
    Ray currentRay = ray;
    float4 lastNormal = ray.direction;
    float rrFactor = 1.0f;

    for(int i=0;i<BOUNCE_DEPTH||BOUNCE_DEPTH < 0;i++)//To ignore bounce limit. set bounceDepth to -1
            const float stopProbability = 0.1f;
            if(((float)rand()/RAND_MAX) < (1.0-stopProbability))
            vrrFactor *= 1.0f/(1.0f-stopProbability);
            //cout << rrFactor << endl;

        //stop bouncing if factor is too small.
        //This is a biased method. Remove this if you want unbiased rendering
        if(factor.x + factor.y + factor.z <= FACTOR_CUT_OFF)

        RayHit hit = acclerator->raycast(currentRay);
        if(hit.distance > 0)
            int index = hit.index;
            Triangle emit(scene->emitColor[scene->indices[index*3]],
            Triangle triangle(scene->vertices[scene->indices[index*3]],
            Triangle reflectColor(scene->reflectColor[scene->indices[index*3]],

            //if(i > 1)//Only collect indirect lighting
            renderedColor += factor*emit.interpolation(hit.u,hit.v)*rrFactor;
            factor *= reflectColor.interpolation(hit.u,hit.v);

            //randomly sample on a hemisphere
            Ray reflecRay;
            reflecRay = createRandomReflect(currentRay, triangle,
                currentRay.origin + normalize(currentRay.direction)*hit.distance);

            float4 triangleNormal = triangle.normal();
            if(dot(triangleNormal,currentRay.direction) < 0)
                triangleNormal = -triangleNormal;

            currentRay = reflecRay;
            lastNormal = triangle.normal();
            if(dot(currentRay.direction,lastNormal) < 0)
                lastNormal = -lastNormal;
            factor *= dot(normalize(currentRay.direction),lastNormal);//Difduse BRDF


    return renderedColor;

And the render() function (generate image)

void Renderer::render(Scene* scene)
    int pixelCount = bufferWidth*bufferHeight;

    high_resolution_clock::time_point t1 = high_resolution_clock::now();

    int renderIndex = 0;


    const int SAMPLE_NUM = getSampleNum();

    float4 origin = scene->camera.position
        + 2.0f*scene->camera.right/(float)scene->camera.width
        + 2.0f*scene->camera.up/(float)scene->camera.height;

    #pragma omp parallel for schedule(dynamic)
    for(int i=0;i<pixelCount;i++)
        float4 renderedColor = float4(0.0f,0.0f,0.0f,0.0f);
        //Create camera ray
        int x = i % bufferWidth;
        int y = i / bufferWidth;

        float4 rayVec = (x/(float)scene->camera.width-0.5f)*scene->camera.right*scene->camera.pixelAspectRatio
            + (0.5f-y/(float)scene->camera.height)*scene->camera.up
            + scene->camera.direction;
        for(int j=0;j<SAMPLE_NUM;j++)
            Ray ray(origin, rayVec);
            renderedColor += trace(ray,scene);
        renderedColor /= (float)SAMPLE_NUM;

        //simple gamma correction
        const float gamma = 2.2;
        const float inversedGamma = 1.0f/gamma;
        float4 correctedColor = pow(renderedColor,float4(inversedGamma,inversedGamma,inversedGamma,0));
        ((float4*)frameBuffer)[i] = correctedColor;

        #pragma omp atomic

        #pragma omp critical
        if(renderIndex%500 == 0)
            cout << "Rendering " << renderIndex << "/" << pixelCount << ". " << (float)renderIndex*100.0/pixelCount << "\r";

    high_resolution_clock::time_point t2 = high_resolution_clock::now();
    double elapsed = duration_cast<duration<double>>( t2 - t1 ).count();
    cout << endl << "elapsed: " << elapsed << " ," << pixelCount*SAMPLE_NUM/elapsed/1000/1000 << " MSamples/s"<< endl;

---- End Edit ----

Scene Rendered by LuxRender (PBRT gives me the same result)
Renderer: Sampler, Sampler : metropolis, (Other settings are defalut) Scene Rendered by LuxRender

The same scene rendered my by renderer. Note the scene is a bit darker and shadow is way too dark. Same Scene rendered my by renderer

  • $\begingroup$ How sure are you that the light source is parameterized in the same way? $\endgroup$
    – David Kuri
    Jan 22, 2016 at 9:27
  • $\begingroup$ Pretty sure, because and Lux uses diffuse IES data on light sources by default and I only support diffuse material. Also, both light sources have the same strength/color of 6.0. $\endgroup$
    – Mary Chang
    Jan 22, 2016 at 9:49
  • 1
    $\begingroup$ Still it might be that you are using a different photometric quantity somewhere, that your tone mapping is still different despite Gamma 2.2, that you're missing a pi somewhere, or any other reason. It's hard to tell, please give some more details about your shading, show some code etc. Besides, the camera is not the same in both images, you might wanna adjust that too :) $\endgroup$
    – David Kuri
    Jan 22, 2016 at 10:20
  • $\begingroup$ I have edited in some code. Hopefully it helps. Also, here is the whole repo. More code could be found here. (Please use the "dev" branch. It's where all the new codes are.) bitbucket.org/Seanstone5923/simpleray5cpu. OK, I'll fix the camera as soon as possible. $\endgroup$
    – Mary Chang
    Jan 22, 2016 at 15:03
  • $\begingroup$ I'm afraid I dont know enough about it to be really helpful, however, I would probably try to adjust the light intensity so the images look equal, then see with which factor you needed, maybe it can give a clue as what went wrong. Or maybe you cant get it to look the same, like the whole image gets to light to get the centre the right brightness, then maybe some angle calculation is not the same.. just some ideas to try out $\endgroup$ Jan 25, 2016 at 19:11

1 Answer 1


I don't know whether this is the only cause of your problems, but the Russian roulette is messed up for sure. stopProbability acts as the survivalProbability in your code; therefore, you compensate the estimate with a different factor than you should, which makes the result heavily biased.

I would put the code as follows to make it more comprehensible:

const float survivalProbability = 0.9f;
if(((float)rand()/RAND_MAX) > survivalProbability)
rrFactor *= 1.0f/survivalProbability;

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.