6.4 Realistic Cameras

The thin lens model makes it possible to render images with blur due to depth of field, but it is a fairly rough approximation of actual camera lens systems, which are comprised of a series of multiple lens elements, each of which modifies the distribution of radiance passing through it. (Figure 6.15 shows a cross section of a 22-mm focal length wide-angle lens with eight elements.) Even basic cell phone cameras tend to have on the order of five individual lens elements, while DSLR lenses may have ten or more. In general, more complex lens systems with larger numbers of lens elements can create higher quality images than simpler lens systems.

Figure 6.15: Cross section of a wide-angle lens system (scenes/lenses/wide.22mm.dat in the pbrt distribution). The lens coordinate system has the film plane perpendicular to the z axis and located at z equals 0 . The lenses are to the left, along negative z , and then the scene is to the left of the lenses. The aperture stop, indicated by the thick black lines in the middle of the lens system, blocks rays that hit it. In many lens systems, the size of the aperture stop can be adjusted to trade off between shorter exposure times (with larger apertures) and more depth of field (with smaller apertures).

This section discusses the implementation of RealisticCamera, which simulates the focusing of light through lens systems like the one in Figure 6.15 to render images like Figure 6.16. Its implementation is based on ray tracing, where the camera follows ray paths through the lens elements, accounting for refraction at the interfaces between media (air, different types of glass) with different indices of refraction, until the ray path either exits the optical system or until it is absorbed by the aperture stop or lens housing. Rays leaving the front lens element represent samples of the camera’s response profile and can be used with integrators that estimate the incident radiance along arbitrary rays, such as the SamplerIntegrator. The RealisticCamera implementation is in the files cameras/realistic.h and cameras/realistic.cpp.

Figure 6.16: Image rendered using a fish-eye lens with a very wide field of view. Note the darkening at the edges, which is due to accurate simulation of the radiometry of image formation (Section 6.4.7) and the distortion of straight lines to curves, which is characteristic of many wide-angle lenses but isn’t accounted for when using projection matrices to represent the lens projection model.

<<RealisticCamera Declarations>>= 
class RealisticCamera : public Camera { public: <<RealisticCamera Public Methods>> 
RealisticCamera(const AnimatedTransform &CameraToWorld, Float shutterOpen, Float shutterClose, Float apertureDiameter, Float focusDistance, bool simpleWeighting, const char *lensFile, Film *film, const Medium *medium); Float GenerateRay(const CameraSample &sample, Ray *) const;
private: <<RealisticCamera Private Declarations>> 
struct LensElementInterface { Float curvatureRadius; Float thickness; Float eta; Float apertureRadius; };
<<RealisticCamera Private Data>> 
const bool simpleWeighting; std::vector<LensElementInterface> elementInterfaces; std::vector<Bounds2f> exitPupilBounds;
<<RealisticCamera Private Methods>> 
Float LensRearZ() const { return elementInterfaces.back().thickness; } Float LensFrontZ() const { Float zSum = 0; for (const LensElementInterface &element : elementInterfaces) zSum += element.thickness; return zSum; } Float RearElementRadius() const { return elementInterfaces.back().apertureRadius; } bool TraceLensesFromFilm(const Ray &ray, Ray *rOut) const; static bool IntersectSphericalElement(Float radius, Float zCenter, const Ray &ray, Float *t, Normal3f *n); bool TraceLensesFromScene(const Ray &rCamera, Ray *rOut) const; void DrawLensSystem() const; void DrawRayPathFromFilm(const Ray &r, bool arrow, bool toOpticalIntercept) const; void DrawRayPathFromScene(const Ray &r, bool arrow, bool toOpticalIntercept) const; static void ComputeCardinalPoints(const Ray &rIn, const Ray &rOut, Float *p, Float *f); void ComputeThickLensApproximation(Float pz[2], Float f[2]) const; Float FocusThickLens(Float focusDistance); Float FocusBinarySearch(Float focusDistance); Float FocusDistance(Float filmDist); Bounds2f BoundExitPupil(Float pFilmX0, Float pFilmX1) const; void RenderExitPupil(Float sx, Float sy, const char *filename) const; Point3f SampleExitPupil(const Point2f &pFilm, const Point2f &lensSample, Float *sampleBoundsArea) const; void TestExitPupilBounds() const;
};

In addition to the usual transformation to place the camera in the scene, the Film, and the shutter open and close times, the RealisticCamera constructor takes a filename for a lens system description file, the distance to the desired plane of focus, and a diameter for the aperture stop. The effect of the simpleWeighting parameter is described later, in Section 13.6.6, after preliminaries related to Monte Carlo integration in Chapter 13 and the radiometry of image formation in Section 6.4.7.

<<RealisticCamera Method Definitions>>= 
RealisticCamera::RealisticCamera(const AnimatedTransform &CameraToWorld, Float shutterOpen, Float shutterClose, Float apertureDiameter, Float focusDistance, bool simpleWeighting, const char *lensFile, Film *film, const Medium *medium) : Camera(CameraToWorld, shutterOpen, shutterClose, film, medium), simpleWeighting(simpleWeighting) { <<Load element data from lens description file>> 
std::vector<Float> lensData; if (ReadFloatFile(lensFile, &lensData) == false) { Error("Error reading lens specification file \"%s\".", lensFile); return; } if ((lensData.size() % 4) != 0) { Error("Excess values in lens specification file \"%s\"; " "must be multiple-of-four values, read %d.", lensFile, (int)lensData.size()); return; } for (int i = 0; i < (int)lensData.size(); i += 4) { if (lensData[i] == 0) { if (apertureDiameter > lensData[i+3]) { Warning("Specified aperture diameter %f is greater than maximum " "possible %f. Clamping it.", apertureDiameter, lensData[i+3]); } else { lensData[i+3] = apertureDiameter; } } elementInterfaces.push_back((LensElementInterface) {lensData[i] * (Float).001, lensData[i+1] * (Float).001, lensData[i+2], lensData[i+3] * Float(.001) / Float(2.)}); }
<<Compute lens–film distance for given focus distance>> 
elementInterfaces.back().thickness = FocusThickLens(focusDistance);
<<Compute exit pupil bounds at sampled points on the film>> 
int nSamples = 64; exitPupilBounds.resize(nSamples); ParallelFor( [&](int i) { Float r0 = (Float)i / nSamples * film->diagonal / 2; Float r1 = (Float)(i + 1) / nSamples * film->diagonal / 2; exitPupilBounds[i] = BoundExitPupil(r0, r1); }, nSamples);
}

<<RealisticCamera Private Data>>= 
const bool simpleWeighting;

