# Shadow appears to be too dark in my ray tracer

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

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)

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

• How sure are you that the light source is parameterized in the same way? – David Kuri Jan 22 '16 at 9:27
• 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 Jan 22 '16 at 9:49
• 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 :) – David Kuri Jan 22 '16 at 10:20
• 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. – Mary Chang Jan 22 '16 at 15:03
• 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 Jan 25 '16 at 19:11

## 1 Answer

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;