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

<p>I would put the code as follows to make it more comprehensible:</p>

<pre><code>const float survivalProbability = 0.9f;
if(((float)rand()/RAND_MAX) &gt; survivalProbability)
    break;
rrFactor *= 1.0f/survivalProbability;
</code></pre>

CC BY-SA 3.0
2016-01-28T13:15:28.897Z
2016-01-28T13:24:04.733Z
2016-01-28T13:24:04.733Z
ivokabel
<p>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?</p>

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

<p>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.)</p>

<p>---- Edit ----<br>
Edit: Here are the trace() function(get radiance)<br></p>

<pre class="lang-c++ prettyprint-override"><code>float4 Renderer::trace(Ray ray, Scene* scene)
{
    const int BOUNCE_DEPTH = getBounceDepth();
    //Russian roulette: starting at depth 5
    const int RUSSIAN_ROULETTE_START_DEPTH = 16;
    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&lt;BOUNCE_DEPTH||BOUNCE_DEPTH &lt; 0;i++)//To ignore bounce limit. set bounceDepth to -1
    {
        if(i &gt;= RUSSIAN_ROULETTE_START_DEPTH)
        {
            const float stopProbability = 0.1f;
            if(((float)rand()/RAND_MAX) &lt; (1.0-stopProbability))
                break;
            vrrFactor *= 1.0f/(1.0f-stopProbability);
            //cout &lt;&lt; rrFactor &lt;&lt; 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 &lt;= FACTOR_CUT_OFF)
            break;

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

            //if(i &gt; 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) &lt; 0)
                triangleNormal = -triangleNormal;

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

    }
    else
        break;
    }

    return renderedColor;
}
</code></pre>

<p>And the render() function (generate image)</p>

<pre class="lang-c++ prettyprint-override"><code>void Renderer::render(Scene* scene)
{
    //srand(5000);
    memset(frameBuffer,0,bufferWidth*bufferHeight*sizeof(float4));
    int pixelCount = bufferWidth*bufferHeight;

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

    int renderIndex = 0;

    acclerator-&gt;setScene(scene);
    acclerator-&gt;build();

    const int SAMPLE_NUM = getSampleNum();

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

    #pragma omp parallel for schedule(dynamic)
    for(int i=0;i&lt;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-&gt;camera.width-0.5f)*scene-&gt;camera.right*scene-&gt;camera.pixelAspectRatio
            + (0.5f-y/(float)scene-&gt;camera.height)*scene-&gt;camera.up
            + scene-&gt;camera.direction;
        for(int j=0;j&lt;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
            renderIndex++;

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

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

<p>---- End Edit ----<br></p>

<p>Scene Rendered by LuxRender (PBRT gives me the same result)<br>
Renderer: Sampler, Sampler : metropolis, (Other settings are defalut)
<a href="https://i.sstatic.net/tsd5v.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/tsd5v.png" alt="Scene Rendered by LuxRender"></a></p>

<p>The same scene rendered my by renderer. Note the scene is a bit darker and shadow is way too dark.
<a href="https://i.sstatic.net/A2knm.png" rel="nofollow noreferrer"><img src="https://i.sstatic.net/A2knm.png" alt="Same Scene rendered my by renderer"></a></p>

CC BY-SA 3.0
2016-01-22T09:27:04.150Z
How sure are you that the light source is parameterized in the same way?
David Kuri
CC BY-SA 3.0
2016-01-22T09:49:04.440Z
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.
Mary Chang
CC BY-SA 3.0
2016-01-22T10:20:14.420Z
Still it might be that you are using a different [photometric quantity](http://mentalraytips.blogspot.de/2007/03/understanding-photometric-and.html) somewhere, that your tone mapping is still different despite Gamma 2.2, that you're [missing a pi](https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/) 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 :)
David Kuri
CC BY-SA 3.0
2016-01-22T15:03:49.677Z
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.) https://bitbucket.org/Seanstone5923/simpleray5cpu. OK, I'll fix the camera as soon as possible.
Mary Chang
CC BY-SA 3.0
2016-01-25T19:11:02.740Z
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
Emile Vrijdags
CC BY-SA 3.0
2016-01-26T13:31:53.990Z
Have you tried setting `FACTOR_CUT_OFF` to 0 (introduces bias, LuxRender is unbiased)? Have you tried excluding techniques like Russian Roulette, to see where the culprit may be?
David Kuri
CC BY-SA 3.0
2016-01-26T15:10:53.090Z
Yes, I did test that. In fact, the image I shown is rendered under a unbiased condition. Also, Russian Roulette is enabled in the image that I shown. I have set `BOUNCE_DEPTH` to -1(which makes my renderer to ignore bounce limits).
Mary Chang
<raytracing><rendering>
Shadow appears to be too dark in my ray tracer
Mary Chang
# Shadow appears to be too dark in my ray tracer

(Asked by: username_0 on 2016-01-22)

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)

```c++
float4 Renderer::trace(Ray ray, Scene* scene)
{
    const int BOUNCE_DEPTH = getBounceDepth();
    //Russian roulette: starting at depth 5
    const int RUSSIAN_ROULETTE_START_DEPTH = 16;
    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
    {
        if(i >= RUSSIAN_ROULETTE_START_DEPTH)
        {
            const float stopProbability = 0.1f;
            if(((float)rand()/RAND_MAX) < (1.0-stopProbability))
                break;
            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)
            break;

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

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

    }
    else
        break;
    }

    return renderedColor;
}

```

And the render() function (generate image)

```c++
void Renderer::render(Scene* scene)
{
    //srand(5000);
    memset(frameBuffer,0,bufferWidth*bufferHeight*sizeof(float4));
    int pixelCount = bufferWidth*bufferHeight;

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

    int renderIndex = 0;

    acclerator->setScene(scene);
    acclerator->build();

    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
            renderIndex++;

        #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](https://i.sstatic.net/tsd5v.png)](https://i.sstatic.net/tsd5v.png)

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](https://i.sstatic.net/A2knm.png)](https://i.sstatic.net/A2knm.png)

**Tags:** raytracing, rendering

> **Comments:**
> **username_1**: How sure are you that the light source is parameterized in the same way?  
> **username_0**: 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.  
> **username_1**: Still it might be that you are using a different [photometric quantity](http://mentalraytips.blogspot.de/2007/03/understanding-photometric-and.html) somewhere, that your tone mapping is still different despite Gamma 2.2, that you're [missing a pi](https://seblagarde.wordpress.com/2012/01/08/pi-or-not-to-pi-in-game-lighting-equation/) 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 :)  
> **username_0**: 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.) https://bitbucket.org/Seanstone5923/simpleray5cpu. OK, I'll fix the camera as soon as possible.  
> **username_2**: 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  
> **username_1**: Have you tried setting `FACTOR_CUT_OFF` to 0 (introduces bias, LuxRender is unbiased)? Have you tried excluding techniques like Russian Roulette, to see where the culprit may be?  
> **username_0**: Yes, I did test that. In fact, the image I shown is rendered under a unbiased condition. Also, Russian Roulette is enabled in the image that I shown. I have set `BOUNCE_DEPTH` to -1(which makes my renderer to ignore bounce limits).  

---

## 1 answers

### Answer by username_3 on 2016-01-28. Score: 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)
    break;
rrFactor *= 1.0f/survivalProbability;

```


---