After loading the lens description file from disk, the constructor adjusts the spacing between the lenses and the film so that the plane of focus is at the desired depth, focusDistance, and then precomputes some information about which areas of the lens element closest to the film carry light from the scene to the film, as seen from various points on the film plane. After background material has been introduced, the fragments <<Compute lens–film distance for given focus distance>> and <<Compute exit pupil bounds at sampled points on the film>> will be defined in Sections 6.4.4 and 6.4.5, respectively.

6.4.1 Lens System Representation

A lens system is made from a series of lens elements, where each element is generally some form of glass. A lens system designer’s challenge is to design a series of elements that form high-quality images on a film or sensor subject to limitations of space (e.g., the thickness of mobile phone cameras is very limited in order to keep phones thin), cost, and ease of manufacture.

It’s easiest to manufacture lenses with cross sections that are spherical, and lens systems are generally symmetric around the optical axis, which is conventionally denoted by z . We will assume both of these properties in the remainder of this section. Lens systems are defined using a coordinate system where the film is aligned with the z equals 0 plane and lenses are to the left of the film, along the negative z axis.

Lens systems are commonly represented in terms of the series of interfaces between the individual lens elements (or air) rather than having an explicit representation of each element. Table 6.1 shows the quantities that define each interface. The last entry in the table defines the rightmost interface, which is shown in Figure 6.17: it’s a section of a sphere with radius equal to the curvature radius. The thickness of an element is the distance along  z to the next element to the right (or to the film plane), and the index of refraction is for the medium to the right of the interface. The element’s extent above and below the z axis is set by the aperture diameter.

Table 6.1: Tabular description of the lens system in Figure 6.15. Each line describes the interface between two lens elements, the interface between an element and air, or the aperture stop. The first line describes the leftmost interface. The element with radius 0 corresponds to the aperture stop. Distances are measured in mm.

Curvature RadiusThickness Index of RefractionAperture Diameter
35.98738 1.21638 1.54 23.716
11.69718 9.9957 1 17.996
13.08714 5.12622 1.772 12.364
minus 22.63294 1.76924 1.617 9.812
71.05802 0.8184 1 9.152
0 2.27766 0 8.756
minus 9.58584 2.43254 1.617 8.184
minus 11.28864 0.11506 1 9.152
minus 166.7765 3.09606 1.713 10.648
minus 7.5911 1.32682 1.805 11.44
minus 16.7662 3.98068 1 12.276
minus 7.70286 1.21638 1.617 13.42
minus 11.97328 (depends on focus) 1 17.996

Figure 6.17: A lens interface (solid curved line) intersecting the optical axis at a position z . The interface geometry is described by the interface’s aperture radius, which describes its extent above and below the optical axis, and the element’s curvature radius r . If the element has a spherical cross section, then its profile is given by a sphere with center a distance r away on the optical axis, where the sphere also passes through z . If r is negative, the element interface is concave as seen from the scene (as is shown here); otherwise it is convex. The thickness of the lens gives the distance to the next interface to the right, or the distance to the film plane for the rearmost interface.

The LensElementInterface structure represents a single lens element interface.

<<RealisticCamera Private Declarations>>= 
struct LensElementInterface { Float curvatureRadius; Float thickness; Float eta; Float apertureRadius; };

The fragment <<Load element data from lens description file>>, not included here, reads the lens elements and initializes the RealisticCamera::elementInterfaces array. See comments in the source code for details of the file format, which parallels the structure of Table 6.1, and see the directory scenes/lenses in the pbrt distribution for a number of example lens descriptions.

Two adjustments are made to the values read from the file: first, lens systems are traditionally described in units of millimeters, but pbrt assumes a scene measured in meters. Therefore, the fields other than the index of refraction are scaled by 1 slash 1000 . Second, the element’s diameter is divided by two; the radius is a more convenient quantity to have at hand in the code to follow.

<<RealisticCamera Private Data>>+=  
std::vector<LensElementInterface> elementInterfaces;

Once the element interface descriptions have been loaded, it’s useful to have a few values related to the lens system easily at hand. LensRearZ() and LensFrontZ() return the z depths of the rear and front elements of the lens system, respectively. Note that the returned z depths are in camera space, not lens space, and thus have positive values.

<<RealisticCamera Private Methods>>= 
Float LensRearZ() const { return elementInterfaces.back().thickness; }

Finding the front element’s z position requires summing all of the element thicknesses (see Figure 6.18). This value isn’t needed in any code that is in a performance-sensitive part of the system, so recomputing it when needed is fine. If performance of this method was a concern, it would be better to cache this value in the RealisticCamera.

Figure 6.18: The Relationship between Element Thickness and Position on the Optical Axis. The film plane is at z equals 0 , and the rear element’s thickness, t 3 , gives the distance to its interface from the film; the rear interface intersects the axis here at z equals minus t 3 . The next element has a thickness t 2 and is positioned at z equals minus t 3 minus t 2 , and so forth. The front element intersects the z axis at sigma-summation Underscript i Endscripts minus t Subscript i .

<<RealisticCamera Private Methods>>+=  
Float LensFrontZ() const { Float zSum = 0; for (const LensElementInterface &element : elementInterfaces) zSum += element.thickness; return zSum; }

RearElementRadius() returns the aperture radius of the rear element in meters.

<<RealisticCamera Private Methods>>+=  
Float RearElementRadius() const { return elementInterfaces.back().apertureRadius; }

6.4.2 Tracing Rays through Lenses

Given a ray starting from the film side of the lens system, TraceLensesFromFilm() computes intersections with each element in turn, terminating the ray and returning false if its path is blocked along the way through the lens system. Otherwise it returns true and initializes *rOut with the exiting ray in camera space. During traversal, elementZ tracks the z intercept of the current lens element. Because the ray is starting from the film, the lenses are traversed in reverse order compared to how they are stored in elementInterfaces.

