Pathtracing 2

A long time ago i wrote an introductory article on the principles of path tracing along with some initial results which were fairly uninspiring. 500 samples per pixel for this…

Pathtraced cornell box

Cornell box scene, 500 samples per pixel. ouch..

At the time I wondered if I’d got something wrong in the algorithm and during my recent work on noise reduction I worked out what it was! It turns out there are two things which were fundamentally incorrect in my initial implementation. First up my random vector generator was summing up more light than it should have been when sampling the diffuse interreflection. This meant that the brightness of the render was increasing significantly when the pathtracer was switched on, drowning out the directly sampled diffuse lighting resulting in ugly noise. I found this out when I replaced my roulette cosine vector generator with the biased cosine sampler documented by Syntopia in his recent pathtracing article.

Then I realised another fundamental mistake in the pathtracing algorithm. I was counting the diffuse contribution of light sources twice. Once in the standard area light code, and again inside the pathtracer. To understand this problem we need to go back a bit and consider the difference between a pathtracer and a directly sampled area light. There’s a simple formula we can use to calculate how much light a sphere will cast at a point in space. It’s documented in Tom Madam’s post here and comes down to the following simple formula.

attenuation = 1 / (1 + 2*d/r + (d*d)/(r*r));

If we use this then we can get a very accurate estimate of the lighting contribution from a sphere using a single calculation. To estimate occlusion we can then cast a few rays at the sphere and see if we hit anything. If we do hit things then the light contribution is zero, otherwise it’s the attenuation*colour.

Now in a pure pathtracer the sphere is simply an emissive surface. To calculate the lighting at a single point in space we’ll send samples in all directions and average the light contribution from these samples. With smaller light sources it can take very many samples before we start to reach an accurate estimate for the incident lighting. In the meantime we’re just going to generate noise all over the place.

So my very naive initial implementation for pathtracing did the following pseudocode

DColour lighting;
foreach (light in Lights)
{
 lighting += Lights.CalculateLighting();
}

foreach (sample in RandomSamples)
{
 lighting += sample.GetColour() / RandomSamples.count();
}

To fix the double lighting issue it’s necessary to modify the sample.GetColour() call so that the raytracer returns black if it encounters an object which we’re already adding light for.

DColour GetColour(bool pathtraced)
{
 bool hit = trace(scene, ray, etc.)
 if (hit && hitObject->LightSource() && pathtraced)
 {
  return DColour(0,0,0);
 }
 else
  return hitObject->GetColour();
}

It’s important that we only return black for pathtraced samples, because for reflections and basic rays we still want to return the colour of the light itself. We’re now getting much nicer results with far fewer pixels. This render took <10 minutes and is far improved over the one at the top of this article. [caption id="attachment_837" align="alignnone" width="720"]Improved Cornell Box Render Improved Cornell Box Render[/caption]

So we’ve now got much cleaner pathtracing coming out of the system for simple diffuse scenes, but there’s another effect I’ve always wanted to reproduce in my raytracer, and that’s caustics. Puns aside, caustics refer to light which is reflected off a highly specular object (eg water) and then illuminate a diffuse object (eg the underside of a bridge).

Pathtracers can manage caustics, although the processing time is often hideous for these more difficult types of light transfer. My initial attempts were disappointing as the pathtracing rays above were setting the return colours to black, even when reflection vectors hit lightsources. If we’re keen to represent caustics in our renders then we have to return true colours for light sources if we’re casting specular rays. In that case we start to get some nice examples of caustics in the renderer.

The one ring

The one ring

Note the lighting inside the ring. Here we’re seeing the effect of light which has come from the lightsource, been reflected off the ring, and then illuminated the interior of the ring.

The improvements I’ve listed above made dramatic changes to the quality of the rendering I can achieve in wooscripter, and here’s a couple more pretty renders that have come out recently using the pathtracer.

More fun in a cornell box

More fun in a cornell box

Arches

Arches

Water surfaces casting caustics

Water surfaces casting caustics

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *

Spam Protection *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>