Offline path tracer produces a darker image than expected

I've been going though Peter Shirley's beginner raytracing books. Upon building the Cornell box and rendering an image, I get a much darker / dimmer image than expected. The color values are normalized (in the range [0.0f, 1.0f]). The author of the books has stated that for a brighter result, I need to gamma correct the values (calculating the nth root of the color values)The above image has a gamma correction of 2 (square root). This is the same value that the author uses, but his result is much brighter I tried making the gamma correction larger (increasing n) and after setting n=4 (each color gets set to the (1/4)th exponent), I get this image. There are obvious artifacts, like a lack of highlights near the light, and the over-saturated small number of pixels where a wall is close to the sphere.

The way a pixel's color is calculated is like this:

for(int j = height - 1; j >= 0; j--)
{
for(int i = 0; i < width; i++)
{
vec3 color(0.0f, 0.0f, 0.0f);
for(int s = 0; s < samplesPerPixel; s++)
{
float u = ((float)i + RandomNumber(0.0f, 1.0f)) / width;
float v = ((float)j + RandomNumber(0.0f, 1.0f)) / height;

ray r = cam.CastRay(u, v);
color += Color(r, list, depth);
}

color /= (float)samplesPerPixel;
color = vec3(pow(color.x, gammaCorrection), pow(color.y, gammaCorrection), pow(color.z, gammaCorrection));

int ir = Map(0.0f, 1.0f, 0.0f, 255.0f, color.x);
int ig = Map(0.0f, 1.0f, 0.0f, 255.0f, color.y);
int ib = Map(0.0f, 1.0f, 0.0f, 255.0f, color.z);

output << ir << " " << ig << " " << ib << "\n";
}
}

And the resulting color values for each channel (red, green and blue) is in the range of [0, 255].

Is there a problem with how I calculate a pixel's color based on all samples? My color function is pretty much the same as it is in the book

vec3 Color(const ray& r, HitableList& hitables, int depth)
{
float tmin = 0.001f;
float tmax = 10000.0f;

HitData data;

// If the ray hit something
if(hitables.Hit(r, tmin, tmax, data))
{
ray scattered;
vec3 attenuation;

// If the material is a light, store its emitted color
vec3 emitted = data.mat->Emitted(data.u, data.v, data.point);

// If the number of bounces is below 50 and the ray gets scattered into a random direction
if(depth < 50 && data.mat->Scatter(r, data, attenuation, scattered))
return emitted + attenuation * Color(scattered, hitables, depth + 1);
else
return emitted;
} else
return vec3(0.0, 0.0f, 0.0f);
}

Has anyone come across a similar issue before?

• Can you try rendering the same thing with the code that goes with the book on github? Also post your scatter and random functions. – lightxbulb Jul 16 at 17:18
• @lightxbulb I hadn't checked the light's color beforehand. I just assumed it's 1.0f, but it had been 18.0f. I changed that and clamped all colors, and the issue is solved. – Stefan Ivanovski Jul 16 at 17:57
• If you find the answer to your problem yourself, self answers are encouraged here. – trichoplax Jul 16 at 21:21
• @trichoplax I have added an answer, but I can't mark it as an answer within 48 hours of posting the answer. I will mark it when I am able. – Stefan Ivanovski Jul 17 at 7:33

I have found the issue. The gamma correction was the correct value, the same as in the book (1/2), but the light source had the brightness of 1.0f. The book had set the light's brightness to 18.0f for all color channels.

This would introduce color overflow if left at that, and the very light areas (above 1.0f, and subsequently when converted, outside the range [0, 255]) would have wrong colors.

To fix this clamp the the pixel's color values.

1. Go through all samples and calculate pixel color.
2. Divide the color by number of samples
3. Apply gamma correction
4. Clamp colors in range [0, 1]
5. Finally, convert values to range [0, 255] (I will accept this answer as a solution in 2 days because StackExchange doesn't let me to do it now.)