<<RealisticCamera Method Definitions>>+=  
bool RealisticCamera::TraceLensesFromFilm(const Ray &rCamera, Ray *rOut) const { Float elementZ = 0; <<Transform rCamera from camera to lens system space>> 
static const Transform CameraToLens = Scale(1, 1, -1); Ray rLens = CameraToLens(rCamera);
for (int i = elementInterfaces.size() - 1; i >= 0; --i) { const LensElementInterface &element = elementInterfaces[i]; <<Update ray from film accounting for interaction with element>> 
elementZ -= element.thickness; <<Compute intersection of ray with lens element>> 
Float t; Normal3f n; bool isStop = (element.curvatureRadius == 0); if (isStop) t = (elementZ - rLens.o.z) / rLens.d.z; else { Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, rLens, &t, &n)) return false; }
<<Test intersection point against element aperture>> 
Point3f pHit = rLens(t); Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; if (r2 > element.apertureRadius * element.apertureRadius) return false; rLens.o = pHit;
<<Update ray path for element interface interaction>> 
if (!isStop) { Vector3f w; Float etaI = element.eta; Float etaT = (i > 0 && elementInterfaces[i - 1].eta != 0) ? elementInterfaces[i - 1].eta : 1; if (!Refract(Normalize(-rLens.d), n, etaI / etaT, &w)) return false; rLens.d = w; }
} <<Transform rLens from lens system space back to camera space>> 
if (rOut != nullptr) { static const Transform LensToCamera = Scale(1, 1, -1); *rOut = LensToCamera(rLens); }
return true; }

Because the camera points down the plus z axis in pbrt’s camera space but lenses are along negative z , the z components of the origin and direction of the ray need to be negated. While this is a simple enough transformation that it could be applied directly, we prefer an explicit Transform to make the intent clear.

<<Transform rCamera from camera to lens system space>>= 
static const Transform CameraToLens = Scale(1, 1, -1); Ray rLens = CameraToLens(rCamera);

Recall from Figure 6.18 how the z intercept of elements is computed: because we are visiting the elements from back-to-front, the element’s thickness must be subtracted from elementZ to compute its z intercept before the element interaction is accounted for.

<<Update ray from film accounting for interaction with element>>= 
elementZ -= element.thickness; <<Compute intersection of ray with lens element>> 
Float t; Normal3f n; bool isStop = (element.curvatureRadius == 0); if (isStop) t = (elementZ - rLens.o.z) / rLens.d.z; else { Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, rLens, &t, &n)) return false; }
<<Test intersection point against element aperture>> 
Point3f pHit = rLens(t); Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; if (r2 > element.apertureRadius * element.apertureRadius) return false; rLens.o = pHit;
<<Update ray path for element interface interaction>> 
if (!isStop) { Vector3f w; Float etaI = element.eta; Float etaT = (i > 0 && elementInterfaces[i - 1].eta != 0) ? elementInterfaces[i - 1].eta : 1; if (!Refract(Normalize(-rLens.d), n, etaI / etaT, &w)) return false; rLens.d = w; }

Given the element’s z axis intercept, the next step is to compute the parametric t value along the ray where it intersects the element interface (or the plane of the aperture stop). For the aperture stop, a ray–plane test (following Section 3.1.2) is used. For spherical interfaces, IntersectSphericalElement() performs this test and also returns the surface normal if an intersection is found; the normal will be needed for computing the refracted ray direction.

<<Compute intersection of ray with lens element>>= 
Float t; Normal3f n; bool isStop = (element.curvatureRadius == 0); if (isStop) t = (elementZ - rLens.o.z) / rLens.d.z; else { Float radius = element.curvatureRadius; Float zCenter = elementZ + element.curvatureRadius; if (!IntersectSphericalElement(radius, zCenter, rLens, &t, &n)) return false; }

The IntersectSphericalElement() method is generally similar to Sphere::Intersect(), though it’s specialized for the fact that the element’s center is along the z axis (and thus, the center’s x and y components are zero). The fragments <<Compute t0 and t1 for ray–element intersection>> and <<Compute surface normal of element at ray intersection point>> aren’t included in the text here due to their similarity with the Sphere::Intersect() implementation.

<<RealisticCamera Method Definitions>>+=  
bool RealisticCamera::IntersectSphericalElement(Float radius, Float zCenter, const Ray &ray, Float *t, Normal3f *n) { <<Compute t0 and t1 for ray–element intersection>> 
Point3f o = ray.o - Vector3f(0, 0, zCenter); Float A = ray.d.x*ray.d.x + ray.d.y*ray.d.y + ray.d.z*ray.d.z; Float B = 2 * (ray.d.x*o.x + ray.d.y*o.y + ray.d.z*o.z); Float C = o.x*o.x + o.y*o.y + o.z*o.z - radius*radius; Float t0, t1; if (!Quadratic(A, B, C, &t0, &t1)) return false;
<<Select intersection t based on ray direction and element curvature>> 
bool useCloserT = (ray.d.z > 0) ^ (radius < 0); *t = useCloserT ? std::min(t0, t1) : std::max(t0, t1); if (*t < 0) return false;
<<Compute surface normal of element at ray intersection point>> 
*n = Normal3f(Vector3f(o + *t * ray.d)); *n = Faceforward(Normalize(*n), -ray.d);
return true; }

There is, however, a subtlety in choosing which intersection point to return: the closest intersection with t greater-than 0 isn’t necessarily on the element interface; see Figure 6.19. For example, for a ray approaching from the scene and intersecting a concave lens (with negative curvature radius), the farther of the two intersections should be returned regardless of whether the closer one has t greater-than 0 . Fortunately, simple logic based on the ray direction and the curvature radius indicates which t value to use.

Figure 6.19: When computing the intersection of a ray with a spherical lens element, the first intersection of the ray with the full sphere isn’t necessarily the desired one. Here, the second intersection is the one on the actual element interface (thick line) and the first should be ignored.

<<Select intersection t based on ray direction and element curvature>>= 
bool useCloserT = (ray.d.z > 0) ^ (radius < 0); *t = useCloserT ? std::min(t0, t1) : std::max(t0, t1); if (*t < 0) return false;

Each lens element extends for some radius around the optical axis; if the intersection point with the element is outside this radius, then the ray will actually intersect the lens housing and terminate. In a similar fashion, if a ray intersects the aperture stop, it also terminates. Therefore, here we test the intersection point against the appropriate limit for the current element, either terminating the ray or updating its origin to the current intersection point if it survives.

<<Test intersection point against element aperture>>= 
Point3f pHit = rLens(t); Float r2 = pHit.x * pHit.x + pHit.y * pHit.y; if (r2 > element.apertureRadius * element.apertureRadius) return false; rLens.o = pHit;

If the current element is the aperture, the ray’s path isn’t affected by traveling through the element’s interface. For glass (or, forbid, plastic) lens elements, the ray’s direction changes at the interface as it goes from a medium with one index of refraction to one with another. (The ray may be passing from air to glass, from glass to air, or from glass with one index of refraction to a different type of glass with a different index of refraction.)

