Final Code

Depth of Field

The raytracer was extended to support depth of field lens effects. A variable for the lens size in world units and a distance from the lens to the focus plane were described at the beginning of the ray spawning process.

When the rays at a given pixel on the image plane are to be cast into the scene to return a color, a ray is instead cast into the scene in order to find the intersect point with a plane placed at focus length distance from the lens. Once this point of intersection is found, the origin of the ray is offset randomly within the area defined by the lens size.

The direction of the ray is then recalculated to intersect with previously determined point. Next, where the ray is actually cast into the scene, the sum of a defined number of rays are cast into the scene with the random offset at their origin. This sum is divided by the number of rays cast in order to achieve an average color at a given pixel.

These images show a render with different focus planes. In the first picture, the closest ball lies on the focus plane. In the second picture, a different ball lies on the focus plane.

Soft Shadows

There are many variations on soft shadow rendering with varying degrees of complication. However all of the complicated, fast, algorithms benchmark image quality with a standard 1024 sample point square area light.

We decided to go with the simple approach. When we shade a point on a surface, we test to see if that light is hit by each of the sample points on our area light.

By counting the number of hits compared with the misses, or occlusions, we can determine what fraction to scale our final Phong shading, specular and diffuse. One area where we sacrifice quality for performance is that we don't calculate our Phong shading off of every sample point on our area light, only the center. This means that if a point is only shaded by one half of our light, the shading is off, but it saves us a lot of computation time as a 64 sample grid (the highest we have rendered at) requires only one Phong calculation instead of 64. After we calculate Phong for the center point, we scale it by the shadow factor and then add ambient shading.

We found that this provides excellent soft shadows in all of the test cases we have tried. The size of the light as well as the distance of the occluding object from the light source affect how large the penumbra is and how quickly it falls off. One performance improvement I would like to implement but did not bother for this iteration of the program was to check for occlusion on a small number of sample points of the area light, perhaps the four corners, and if none of those hit then assume the point is completely shadowed. This would greatly reduce the number of collisions we have to do for the umbra, from say 64 down to 4. This could be a problem in a complex scene with many small occluders in close proximity, but for our purposes this would not be an issue.

These images show a render with a single light sampled at 2x2, and a render with a single light sampled at 16x16. Notice the much softer and blended shadows on the 16x16 sampled render.