[Book 3] Section 2.3 Stratified Samples cause color loss
In camera.h:
for (int j = 0; j < image_height; ++j) {
std::clog << "\rScanlines remaining: " << (image_height - j) << ' ' << std::flush;
for (int i = 0; i < image_width; ++i) {
color pixel_color(0,0,0);
for (int s_j = 0; s_j < sqrt_spp; ++s_j) {
for (int s_i = 0; s_i < sqrt_spp; ++s_i) {
ray r = get_ray(i, j, s_i, s_j);
pixel_color += ray_color(r, max_depth, world);
}
}
write_color(std::cout, pixel_color, samples_per_pixel);
}
}
We can see that ray_color have been called sqrt_spp*sqrt_spp times, which is small than samples_per_pixel when the latter isn't a perfect square.
But in write_color, scale = 1.0 / samples_per_pixel, so color loss happens.
Should use scale = 1.0 / (sqrt_spp * sqrt_spp).
write_color in color.h:
void write_color(std::ostream &out, color pixel_color, int samples_per_pixel) {
auto r = pixel_color.x();
auto g = pixel_color.y();
auto b = pixel_color.z();
// Divide the color by the number of samples.
auto scale = 1.0 / samples_per_pixel;
r *= scale;
g *= scale;
b *= scale;
// Apply a linear to gamma transform for gamma 2
r = linear_to_gamma(r);
g = linear_to_gamma(g);
b = linear_to_gamma(b);
// Write the translated [0,255] value of each color component.
static const interval intensity(0.000, 0.999);
out << static_cast<int>(256 * intensity.clamp(r)) << ' '
<< static_cast<int>(256 * intensity.clamp(g)) << ' '
<< static_cast<int>(256 * intensity.clamp(b)) << '\n';
}
Excellent catch!
We should in fact pass scale to the write_color() function instead of samples_per_pixel, so we don't have to calculate the reciprocal over and over again as well.
Sadly, we should probably make this change retroactive for the write_color function and render loop throughout the books. Sigh.
I'm putting this on the alpha.2 milestone since it will affect all three books.
I have a suggestion that calculate int miss_spp = samples_per_pixel - sqrt_spp * sqrt_spp; and call get_ray() miss_spp times with random s_i、s_j to replenish loss, then there will be no loss and no change need be made to write_color().
If we restrict samples to the s_i, s_j, then extra samples will just mean duplicating all of the work for some sample points, and the only difference will be an increased weight for some samples. If this were an acceptable approach, then you'd be better off just doubling the weight of some of the samples. However, this wouldn't get us the uniform stratified sampling that we set out to do.
Instead, I think that restricting multiple samples to squares is the right fix for this.
Addressed in #1417