Section 8.2 discusses how a change in index of refraction at the boundary between two media changes the direction of a ray and the amount of radiance carried by the ray. (In this case, we can ignore the change of radiance, as it cancels out if the ray is in the same medium going into the lens system as it is when it exits—here, both are air.) The Refract() function is defined in Section 8.2.3; note that it expects that the incident direction will point away from the surface, so the ray direction is negated before being passed to it. This function returns false in the presence of total internal reflection, in which case the ray path terminates. Otherwise, the refracted direction is returned in w.

In general, some light passing through an interface like this is transmitted and some is reflected. Here we ignore reflection and assume perfect transmission. Though an approximation, it is a reasonable one: lenses are generally manufactured with coatings designed to reduce the reflection to around 0.25 percent-sign of the radiance carried by the ray. (However, modeling this small amount of reflection can be important for capturing effects like lens flare.)

<<Update ray path for element interface interaction>>= 
if (!isStop) { Vector3f w; Float etaI = element.eta; Float etaT = (i > 0 && elementInterfaces[i - 1].eta != 0) ? elementInterfaces[i - 1].eta : 1; if (!Refract(Normalize(-rLens.d), n, etaI / etaT, &w)) return false; rLens.d = w; }

If the ray has successfully made it out of the front lens element, it just needs to be transformed from lens space to camera space.

<<Transform rLens from lens system space back to camera space>>= 
if (rOut != nullptr) { static const Transform LensToCamera = Scale(1, 1, -1); *rOut = LensToCamera(rLens); }

The TraceLensesFromScene() method is quite similar to TraceLensesFromFilm() and isn’t included here. The main differences are that it traverses the elements from front-to-back rather than back-to-front. Note that it assumes that the ray passed to it is already in camera space; the caller is responsible for performing the transformation if the ray is starting from world space. The returned ray is in camera space, leaving the rear lens element toward the film.

<<RealisticCamera Private Methods>>+= 
bool TraceLensesFromScene(const Ray &rCamera, Ray *rOut) const;

6.4.3 The Thick Lens Approximation

The thin lens approximation used in Section 6.2.3 was based on the simplifying assumption that the lens system had 0 thickness along the optical axis. The thick lens approximation of a lens system is slightly more accurate in that it accounts for the lens system’s z extent. After introducing the basic concepts of the thick lenses here, we’ll use the thick lens approximation to determine how far to place the lens system from the film in order to focus at the desired focal depth in Section 6.4.4.

The thick lens approximation represents a lens system by two pairs of distances along the optical axis—the focal points and the depths of the principal planes; these are two of the cardinal points of a lens system. If rays parallel to the optical axis are traced through an ideal lens system, all of the rays will intersect the optical axis at the same point—this is the focal point. (In practice, real lens systems aren’t perfectly ideal and incident rays at different heights will intersect the optical axis along a small range of z values—this is the spherical aberration.) Given a specific lens system, we can trace rays parallel to the optical axis through it from each side and compute their intersections with the z axis to find the focal points. (See Figure 6.20.)

Figure 6.20: Computing the Cardinal Points of a Lens System. The lens system described in the file lenses/dgauss.dat with an incident ray from the scene parallel to the optical axis (above the axis), and a ray from the film parallel to the optical axis (below). The intersections with the optical axis of the rays leaving the lens system due to these incident rays give the two focal points, f prime Subscript z (on the film side) and f Subscript z (on the scene side). The principal planes z equals p Subscript z and z equals p prime Subscript z are given by the intersection of the extension of each pair of incident and exiting rays with the original rays and are shown here as blue lines perpendicular to the optical axis.

Each principal plane is found by extending the incident ray parallel to the optical axis and the ray leaving the lens until they intersect; the z depth of the intersection gives the depth of the corresponding principal plane. Figure 6.20 shows a lens system with its focal points f Subscript z and f prime Subscript z and principal planes at z values p Subscript z and p prime Subscript z . (As in Section 6.2.3, primed variables represent points on the film side of the lens system, and unprimed variables represent points in the scene being imaged.)

Given the ray leaving the lens, finding the focal point requires first computing the t Subscript normal f value where the ray’s x and y components are zero. If the entering ray was offset from the optical axis only along x , then we’d like to find t Subscript normal f such that normal o Subscript x Baseline plus t Subscript normal f Baseline bold d Subscript x Baseline equals 0 . Thus,

t Subscript normal f Baseline equals minus normal o Subscript x Baseline slash bold d Subscript x Baseline period

In a similar manner, to find the t Subscript normal p for the principal plane where the ray leaving the lens has the same x height as the original ray, we have normal o Subscript x Baseline plus t Subscript normal p Baseline bold d Subscript x Baseline equals x , and so

t Subscript normal p Baseline equals left-parenthesis x minus normal o Subscript x Baseline right-parenthesis slash bold d Subscript x Baseline period

Once these two t values have been computed, the ray equation can be used to find the z coordinates of the corresponding points.

The ComputeCardinalPoints() method computes the z depths of the focal point and the principal plane for the given rays. Note that it assumes that the rays are in camera space but returns z values along the optical axis in lens space.

<<RealisticCamera Method Definitions>>+=  
void RealisticCamera::ComputeCardinalPoints(const Ray &rIn, const Ray &rOut, Float *pz, Float *fz) { Float tf = -rOut.o.x / rOut.d.x; *fz = -rOut(tf).z; Float tp = (rIn.o.x - rOut.o.x) / rOut.d.x; *pz = -rOut(tp).z; }

The ComputeThickLensApproximation() method computes both pairs of cardinal points for the lens system.

<<RealisticCamera Method Definitions>>+=  
void RealisticCamera::ComputeThickLensApproximation(Float pz[2], Float fz[2]) const { <<Find height x from optical axis for parallel rays>> 
Float x = .001 * film->diagonal;
<<Compute cardinal points for film side of lens system>> 
Ray rScene(Point3f(x, 0, LensFrontZ() + 1), Vector3f(0, 0, -1)); Ray rFilm; TraceLensesFromScene(rScene, &rFilm); ComputeCardinalPoints(rScene, rFilm, &pz[0], &fz[0]);
<<Compute cardinal points for scene side of lens system>> 
rFilm = Ray(Point3f(x, 0, LensRearZ() - 1), Vector3f(0, 0, 1)); TraceLensesFromFilm(rFilm, &rScene); ComputeCardinalPoints(rFilm, rScene, &pz[1], &fz[1]);
}

First, we must choose a height along the x axis for the rays to be traced. It should be far enough from x equals 0 so that there is sufficient numeric precision to accurately compute where rays leaving the lens system intersect the z axis, but not so high up the x axis that it hits the aperture stop on the ray through the lens system. Here, we use a small fraction of the film’s diagonal extent; this works well unless the aperture stop is extremely small.

