This image has a resolution of 1920x1080, and it was rendered with 256 samples/pixel, 4 samples/light, and max ray depth equal to 12. It takes about 25 minutes on a desktop using 8 threads


In this part, you're going to simulate a thin lens to enable the depth of field effect.

Previously, we were using ideal pin-hole cameras. With the pin-hole model, everything is in focus. However, real cameras, including human eyes, are actually modeled as lenses with finite apertures defined by lensRadius. With the lens model, objects are in focus only if they're within a plane that is at focalDistance from the lens.

We're going to simulate lens in this part. To simplify your task, let's implement a thin lens, i.e. we ignore its actual thickness, but still assume that it refracts like a lens.

Once you've implemented this part, you can use the hotkeys k and l in the GUI to change the lens radius and ; and ' to change the focal distance. You can also set these parameters in the command line using -b and -d respectively. Setting lens radius to zero will reduce to the pinhole camera model you were using previously. Use the hotkey d and the command line flag -c to dump and load camera settings for doing command line rendering from different points of view and with different lens settings.

Basic principles

Let's first define the scene in camera space. We assume that the lens is located at position z=0z=0, the plane of focus is at z=focalDistancez=-\mathrm{focalDistance}, and the image plane is at z=1z=1.

In project 3-1 where there's no lens, we shoot a ray from (0,0,0)(0, 0, 0) towards some direction (X,Y,1)(X, Y, -1) (red segment in the figure). This indicates that the point pFilm receiving radiance on the image plane should be (X,Y,1)(-X, -Y, 1).

Now, with the thin lens, pFilm no longer receives radiance just from (0,0,0)(0, 0, 0). Instead, it is able to receive radiance from any point on the thin lens (yellow segment in the figure). So, we can uniformly sample the lens, which is a disk of radius lensRadius, to get the sampled point pLens on the lens at (sx,sy,0)(s_x,s_y,0).

The next step is to calculate the direction of a ray from pLens towards the scene (blue segment in the figure). But how does the yellow segment refract through the thin lens? That can be complicated. However, we can take advantage of these two properties:

  1. Rays from the same point on the plane of focus will always be focused to the same point on the image plane, no matter where they pass through the lens.

  2. The ray passing though the lens' center won't change direction.

We can thus calculate just the intersection pFocus of the red segment and the plane of focus. We then immediately know that the blue segment must go through pFocus as well.

The blue ray is what we will generate in this task. Compared to the red ray for pin-hole cameras, its origin is no longer (0,0,0)(0, 0, 0) but randomly sampled. Its direction also varies but is decided once its origin is sampled.

Task 0: Refactoring 3-1 Code

In order to enable depth of field effects, you'll need to change the ray-generating function called by PathTracer::raytrace_pixel().

If you're using your code from 3-1, replace the generate_ray() function in PathTracer::raytrace_pixel() with your generate_ray_for_thin_lens() function. You can use gridSampler->get_sample() to generate two random values in the range [0,1)[0, 1), and multiply one of them by 2π2\pi to get your inputs for rndR and rndTheta (the other inputs are the same as before).

Vector2D samplesForLens = gridSampler->get_sample();
r = camera->generate_ray_for_thin_lens(/*same*/, /*same*/, samplesForLens.x, samplesForLens.y * 2.0 * PI);

If you're using the staff library, you can enable the thin lens by setting the aperture to anything other than 0. This will tell raytrace_tile() to call your precompiled generate_ray_for_thin_lens() function with the proper inputs.

Task 1: Generating Rays for a Thin Lens

In this task, you're required to implement the Camera::generate_ray_for_thin_lens() function. This function takes the same parameters x and y as in Camera::generate_ray(), but also receives two additional parameters rndR and rndTheta that are random numbers uniformly distributed in [0,1)[0, 1) and [0,2π)[0, 2\pi) respectively. They are used to uniformly sample the thin lens.

Specifically, your can follow these steps:

  • Look up your code from Project 3-1, Part 1 to figure out the generated ray direction (red segment in the figure).
  • Uniformly sample the disk representing the thin lens at pLens=(lensRadius rndRcos(rndTheta),lensRadius rndRsin(rndTheta),0)\mathrm{pLens}=(\mathrm{lensRadius}\ \sqrt{rndR} \cdot \cos(\mathrm{rndTheta}), \mathrm{lensRadius}\ \sqrt{rndR} \cdot \sin(\mathrm{rndTheta}), 0).
  • Calculate pFocus by intersecting the plane of focus with the red segment.
  • Calculate the ray that originates from pLens, and set its direction towards pFocus (blue segment in the figure).
  • Normalize the direction of the ray, perform the camera-to-world conversion for both its origin and direction, add pos to the ray's origin, and set the near and far clips to be the same as in Project 3-1, Part 1.

This Part's Writeup

For these subparts, we recommend using a microfacet BSDF scene to show off the cool out of focus effects you can get with depth of field!

  • In a few sentences, explain the differences between a pinhole camera model and a thin-lens camera model.
  • Show a "focus stack" where you focus at 4 visibly different depths through a scene.
  • Show a sequence of 4 pictures with visibly different aperture sizes, all focused at the same point in a scene.