12.6 Infinite Area Lights

Another useful kind of light is the infinite area light—an infinitely far away area light source that surrounds the entire scene. One way to visualize this light is as an enormous sphere that casts light into the scene from every direction. One important use of infinite area lights is for environment lighting, where an image that represents illumination in an environment is used to illuminate synthetic objects as if they were in that environment. Figure 12.19 compares illuminating a car model with a standard area light to illuminating it with a few environment maps that simulate illumination from the sky at a few different times of day. The increase in realism is striking. The InfiniteAreaLight class is implemented in lights/infinite.h and lights/infinite.cpp.

Figure 12.19: Car model (1) illuminated with an area light and a directional light, (2) illuminated with morning skylight from an environment map, (3) using a midday skylight distribution and (4) using a sunset environment map. Using a realistic distribution of illumination gives an image that is much more visually compelling. In particular, with illumination arriving from all directions, the glossy reflective properties of the paint are much more visually apparent. (Model courtesy of Yasutoshi Mori.)

A widely used representation for light for this application is the latitude–longitude radiance map. (This representation is also known as the equirectangular projection.) The EnvironmentCamera can be used to create image maps for the light, or see the “Further Reading” section for information about techniques for capturing this lighting data from real-world environments. Figure 12.20) shows the radiance maps used in Figure 12.19.

Figure 12.20: Environment Maps Used for Illumination in Figure 12.19 (1) Morning, (2) midday, and (3) sunset sky. (The bottom halves of these maps aren’t shown here, since they are just black pixels.)

<<InfiniteAreaLight Declarations>>= 
class InfiniteAreaLight : public Light { public: <<InfiniteAreaLight Public Methods>> 
InfiniteAreaLight(const Transform &LightToWorld, const Spectrum &power, int nSamples, const std::string &texmap); void Preprocess(const Scene &scene) { scene.WorldBound().BoundingSphere(&worldCenter, &worldRadius); } Spectrum Power() const; Spectrum Le(const RayDifferential &ray) const; Spectrum Sample_Li(const Interaction &ref, const Point2f &u, Vector3f *wi, Float *pdf, VisibilityTester *vis) const; Float Pdf_Li(const Interaction &, const Vector3f &) const; Spectrum Sample_Le(const Point2f &u1, const Point2f &u2, Float time, Ray *ray, Normal3f *nLight, Float *pdfPos, Float *pdfDir) const; void Pdf_Le(const Ray &, const Normal3f &, Float *pdfPos, Float *pdfDir) const;
private: <<InfiniteAreaLight Private Data>> 
std::unique_ptr<MIPMap<RGBSpectrum>> Lmap; Point3f worldCenter; Float worldRadius; std::unique_ptr<Distribution2D> distribution;

Like the other lights, the InfiniteAreaLight takes a transformation matrix; here, its use is to orient the image map. It then uses spherical coordinates to map from directions on the sphere to left-parenthesis theta comma phi right-parenthesis directions, and from there to left-parenthesis u comma v right-parenthesis texture coordinates. The provided transformation thus determines which direction is “up.”

The constructor loads the image data from the disk and creates a MIPMap to store it. The fragment that loads the data, <<Read texel data from texmap and initialize Lmap>>, is straightforward and won’t be included here. The other code fragment in the constructor, <<Initialize sampling PDFs for infinite area light>>, is related to Monte Carlo sampling of InfiniteAreaLights and will be defined later, in Section 14.2.4.

As with DistantLights, because the light is defined as being infinitely far away, the MediumInterface for an infinite area light must have nullptr values for its Medium *s, corresponding to a vacuum.

<<InfiniteAreaLight Method Definitions>>= 
InfiniteAreaLight::InfiniteAreaLight(const Transform &LightToWorld, const Spectrum &L, int nSamples, const std::string &texmap) : Light((int)LightFlags::Infinite, LightToWorld, MediumInterface(), nSamples) { <<Read texel data from texmap and initialize Lmap>> 
Point2i resolution; std::unique_ptr<RGBSpectrum[]> texels(nullptr); if (texmap != "") { texels = ReadImage(texmap, &resolution); if (texels) for (int i = 0; i < resolution.x * resolution.y; ++i) texels[i] *= L.ToRGBSpectrum(); } if (!texels) { resolution.x = resolution.y = 1; texels = std::unique_ptr<RGBSpectrum[]>(new RGBSpectrum[1]); texels[0] = L.ToRGBSpectrum(); } Lmap.reset(new MIPMap<RGBSpectrum>(resolution, texels.get()));
<<Initialize sampling PDFs for infinite area light>> 
<<Compute scalar-valued image img from environment map>> 
int width = resolution.x, height = resolution.y; Float filter = (Float)1 / std::max(width, height); std::unique_ptr<Float[]> img(new Float[width * height]); for (int v = 0; v < height; ++v) { Float vp = (Float)v / (Float)height; Float sinTheta = std::sin(Pi * Float(v + .5f) / Float(height)); for (int u = 0; u < width; ++u) { Float up = (Float)u / (Float)width; img[u + v * width] = Lmap->Lookup(Point2f(up, vp), filter).y(); img[u + v * width] *= sinTheta; } }
<<Compute sampling distributions for rows and columns of image>> 
distribution.reset(new Distribution2D(img.get(), width, height));

<<InfiniteAreaLight Private Data>>= 
std::unique_ptr<MIPMap<RGBSpectrum>> Lmap;

Like DistantLights, InfiniteAreaLights also need the scene bounds; here again, the Preprocess() method finds the scene bounds after all of the scene geometry has been created.

<<InfiniteAreaLight Public Methods>>= 
void Preprocess(const Scene &scene) { scene.WorldBound().BoundingSphere(&worldCenter, &worldRadius); }

<<InfiniteAreaLight Private Data>>+=  
Point3f worldCenter; Float worldRadius;

Because InfiniteAreaLights cast light from all directions, it’s also necessary to use Monte Carlo integration to sample their illumination. Therefore, the InfiniteAreaLight::Sample_Li() method will be defined in Section 14.2.

Like directional lights, the total power from the infinite area light is related to the surface area of the scene. Like many other lights in this chapter, the power computed here is approximate; here, all texels are given equal weight, which ignores the fact that with an equirectangular projection, the differential solid angle subtended by each pixel varies with its theta value (Section 14.2.4).

<<InfiniteAreaLight Method Definitions>>+=  
Spectrum InfiniteAreaLight::Power() const { return Pi * worldRadius * worldRadius * Spectrum(Lmap->Lookup(Point2f(.5f, .5f), .5f), SpectrumType::Illuminant); }

Because infinite area lights need to be able to contribute radiance to rays that don’t hit any geometry in the scene, we’ll add a method to the base Light class that returns emitted radiance due to that light along a ray that escapes the scene bounds. (The default implementation for other lights returns no radiance.) It is the responsibility of the integrators to call this method for these rays.

<<Light Method Definitions>>+= 
Spectrum Light::Le(const RayDifferential &ray) const { return Spectrum(0.f); }

<<InfiniteAreaLight Method Definitions>>+=  
Spectrum InfiniteAreaLight::Le(const RayDifferential &ray) const { Vector3f w = Normalize(WorldToLight(ray.d)); Point2f st(SphericalPhi(w) * Inv2Pi, SphericalTheta(w) * InvPi); return Spectrum(Lmap->Lookup(st), SpectrumType::Illuminant); }