<<Find height x from optical axis for parallel rays>>= 
Float x = .001 * film->diagonal;

To construct the ray from the scene entering the lens system rScene, we offset a bit from the front of the lens. (Recall that the ray passed to TraceLensesFromScene() should be in camera space.)

<<Compute cardinal points for film side of lens system>>= 
Ray rScene(Point3f(x, 0, LensFrontZ() + 1), Vector3f(0, 0, -1)); Ray rFilm; TraceLensesFromScene(rScene, &rFilm); ComputeCardinalPoints(rScene, rFilm, &pz[0], &fz[0]);

An equivalent process starting from the film side of the lens system gives us the other two cardinal points.

<<Compute cardinal points for scene side of lens system>>= 
rFilm = Ray(Point3f(x, 0, LensRearZ() - 1), Vector3f(0, 0, 1)); TraceLensesFromFilm(rFilm, &rScene); ComputeCardinalPoints(rFilm, rScene, &pz[1], &fz[1]);

6.4.4 Focusing

Lens systems can be focused at a given depth in the scene by moving the system in relation to the film so that a point at the desired focus depth images to a point on the film plane. The Gaussian lens equation, (6.3), gives us a relation that we can solve to focus a thick lens.

For thick lenses, the Gaussian lens equation relates distances from a point in the scene at z and the point it focuses to z prime by

StartFraction 1 Over z prime minus p prime Subscript z EndFraction minus StartFraction 1 Over z minus p Subscript z Baseline EndFraction equals StartFraction 1 Over f EndFraction period
(6.3)

For thin lenses, p Subscript z Baseline equals p prime Subscript z Baseline equals 0 , and Equation (6.1) follows.

If we know the positions p Subscript z and p prime Subscript z of the principal planes and the focal length of the lens f and would like to focus at some depth z along the optical axis, then we need to determine how far to translate the system delta so that

StartFraction 1 Over z prime minus p prime Subscript z plus delta EndFraction minus StartFraction 1 Over z minus p Subscript z Baseline plus delta EndFraction equals StartFraction 1 Over f EndFraction period

The focus point on the film side should be at the film, so z prime equals 0 , and z equals z Subscript normal f , the given focus depth. The only unknown is delta , and some algebraic manipulation gives us

delta equals one-half left-parenthesis p Subscript z Baseline minus z Subscript normal f Baseline plus p prime Subscript z minus StartRoot left-parenthesis p Subscript z Baseline minus z Subscript normal f Baseline minus p prime Subscript z right-parenthesis left-parenthesis p Subscript z Baseline minus z Subscript normal f Baseline minus 4 f minus p prime Subscript z right-parenthesis EndRoot right-parenthesis period
(6.4)

(There are actually two solutions, but this one, which is the closer of the two, gives a small adjustment to the lens position and is thus the appropriate one.)

FocusThickLens() focuses the lens system using this approximation. After computing delta , it returns the offset along the z axis from the film where the lens system should be placed.

<<RealisticCamera Method Definitions>>+=  
Float RealisticCamera::FocusThickLens(Float focusDistance) { Float pz[2], fz[2]; ComputeThickLensApproximation(pz, fz); <<Compute translation of lens, delta, to focus at focusDistance>> 
Float f = fz[0] - pz[0]; Float z = -focusDistance; Float delta = 0.5f * (pz[1] - z + pz[0] - std::sqrt((pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0])));
return elementInterfaces.back().thickness + delta; }

Equation (6.4) gives the offset delta . The focal length of the lens f is the distance between the cardinal points f prime Subscript z and p prime Subscript z . Note also that the negation of the focus distance is used for z , since the optical axis points along negative z .

<<Compute translation of lens, delta, to focus at focusDistance>>= 
Float f = fz[0] - pz[0]; Float z = -focusDistance; Float delta = 0.5f * (pz[1] - z + pz[0] - std::sqrt((pz[1] - z - pz[0]) * (pz[1] - z - 4 * f - pz[0])));

We can now finally implement the fragment in the RealisticCamera constructor that focuses the lens system. (Recall that the thickness of the rearmost element interface is the distance from the interface to the film.)

<<Compute lens–film distance for given focus distance>>= 
elementInterfaces.back().thickness = FocusThickLens(focusDistance);

6.4.5 The Exit Pupil

From a given point on the film plane, not all rays toward the rear lens element will successfully exit the lens system; some will be blocked by the aperture stop or will intersect the lens system enclosure. In turn, not all points on the rear lens element transmit radiance to the point on the film. The set of points on the rear element that do carry light through the lens system is called the exit pupil; its size and position vary across viewpoints on the film plane. (Analogously, the entrance pupil is the area over the front lens element where rays from a given point in the scene will reach the film.)

Figure 6.21 shows the exit pupil as seen from two points on the film plane with a wide angle lens. The exit pupil gets smaller for points toward the edges of the film. An implication of this shrinkage is vignetting.

Figure 6.21: The Exit Pupil for a 22-mm-Wide Angle Lens with a 5.5-mm Aperture (f/4). (a) Rays from a point at the edge of the film plane entering the rear lens element at various points. Dashed lines indicate rays that are blocked and don’t exit the lens system. (b) Image of the exit pupil as seen from the vantage point in (a). The rear lens element is black, while the exit pupil is shown in gray. (c) At the center of the film, a different region of the exit pupil allows rays out into the scene. (d) Image of the exit pupil as seen from the center of the film.

When tracing rays starting from the film, we’d like to avoid tracing too many rays that don’t make it through the lens system; therefore, it’s worth limiting sampling to the exit pupil itself and a small area around it rather than, for example, wastefully sampling the entire area of the rear lens element.

Computing the exit pupil at each point on the film plane before tracing a ray would be prohibitively expensive; instead the RealisticCamera implementation precomputes exit pupil bounds along segments of a line on the film plane. Since we assumed that the lens system is radially symmetric around the optical axis, exit pupil bounds will also be radially symmetric, and bounds for arbitrary points on the film plane can be found by rotating these segment bounds appropriately (Figure 6.22). These bounds are then used to efficiently find exit pupil bounds for specific film sample positions.

Figure 6.22: Precomputing Exit Pupil Bounds. (a) The RealisticCamera computes bounds of the exit pupil at a series of segments along the x axis of the film plane, up to the distance r from the center of the film to a corner. (b) Due to the assumption of radial symmetry, we can find exit pupil bounds for an arbitrary point on the film (solid dot) by computing the angle theta between the point and the x axis. If a point is sampled in the original exit pupil bounds and is then rotated by theta , we have a point in the exit pupil bounds at the original point.

