2.5 Rays

A ray is a semi-infinite line specified by its origin and direction. pbrt represents a Ray with a Point3f for the origin and a Vector3f for the direction. We only need rays with floating-point origins and directions, so Ray isn’t a template class parameterized by an arbitrary type, as points, vectors, and normals were. A ray is denoted by  normal r ; it has origin normal o and direction bold d , as shown in Figure 2.7.

Figure 2.7: A ray is a semi-infinite line defined by its origin normal o and its direction vector bold d .

<<Ray Declarations>>= 
class Ray { public: <<Ray Public Methods>> 
Ray() : tMax(Infinity), time(0.f), medium(nullptr) { } Ray(const Point3f &o, const Vector3f &d, Float tMax = Infinity, Float time = 0.f, const Medium *medium = nullptr) : o(o), d(d), tMax(tMax), time(time), medium(medium) { } Point3f operator()(Float t) const { return o + d * t; } bool HasNaNs() const { return (o.HasNaNs() || d.HasNaNs() || std::isnan(tMax)); } friend std::ostream& operator<<(std::ostream& os, const Ray &r) { os << "[o=" << r.o << ", d=" << r.d << ", tMax=" << r.tMax << ", time=" << r.time << "]"; return os; }
<<Ray Public Data>> 
Point3f o; Vector3f d; mutable Float tMax; Float time; const Medium *medium;
};

Because we will be referring to these variables often throughout the code, the origin and direction members of a Ray are succinctly named o and d. Note that we again make the data publicly available for convenience.

<<Ray Public Data>>= 

The parametric form of a ray expresses it as a function of a scalar value t , giving the set of points that the ray passes through:

normal r left-parenthesis t right-parenthesis equals normal o plus t bold d Baseline 0 less-than-or-equal-to t less-than normal infinity period
(2.3)

The Ray also includes a member variable that limits the ray to a segment along its infinite extent. This field, tMax, allows us to restrict the ray to a segment of points left-bracket normal o comma normal r left-parenthesis t Subscript normal m normal a normal x Baseline right-parenthesis right-parenthesis .

Notice that this field is declared as mutable, meaning that it can be changed even if the Ray that contains it is const—thus, when a ray is passed to a method that takes a const Ray &, that method is not allowed to modify its origin or direction but can modify its extent. This convention fits one of the most common uses of rays in the system, as parameters to ray–object intersection testing routines, which will record the offsets to the closest intersection in tMax.

<<Ray Public Data>>+=  
mutable Float tMax;

Each ray has a time value associated with it. In scenes with animated objects, the rendering system constructs a representation of the scene at the appropriate time for each ray.

<<Ray Public Data>>+=  
Float time;

Finally, each ray records the medium containing its origin. The Medium class, introduced in Section 11.3, encapsulates the (potentially spatially varying) properties of media such as a foggy atmosphere, smoke, or scattering liquids like milk or shampoo. Associating this information with rays makes it possible for other parts of the system to account correctly for the effect of rays passing from one medium to another.

<<Ray Public Data>>+= 
const Medium *medium;

Constructing Rays is straightforward. The default constructor relies on the Point3f and Vector3f constructors to set the origin and direction to left-parenthesis 0 comma 0 comma 0 right-parenthesis . Alternately, a particular point and direction can be provided. If an origin and direction are provided, the constructor allows a value to be given for tMax, the ray’s time and medium.

<<Ray Public Methods>>= 
Ray() : tMax(Infinity), time(0.f), medium(nullptr) { } Ray(const Point3f &o, const Vector3f &d, Float tMax = Infinity, Float time = 0.f, const Medium *medium = nullptr) : o(o), d(d), tMax(tMax), time(time), medium(medium) { }

Because position along a ray can be thought of as a function of a single parameter t , the Ray class overloads the function application operator for rays. This way, when we need to find the point at a particular position along a ray, we can write code like:

Ray r(Point3f(0, 0, 0), Vector3f(1, 2, 3)); Point3f p = r(1.7);

<<Ray Public Methods>>+= 
Point3f operator()(Float t) const { return o + d * t; }

2.5.1 Ray Differentials

In order to be able to perform better antialiasing with the texture functions defined in Chapter 10, pbrt can keep track of some additional information with rays. In Section 10.1, this information will be used to compute values that are used by the Texture class to estimate the projected area on the image plane of a small part of the scene. From this, the Texture can compute the texture’s average value over that area, leading to a higher-quality final image.

RayDifferential is a subclass of Ray that contains additional information about two auxiliary rays. These extra rays represent camera rays offset by one sample in the x and y direction from the main ray on the film plane. By determining the area that these three rays project to on an object being shaded, the Texture can estimate an area to average over for proper antialiasing.

Because the RayDifferential class inherits from Ray, geometric interfaces in the system can be written to take const Ray & parameters, so that either a Ray or RayDifferential can be passed to them. Only the routines that need to account for antialiasing and texturing require RayDifferential parameters.

<<Ray Declarations>>+= 
class RayDifferential : public Ray { public: <<RayDifferential Public Methods>> 
RayDifferential() { hasDifferentials = false; } RayDifferential(const Point3f &o, const Vector3f &d, Float tMax = Infinity, Float time = 0.f, const Medium *medium = nullptr) : Ray(o, d, tMax, time, medium) { hasDifferentials = false; } RayDifferential(const Ray &ray) : Ray(ray) { hasDifferentials = false; } bool HasNaNs() const { return Ray::HasNaNs() || (hasDifferentials && (rxOrigin.HasNaNs() || ryOrigin.HasNaNs() || rxDirection.HasNaNs() || ryDirection.HasNaNs())); } void ScaleDifferentials(Float s) { rxOrigin = o + (rxOrigin - o) * s; ryOrigin = o + (ryOrigin - o) * s; rxDirection = d + (rxDirection - d) * s; ryDirection = d + (ryDirection - d) * s; }
<<RayDifferential Public Data>> 
bool hasDifferentials; Point3f rxOrigin, ryOrigin; Vector3f rxDirection, ryDirection;
};

The RayDifferential constructors mirror the Ray’s constructors.

<<RayDifferential Public Methods>>= 
RayDifferential() { hasDifferentials = false; } RayDifferential(const Point3f &o, const Vector3f &d, Float tMax = Infinity, Float time = 0.f, const Medium *medium = nullptr) : Ray(o, d, tMax, time, medium) { hasDifferentials = false; }

<<RayDifferential Public Data>>= 
bool hasDifferentials; Point3f rxOrigin, ryOrigin; Vector3f rxDirection, ryDirection;

There is a constructor to create RayDifferentials from Rays. The constructor sets hasDifferentials to false initially because the neighboring rays, if any, are not known.

<<RayDifferential Public Methods>>+=  
RayDifferential(const Ray &ray) : Ray(ray) { hasDifferentials = false; }

Camera implementations in pbrt compute differentials for rays leaving the camera under the assumption that camera rays are spaced one pixel apart. Integrators such as the SamplerIntegrator can generate multiple camera rays per pixel, in which case the actual distance between samples is lower. The fragment <<Generate camera ray for current sample>> encountered in Chapter 1 called the ScaleDifferentials() method defined below to update differential rays for an estimated sample spacing of s.

<<RayDifferential Public Methods>>+= 
void ScaleDifferentials(Float s) { rxOrigin = o + (rxOrigin - o) * s; ryOrigin = o + (ryOrigin - o) * s; rxDirection = d + (rxDirection - d) * s; ryDirection = d + (ryDirection - d) * s; }