I have started implementing a simple path tracer. However, I have run into some problems. Placing spheres into the scene leaves most of them not being lit at all.
Scene setup
First, let me show the setup of the scene:
I have a sphere in the middle with a radius of 4 that emits light. Around it are four spheres with a radius of 1.
Code
One path cannot bounce on a surface more than 5 times. If a ray does not intersect any of the objects in the world, the path terminates as well. The last way of terminating a path is by returning the emitting color of the material we just hit. This happens with 20% of the hits.
Color TraceRay(const Ray& ray, unsigned depth) {
const unsigned maxDepth = 5;
if (depth > maxDepth)
return Color(0.f, 0.f, 0.f);
float t;
Shape* shape = NULL;
if (!world.Intersect(ray, t, &shape))
return Color(0.f, 0.f, 0.f);
Point p = ray(t);
Normal n = shape->GetNormal(p);
const float pEmit = 0.2f;
if (urd(mt) < pEmit) {
return shape->emittance * (1.f / pEmit);
}
else {
Vector newDir = RandomDirection(n);
Ray newRay(p, newDir, 0.001f);
return TraceRay(newRay, depth+1) * Dot(n, newDir) * (1.f / (1.f - pEmit));
}
}
.
When bouncing off a surface, a random new direction in the same hemisphere as the surface normal must be generated. I generate three random floats which form a vector v. I normalize this vector and check whether it is in the same hemisphere as the surface normal. If so, return v. If not, flip v and return it.
Vector RandomDirection(const Normal& n) {
Vector v(urd(mt), urd(mt), urd(mt));
Normalize(v);
return Dot(v, n) < 0.f ? -v : v;
}
.
After every pixel has been sampled, I present the results so far. The function below is called 500 times to take 500 samples per pixel. All sampled colors are summed up and divided by the number of them for the final resulting color.
void TraceRays(unsigned maxIterations, sf::Texture& texture) {
for (unsigned x = 0; x < camera.film.GetWidth(); x++) {
for (unsigned y = 0; y < camera.film.GetHeight(); y++) {
Ray ray = camera.GetRay(x, y);
Color c = camera.film.GetPixel(x, y);
Color l = TraceRay(ray, 0);
camera.film.SetPixel(x, y, l + c);
}
}
ClearImage();
for (unsigned x = 0; x < camera.film.GetWidth(); x++) {
for (unsigned y = 0; y < camera.film.GetHeight(); y++) {
Color c = camera.film.GetPixel(x,y);
c /= maxIterations;
image.setPixel(x, y, c.ToSFMLColor());
}
}
texture.update(image);
}
.
Results
The light emitting sphere is clearly visible. You can also see sphere D being slightly lit in the lower right corner.
However, none of the other spheres are being lit. I would expect at least a few of the paths that bounce on spheres A and B to bounce in the direction of the light emitting sphere, leading to those pixels being brightened.
Questions
I'm having a hard time debugging things pixel by pixel. I'm hoping someone here might be able to make an educated guess about what I'm doing wrong, either by seeing the resulting image or browsing through the above code.
Any help would be greatly appreciated!