One important subtlety to be aware of is that because the lens system is focused by translating it along the optical axis, the shape and position of the exit pupil change when the focus of the lens system is adjusted. Therefore, it’s critical that these bounds be computed after focusing.

<<Compute exit pupil bounds at sampled points on the film>>= 
int nSamples = 64; exitPupilBounds.resize(nSamples); ParallelFor( [&](int i) { Float r0 = (Float)i / nSamples * film->diagonal / 2; Float r1 = (Float)(i + 1) / nSamples * film->diagonal / 2; exitPupilBounds[i] = BoundExitPupil(r0, r1); }, nSamples);

<<RealisticCamera Private Data>>+= 
std::vector<Bounds2f> exitPupilBounds;

The BoundExitPupil() method computes a 2D bounding box of the exit pupil as seen from a point along a segment on the film plane. The bounding box is computed by attempting to trace rays through the lens system at a set of points on a plane tangent to the rear lens element. The bounding box of the rays that make it through the lens system gives an approximate bound on the exit pupil—see Figure 6.23.

Figure 6.23: 2D Illustration of How Exit Pupil Bounds Are Computed. BoundExitPupil() takes an interval along the x axis on the film. It samples a series of points along the interval (bottom of the figure). For each point, it also samples a point on the bounding box of the rear lens element’s extent on the plane tangent to its rear. It computes the bounding box on the tangent plane of all of the rays that make it through the lens system from points along the interval.

<<RealisticCamera Method Definitions>>+=  
Bounds2f RealisticCamera::BoundExitPupil(Float pFilmX0, Float pFilmX1) const { Bounds2f pupilBounds; <<Sample a collection of points on the rear lens to find exit pupil>> 
const int nSamples = 1024 * 1024; int nExitingRays = 0; <<Compute bounding box of projection of rear element on sampling plane>> 
Float rearRadius = RearElementRadius(); Bounds2f projRearBounds(Point2f(-1.5f * rearRadius, -1.5f * rearRadius), Point2f( 1.5f * rearRadius, 1.5f * rearRadius));
for (int i = 0; i < nSamples; ++i) { <<Find location of sample points on x segment and rear lens element>> 
Point3f pFilm(Lerp((i + 0.5f) / nSamples, pFilmX0, pFilmX1), 0, 0); Float u[2] = { RadicalInverse(0, i), RadicalInverse(1, i) }; Point3f pRear(Lerp(u[0], projRearBounds.pMin.x, projRearBounds.pMax.x), Lerp(u[1], projRearBounds.pMin.y, projRearBounds.pMax.y), LensRearZ());
<<Expand pupil bounds if ray makes it through the lens system>> 
if (Inside(Point2f(pRear.x, pRear.y), pupilBounds) || TraceLensesFromFilm(Ray(pFilm, pRear - pFilm), nullptr)) { pupilBounds = Union(pupilBounds, Point2f(pRear.x, pRear.y)); ++nExitingRays; }
}
<<Return entire element bounds if no rays made it through the lens system>> 
if (nExitingRays == 0) return projRearBounds;
<<Expand bounds to account for sample spacing>> 
pupilBounds = Expand(pupilBounds, 2 * projRearBounds.Diagonal().Length() / std::sqrt(nSamples));
return pupilBounds; }

The implementation samples the exit pupil fairly densely—at a total of 1024 squared points for each segment. We’ve found this sampling rate to provide good exit pupil bounds in practice.

<<Sample a collection of points on the rear lens to find exit pupil>>= 
const int nSamples = 1024 * 1024; int nExitingRays = 0; <<Compute bounding box of projection of rear element on sampling plane>> 
Float rearRadius = RearElementRadius(); Bounds2f projRearBounds(Point2f(-1.5f * rearRadius, -1.5f * rearRadius), Point2f( 1.5f * rearRadius, 1.5f * rearRadius));
for (int i = 0; i < nSamples; ++i) { <<Find location of sample points on x segment and rear lens element>> 
Point3f pFilm(Lerp((i + 0.5f) / nSamples, pFilmX0, pFilmX1), 0, 0); Float u[2] = { RadicalInverse(0, i), RadicalInverse(1, i) }; Point3f pRear(Lerp(u[0], projRearBounds.pMin.x, projRearBounds.pMax.x), Lerp(u[1], projRearBounds.pMin.y, projRearBounds.pMax.y), LensRearZ());
<<Expand pupil bounds if ray makes it through the lens system>> 
if (Inside(Point2f(pRear.x, pRear.y), pupilBounds) || TraceLensesFromFilm(Ray(pFilm, pRear - pFilm), nullptr)) { pupilBounds = Union(pupilBounds, Point2f(pRear.x, pRear.y)); ++nExitingRays; }
}

The bounding box of the rear element in the plane perpendicular to it is not enough to be a conservative bound of the projection of the exit pupil on that plane; because the element is generally curved, rays that pass through the plane outside of that bound may themselves intersect the valid extent of the rear lens element. Rather than compute a precise bound, we’ll increase the bounds substantially. The result is that many of the samples taken to compute the exit pupil bound will be wasted; in practice, this is a minor price to pay, as these samples are generally quickly terminated during the lens ray-tracing phase.

<<Compute bounding box of projection of rear element on sampling plane>>= 
Float rearRadius = RearElementRadius(); Bounds2f projRearBounds(Point2f(-1.5f * rearRadius, -1.5f * rearRadius), Point2f( 1.5f * rearRadius, 1.5f * rearRadius));

The x sample point on the film is found by linearly interpolating between the x interval endpoints. The RadicalInverse() function that is used to compute the interpolation offsets for the sample point inside the exit pupil bounding box will be defined later, in Section 7.4.1. There, we will see that the sampling strategy implemented here corresponds to using Hammersley points in 3D; the resulting point set minimizes gaps in the coverage of the overall 3D domain, which in turn ensures an accurate exit pupil bound estimate.

<<Find location of sample points on x segment and rear lens element>>= 
Point3f pFilm(Lerp((i + 0.5f) / nSamples, pFilmX0, pFilmX1), 0, 0); Float u[2] = { RadicalInverse(0, i), RadicalInverse(1, i) }; Point3f pRear(Lerp(u[0], projRearBounds.pMin.x, projRearBounds.pMax.x), Lerp(u[1], projRearBounds.pMin.y, projRearBounds.pMax.y), LensRearZ());

Now we can construct a ray from pFilm to pRear and determine if it is within the exit pupil by seeing if it makes it out of the front of the lens system. If so, the exit pupil bounds are expanded to include this point. If the sampled point is already inside the exit pupil’s bounding box as computed so far, then we can skip the lens ray tracing step to save a bit of unnecessary work.

