I am trying to implement a simple path tracer with next-event estimation in Java. The general idea is to trace a ray through the scene as usual (using a cosine distribution to determine the next random direction at every bounce), while also taking the direct illumination into account at every bounce. I think I have most pieces of the puzzle available, but I can't fit them together correctly.
Here is how I shade matte objects: add the direct illumination to the radiance coming from a random ray (chosen according to a cosine distribution), the latter multiplied by the BRDF.
public RGBSpectrum shadePath(Shading shading, HybridPathTracer tracer, int length) {
cosinusSampler sampler = new cosinusSampler(1);
Point sample = sampler.computeSample();
Vector w = shading.getIntersectionPoint().getNormal();
Random PRNG = new Random();
Vector r = new Vector(PRNG.nextDouble(), PRNG.nextDouble(), PRNG.nextDouble()).normalize();
Vector v = r.cross(w);
Vector u = v.cross(w);
Vector wi = u.scale(sample.x).add(v.scale(sample.y)).add(w.scale(sample.z));
Ray ray = new Ray(shading.getIntersectionPoint().toPoint(shading.getRay()), wi);
RGBSpectrum reflection = tracer.trace(ray, length + 1);
RGBSpectrum brdf = shading.getColour().scale(getDiffuseReflection());
return shade(shading).add(brdf.multiply(reflection));
}
When a ray reaches an emissive material, black is returned, unless the path length is equal to 1:
public RGBSpectrum shadePath(Shading shading, HybridPathTracer tracer, int length) {
Ray ray = shading.getRay();
Vector n = shading.getIntersectionPoint().getNormal();
if (length == 1 && n.dot(ray.direction) < 0) {
return getTexture().getColour();
} else {
return RGBSpectrum.BLACK;
}
}
I read Progressive Path Tracing with Explicit Light Sampling | Computer Graphics and thought that I was using the same idea in my code, but I must be doing something wrong, because my images are getting too bright when the maximal path length increases (see the examples for maximal length 2, 4 and 6 underneath). What is the problem?
Note: I know I should also consider some edge cases for specular surfaces, but maybe it's better to ignore those first and focus on matte objects. (I think I have prepared my code for specular surfaces anyway.) My shade function for direct illumination of matte objects should be fine, but here it is for reference:
public RGBSpectrum shade(Shading shading) {
RGBSpectrum colour = shading.getColour();
double L = 0;
final Point intersection = shading.getIntersectionPoint().toPoint(shading.getRay());
for (Light light : shading.getScene().getLights()) {
double l = 0;
for (Point sample : light.computeSamples()) {
Vector wi = sample.subtract(intersection);
double lengthSquared = wi.lengthSquared();
wi = wi.normalize();
double cos = shading.getIntersectionPoint().getNormal().dot(wi);
if (cos > 0) {
Ray shadowRay = new Ray(intersection, wi);
if (!light.inShadow(shadowRay, shading.getScene())) {
if (light instanceof PointLight) {
double denom = 4 * Math.PI * lengthSquared;
l += light.getPower() * cos / denom;
} else if (light instanceof RectangularLight) {
Rectangle rectangle = ((RectangularLight) light).getRectangle();
double cosp = -wi.dot(rectangle.getNormal());
l += light.getPower() * cos * cosp * rectangle.getArea() / lengthSquared;
}
}
}
}
L += l / light.getSamplesNumber();
}
return colour.scale(getAmbientReflection() + L * getDiffuseReflection());
}