12.3 Distant Lights

Another useful light source type is the distant light, also known as a directional light. It describes an emitter that deposits illumination from the same direction at every point in space. Such a light is also called a point light “at infinity,” since, as a point light becomes progressively farther away, it acts more and more like a directional light. For example, the sun (as considered from Earth) can be thought of as a directional light source. Although it is actually an area light source, the illumination effectively arrives at Earth in nearly parallel beams because it is so far away.

<<DistantLight Definition>>= 
class DistantLight : public LightBase { public: <<DistantLight Public Methods>> 
DistantLight(const Transform &renderFromLight, Spectrum Lemit, Float scale) : LightBase(LightType::DeltaDirection, renderFromLight, {}), Lemit(LookupSpectrum(Lemit)), scale(scale) {} static DistantLight *Create(const Transform &renderFromLight, const ParameterDictionary &parameters, const RGBColorSpace *colorSpace, const FileLoc *loc, Allocator alloc); SampledSpectrum Phi(SampledWavelengths lambda) const; PBRT_CPU_GPU Float PDF_Li(LightSampleContext, Vector3f, bool allowIncompletePDF) const { return 0; } PBRT_CPU_GPU pstd::optional<LightLeSample> SampleLe(Point2f u1, Point2f u2, SampledWavelengths &lambda, Float time) const; PBRT_CPU_GPU void PDF_Le(const Ray &, Float *pdfPos, Float *pdfDir) const; PBRT_CPU_GPU void PDF_Le(const Interaction &, Vector3f w, Float *pdfPos, Float *pdfDir) const { LOG_FATAL("Shouldn't be called for non-area lights"); } pstd::optional<LightBounds> Bounds() const { return {}; } std::string ToString() const; void Preprocess(const Bounds3f &sceneBounds) { sceneBounds.BoundingSphere(&sceneCenter, &sceneRadius); } pstd::optional<LightLiSample> SampleLi(LightSampleContext ctx, Point2f u, SampledWavelengths lambda, bool allowIncompletePDF) const { Vector3f wi = Normalize(renderFromLight(Vector3f(0, 0, 1))); Point3f pOutside = ctx.p() + wi * (2 * sceneRadius); return LightLiSample(scale * Lemit->Sample(lambda), wi, 1, Interaction(pOutside, nullptr)); }
private: <<DistantLight Private Members>> 
const DenselySampledSpectrum *Lemit; Float scale; Point3f sceneCenter; Float sceneRadius;
};

The DistantLight constructor does not take a MediumInterface parameter; the only reasonable medium for a distant light to be in is a vacuum—if it was itself in a medium that absorbed any light at all, then all of its emission would be absorbed, since it is modeled as being infinitely far away.

<<DistantLight Public Methods>>= 
DistantLight(const Transform &renderFromLight, Spectrum Lemit, Float scale) : LightBase(LightType::DeltaDirection, renderFromLight, {}), Lemit(LookupSpectrum(Lemit)), scale(scale) {}

<<DistantLight Private Members>>= 
const DenselySampledSpectrum *Lemit; Float scale;

Some of the DistantLight methods need to know the bounds of the scene. Because pbrt creates lights before the scene geometry, these bounds are not available when the DistantLight constructor executes. Therefore, DistantLight implements the optional Preprocess() method where it converts the scene’s Bounds3f to a bounding sphere, which will be an easier representation to work with in the following.

<<DistantLight Public Methods>>+=  
void Preprocess(const Bounds3f &sceneBounds) { sceneBounds.BoundingSphere(&sceneCenter, &sceneRadius); }

<<DistantLight Private Members>>+= 
Point3f sceneCenter; Float sceneRadius;

The incident radiance at a point normal p Subscript due to a distant light can be described using a Dirac delta distribution,

upper L Subscript normal i Baseline left-parenthesis normal p Subscript Baseline comma omega Subscript Baseline right-parenthesis equals upper L Subscript normal e Baseline delta left-parenthesis omega Subscript Baseline minus omega Subscript normal l Baseline right-parenthesis comma

where the light’s direction is omega Subscript normal l . Given this definition, the implementation of the SampleLi() method is straightforward: the incident direction and radiance are always the same. The only interesting bit is the initialization of the Interaction that provides the second point for the future shadow ray. It is set along the distant light’s incident direction at a distance of twice the radius of the scene’s bounding sphere, guaranteeing a second point that is outside of the scene’s bounds (Figure 12.13).

Figure 12.13: Computing the Second Point for a DistantLight Shadow Ray. Given a sphere that bounds the scene (dashed line) with radius r and given some point in the scene normal p Subscript , if we then move a distance of 2 r along any vector from normal p Subscript , the resulting point must be outside of the scene’s bound. If a shadow ray to such a point is unoccluded, then we can be certain that the point normal p Subscript receives illumination from a distant light along the vector’s direction.

<<DistantLight Public Methods>>+= 
pstd::optional<LightLiSample> SampleLi(LightSampleContext ctx, Point2f u, SampledWavelengths lambda, bool allowIncompletePDF) const { Vector3f wi = Normalize(renderFromLight(Vector3f(0, 0, 1))); Point3f pOutside = ctx.p() + wi * (2 * sceneRadius); return LightLiSample(scale * Lemit->Sample(lambda), wi, 1, Interaction(pOutside, nullptr)); }

The distant light is different than the lights we have seen so far in that the amount of power it emits is related to the spatial extent of the scene. In fact, it is proportional to the area of the scene receiving light. To see why this is so, consider a disk of area upper A being illuminated by a distant light with emitted radiance upper L Subscript Superscript where the incident light arrives along the disk’s normal direction. The total power reaching the disk is normal upper Phi equals upper A upper L Subscript Superscript . As the size of the receiving surface varies, power varies proportionally.

To find the emitted power for a DistantLight, it is impractical to compute the total surface area of the objects that are visible to the light. Instead, we will approximate this area with a disk inside the scene’s bounding sphere oriented in the light’s direction (Figure 12.14). This will always overestimate the actual area but is sufficient for the needs of code elsewhere in the system.

Figure 12.14: An approximation of the power emitted by a distant light into a given scene can be obtained by finding the sphere that bounds the scene, computing the area of an inscribed disk, and computing the power that arrives on the surface of that disk.

<<DistantLight Method Definitions>>= 
SampledSpectrum DistantLight::Phi(SampledWavelengths lambda) const { return scale * Lemit->Sample(lambda) * Pi * Sqr(sceneRadius); }