<<Expand pupil bounds if ray makes it through the lens system>>= 
if (Inside(Point2f(pRear.x, pRear.y), pupilBounds) || TraceLensesFromFilm(Ray(pFilm, pRear - pFilm), nullptr)) { pupilBounds = Union(pupilBounds, Point2f(pRear.x, pRear.y)); ++nExitingRays; }

It may be that none of the sample rays makes it through the lens system; this case can legitimately happen with some very wide-angle lenses where the exit pupil vanishes at the edges of the film extent, for example. In this case, the bound doesn’t matter and BoundExitPupil() returns the bound that encompasses the entire rear lens element.

<<Return entire element bounds if no rays made it through the lens system>>= 
if (nExitingRays == 0) return projRearBounds;

While one sample may have made it through the lens system and one of its neighboring samples didn’t, it may well be that another sample very close to the neighbor actually would have made it out. Therefore, the final bound is expanded by roughly the spacing between samples in each direction in order to account for this uncertainty.

<<Expand bounds to account for sample spacing>>= 
pupilBounds = Expand(pupilBounds, 2 * projRearBounds.Diagonal().Length() / std::sqrt(nSamples));

Given the precomputed bounds stored in RealisticCamera::exitPupilBounds, the SampleExitPupil() method can fairly efficiently find the bounds on the exit pupil for a given point on the film plane. It then samples a point inside this bounding box for the ray from the film to pass through. In order to accurately model the radiometry of image formation, the following code will need to know the area of this bounding box, so it is returned via sampleBoundsArea.

<<RealisticCamera Method Definitions>>+=  
Point3f RealisticCamera::SampleExitPupil(const Point2f &pFilm, const Point2f &lensSample, Float *sampleBoundsArea) const { <<Find exit pupil bound for sample distance from film center>> 
Float rFilm = std::sqrt(pFilm.x * pFilm.x + pFilm.y * pFilm.y); int rIndex = rFilm / (film->diagonal / 2) * exitPupilBounds.size(); rIndex = std::min((int)exitPupilBounds.size() - 1, rIndex); Bounds2f pupilBounds = exitPupilBounds[rIndex]; if (sampleBoundsArea) *sampleBoundsArea = pupilBounds.Area();
<<Generate sample point inside exit pupil bound>> 
Point2f pLens = pupilBounds.Lerp(lensSample);
<<Return sample point rotated by angle of pFilm with plus x axis>> 
Float sinTheta = (rFilm != 0) ? pFilm.y / rFilm : 0; Float cosTheta = (rFilm != 0) ? pFilm.x / rFilm : 1; return Point3f(cosTheta * pLens.x - sinTheta * pLens.y, sinTheta * pLens.x + cosTheta * pLens.y, LensRearZ());
}

<<Find exit pupil bound for sample distance from film center>>= 
Float rFilm = std::sqrt(pFilm.x * pFilm.x + pFilm.y * pFilm.y); int rIndex = rFilm / (film->diagonal / 2) * exitPupilBounds.size(); rIndex = std::min((int)exitPupilBounds.size() - 1, rIndex); Bounds2f pupilBounds = exitPupilBounds[rIndex]; if (sampleBoundsArea) *sampleBoundsArea = pupilBounds.Area();

Given the pupil’s bounding box, a point inside it is sampled via linear interpolation with the provided lensSample value, which is in left-bracket 0 comma 1 right-parenthesis squared .

<<Generate sample point inside exit pupil bound>>= 
Point2f pLens = pupilBounds.Lerp(lensSample);

Because the exit pupil bound was computed from a point on the film along the plus x axis but the point pFilm is an arbitrary point on the film, the sample point in the exit pupil bound must be rotated by the same angle as pFilm makes with the plus x axis.

<<Return sample point rotated by angle of pFilm with plus x axis>>= 
Float sinTheta = (rFilm != 0) ? pFilm.y / rFilm : 0; Float cosTheta = (rFilm != 0) ? pFilm.x / rFilm : 1; return Point3f(cosTheta * pLens.x - sinTheta * pLens.y, sinTheta * pLens.x + cosTheta * pLens.y, LensRearZ());

6.4.6 Generating Rays

Now that we have the machinery to trace rays through lens systems and to sample points in the exit pupil bound from points on the film plane, transforming a CameraSample into a ray leaving the camera is fairly straightforward: we need to compute the sample’s position on the film plane and generate a ray from this point to the rear lens element, which is then traced through the lens system.

<<RealisticCamera Method Definitions>>+= 
Float RealisticCamera::GenerateRay(const CameraSample &sample, Ray *ray) const { <<Find point on film, pFilm, corresponding to sample.pFilm>> 
Point2f s(sample.pFilm.x / film->fullResolution.x, sample.pFilm.y / film->fullResolution.y); Point2f pFilm2 = film->GetPhysicalExtent().Lerp(s); Point3f pFilm(-pFilm2.x, pFilm2.y, 0);
<<Trace ray from pFilm through lens system>> 
Float exitPupilBoundsArea; Point3f pRear = SampleExitPupil(Point2f(pFilm.x, pFilm.y), sample.pLens, &exitPupilBoundsArea); Ray rFilm(pFilm, pRear - pFilm, Infinity, Lerp(sample.time, shutterOpen, shutterClose)); if (!TraceLensesFromFilm(rFilm, ray)) return 0;
<<Finish initialization of RealisticCamera ray>> 
*ray = CameraToWorld(*ray); ray->d = Normalize(ray->d); ray->medium = medium;
<<Return weighting for RealisticCamera ray>> 
Float cosTheta = Normalize(rFilm.d).z; Float cos4Theta = (cosTheta * cosTheta) * (cosTheta * cosTheta); if (simpleWeighting) return cos4Theta; else return (shutterClose - shutterOpen) * (cos4Theta * exitPupilBoundsArea) / (LensRearZ() * LensRearZ());
}

The CameraSample::pFilm value is with respect to the overall resolution of the image in pixels. Here, we’re operating with a physical model of a sensor, so we start by converting back to a sample in left-bracket 0 comma 1 right-parenthesis squared . Next, the corresponding point on the film is found by linearly interpolating with this sample value over its area.

<<Find point on film, pFilm, corresponding to sample.pFilm>>= 
Point2f s(sample.pFilm.x / film->fullResolution.x, sample.pFilm.y / film->fullResolution.y); Point2f pFilm2 = film->GetPhysicalExtent().Lerp(s); Point3f pFilm(-pFilm2.x, pFilm2.y, 0);

SampleExitPupil() then gives us a point on the plane tangent to the rear lens element, which in turn lets us determine the ray’s direction. In turn, we can trace this ray through the lens system. If the ray is blocked by the aperture stop or otherwise doesn’t make it through the lens system, GenerateRay() returns a 0 weight. (Callers should be sure to check for this case.)

<<Trace ray from pFilm through lens system>>= 
Float exitPupilBoundsArea; Point3f pRear = SampleExitPupil(Point2f(pFilm.x, pFilm.y), sample.pLens, &exitPupilBoundsArea); Ray rFilm(pFilm, pRear - pFilm, Infinity, Lerp(sample.time, shutterOpen, shutterClose)); if (!TraceLensesFromFilm(rFilm, ray)) return 0;

If the ray does successfully exit the lens system, then the usual details have to be handled to finish its initialization.

<<Finish initialization of RealisticCamera ray>>= 
*ray = CameraToWorld(*ray); ray->d = Normalize(ray->d); ray->medium = medium;

The fragment <<Return weighting for RealisticCamera ray>> will be defined later, in Section 13.6.6, after some necessary background from Monte Carlo integration has been introduced.

6.4.7 The Camera Measurement Equation

Given this more accurate simulation of the process of real image formation, it’s also worthwhile to more carefully define the radiometry of the measurement made by a film or a camera sensor. Rays from the exit pupil to the film carry radiance from the scene; as considered from a point on the film plane, there is thus a set of directions from which radiance is incident. The distribution of radiance leaving the exit pupil is affected by the amount of defocus blur seen by the point on the film—Figure 6.24 shows two renderings of the exit pupil’s radiance as seen from two points on the film.

Figure 6.24: The Exit Pupil, as Seen from Two Points on the Film Plane in Figure 6.16. (first) The exit pupil as seen from a point where the scene is in sharp focus; the incident radiance is effectively constant over its area. (second) As seen from a pixel in an out-of-focus area, the exit pupil is a small image of part of the scene, with potentially rapidly varying radiance.

Given the incident radiance function, we can define the irradiance at a point on the film plane. If we start with the definition of irradiance in terms of radiance, Equation (5.4), we can then convert from an integral over solid angle to an integral over area (in this case, an area of the plane tangent to the rear lens element that bounds the exit pupil, upper A Subscript normal e ) using Equation (5.6). This gives us the irradiance for a point normal p Subscript on the film plane:

upper E left-parenthesis normal p Subscript Baseline right-parenthesis equals integral Underscript upper A Subscript normal e Baseline Endscripts upper L Subscript normal i Baseline left-parenthesis normal p Subscript Baseline comma normal p Superscript prime Baseline right-parenthesis StartFraction StartAbsoluteValue cosine theta cosine theta Superscript prime Baseline EndAbsoluteValue Over StartAbsoluteValue EndAbsoluteValue normal p Superscript prime Baseline minus normal p Subscript Baseline StartAbsoluteValue EndAbsoluteValue squared EndFraction normal d upper A Subscript normal e Baseline period

Figure 6.25 shows the geometry of the situation.

Figure 6.25: Geometric setting for the irradiance measurement equation, (6.5). Radiance can be measured as it passes through points normal p prime on the plane tangent to the rear lens element to a point on the film plane normal p Subscript . z is the axial distance from the film plane to the rear element tangent plane, RealisticCamera::LensRearZ(), and theta is the angle between the vector from normal p prime to normal p Subscript and the optical axis.

Because the film plane is perpendicular to the exit pupil plane, theta equals theta prime . We can further take advantage of the fact that the distance between normal p Subscript and normal p prime is equal to the axial distance from the film plane to the exit pupil (which we’ll denote here by z ) divided by cosine theta . Putting this all together, we have

upper E left-parenthesis normal p Subscript Baseline right-parenthesis equals StartFraction 1 Over z squared EndFraction integral Underscript upper A Subscript normal e Baseline Endscripts upper L Subscript normal i Baseline left-parenthesis normal p Subscript Baseline comma normal p Superscript prime Baseline right-parenthesis StartAbsoluteValue cosine Superscript 4 Baseline theta EndAbsoluteValue normal d upper A Subscript normal e Baseline period
(6.5)

For cameras where the extent of the film is relatively large with respect to the distance z , the cosine Superscript 4 Baseline theta term can meaningfully reduce the incident irradiance—this factor also contributes to vignetting. Most modern digital cameras correct for this effect with preset correction factors that increase pixel values toward the edges of the sensor.

Integrating irradiance at a point on the film over the time that the shutter is open gives fluence, which is the radiometric unit for energy per area, normal upper J slash normal m squared .

upper H left-parenthesis normal p Subscript Baseline right-parenthesis equals StartFraction 1 Over z squared EndFraction integral Subscript t 0 Superscript t 1 Baseline integral Underscript upper A Subscript normal e Baseline Endscripts upper L Subscript normal i Baseline left-parenthesis normal p Subscript Baseline comma normal p prime comma t Superscript prime Baseline right-parenthesis StartAbsoluteValue cosine Superscript 4 Baseline theta EndAbsoluteValue normal d upper A Subscript normal e Baseline normal d t Superscript prime Baseline period
(6.6)

Measuring fluence at a point captures the effect that the amount of energy received on the film plane is partially related to the length of time the camera shutter is open.

Photographic film (or CCD or CMOS sensors in digital cameras) actually measure radiant energy over a small area. Taking Equation (6.6) and also integrating over sensor pixel area, upper A Subscript normal p , we have

upper J equals StartFraction 1 Over z squared EndFraction integral Underscript upper A Subscript normal p Baseline Endscripts integral Subscript t 0 Superscript t 1 Baseline integral Underscript upper A Subscript normal e Baseline Endscripts upper L Subscript normal i Baseline left-parenthesis normal p Subscript Baseline comma normal p prime comma t Superscript prime Baseline right-parenthesis StartAbsoluteValue cosine Superscript 4 Baseline theta EndAbsoluteValue normal d upper A Subscript normal e Baseline normal d t prime normal d upper A Subscript normal p Baseline comma
(6.7)

the Joules arriving at a pixel.

In Section 13.2, we’ll see how Monte Carlo can be applied to estimate the values of these various integrals. Then in Section 13.6.6 we will define the fragment <<Return weighting for RealisticCamera ray>> in RealisticCamera::GenerateRay(); various approaches to computing the weight allow us to compute each of these quantities. Section 16.1.1 defines the importance function of a camera model, which characterizes its sensitivity to incident illumination arriving along different rays.