10.1 Texture Sampling and Antialiasing

The sampling task from Chapter 8 was a frustrating one since the aliasing problem was known to be unsolvable from the start. The infinite frequency content of geometric edges and hard shadows guarantees aliasing in the final images, no matter how high the image sampling rate. (Our only consolation is that the visual impact of this remaining aliasing can be reduced to unobjectionable levels with a sufficient number of well-placed samples.)

Fortunately, things are not this difficult from the start for textures: either there is often a convenient analytic form of the texture function available, which makes it possible to remove excessively high frequencies before sampling it, or it is possible to be careful when evaluating the function so as not to introduce high frequencies in the first place. When this problem is carefully addressed in texture implementations, as is done through the rest of this chapter, there is usually no need for more than one sample per pixel in order to render an image without texture aliasing. (Of course, sufficiently reducing Monte Carlo noise from lighting calculations may be another matter.)

Two problems must be addressed in order to remove aliasing from texture functions:

  1. The sampling rate in texture space must be computed. The screen-space sampling rate is known from the image resolution and pixel sampling rate, but here we need to determine the resulting sampling rate on a surface in the scene in order to find the rate at which the texture function is being sampled.
  2. Given the texture sampling rate, sampling theory must be applied to guide the computation of a texture value that does not have higher frequency variation than can be represented by the sampling rate (e.g., by removing excess frequencies beyond the Nyquist limit from the texture function).

These two issues will be addressed in turn throughout the rest of this section.

10.1.1 Finding the Texture Sampling Rate

Consider an arbitrary texture function that is a function of position, upper T left-parenthesis normal p Subscript Baseline right-parenthesis , defined on a surface in the scene. If we ignore the complications introduced by visibility—the possibility that another object may occlude the surface at nearby image samples or that the surface may have a limited extent on the image plane—this texture function can also be expressed as a function over points left-parenthesis x comma y right-parenthesis on the image plane, upper T left-parenthesis f left-parenthesis x comma y right-parenthesis right-parenthesis , where f left-parenthesis x comma y right-parenthesis is the function that maps image points to points on the surface. Thus, upper T left-parenthesis f left-parenthesis x comma y right-parenthesis right-parenthesis gives the value of the texture function as seen at image position left-parenthesis x comma y right-parenthesis .

As a simple example of this idea, consider a 2D texture function upper T left-parenthesis s comma t right-parenthesis applied to a quadrilateral that is perpendicular to the z axis and has corners at the world-space points left-parenthesis 0 comma 0 comma 0 right-parenthesis , left-parenthesis 1 comma 0 comma 0 right-parenthesis , left-parenthesis 1 comma 1 comma 0 right-parenthesis , and left-parenthesis 0 comma 1 comma 0 right-parenthesis . If an orthographic camera is placed looking down the z axis such that the quadrilateral precisely fills the image plane and if points normal p Subscript on the quadrilateral are mapped to 2D left-parenthesis s comma t right-parenthesis texture coordinates by

s equals normal p Subscript Baseline Subscript x Baseline t equals normal p Subscript Baseline Subscript y Baseline comma

then the relationship between left-parenthesis s comma t right-parenthesis and screen left-parenthesis x comma y right-parenthesis pixels is straightforward:

s equals StartFraction x Over x Subscript normal r Baseline EndFraction t equals StartFraction y Over y Subscript normal r Baseline EndFraction comma

where the overall image resolution is left-parenthesis x Subscript normal r Baseline comma y Subscript normal r Baseline right-parenthesis (Figure 10.2). Thus, given a sample spacing of one pixel in the image plane, the sample spacing in left-parenthesis s comma t right-parenthesis texture parameter space is left-parenthesis 1 slash x Subscript normal r Baseline comma 1 slash y Subscript normal r Baseline right-parenthesis , and the texture function must remove any detail at a higher frequency than can be represented at that sampling rate.

Figure 10.2: If a quadrilateral is viewed with an orthographic perspective such that the quadrilateral precisely fills the image plane, it is easy to compute the relationship between the sampling rate in left-parenthesis x comma y right-parenthesis pixel coordinates and the texture sampling rate.

This relationship between pixel coordinates and texture coordinates, and thus the relationship between their sampling rates, is the key bit of information that determines the maximum frequency content allowable in the texture function. As a slightly more complex example, given a triangle with left-parenthesis u comma v right-parenthesis texture coordinates at its vertices and viewed with a perspective projection, it is possible to analytically find the differences in  u and  v across the sample points on the image plane. This approach was the basis of texture antialiasing in graphics processors before they became programmable.

For more complex scene geometry, camera projections, and mappings to texture coordinates, it is much more difficult to precisely determine the relationship between image positions and texture parameter values. Fortunately, for texture antialiasing, we do not need to be able to evaluate f left-parenthesis x comma y right-parenthesis for arbitrary left-parenthesis x comma y right-parenthesis but just need to find the relationship between changes in pixel sample position and the resulting change in texture sample position at a particular point on the image. This relationship is given by the partial derivatives of this function, partial-differential f slash partial-differential x and partial-differential f slash partial-differential y . For example, these can be used to find a first-order approximation to the value of f ,

f left-parenthesis x prime comma y Superscript prime Baseline right-parenthesis almost-equals f left-parenthesis x comma y right-parenthesis plus left-parenthesis x prime minus x right-parenthesis StartFraction partial-differential f Over partial-differential x EndFraction plus left-parenthesis y prime minus y right-parenthesis StartFraction partial-differential f Over partial-differential y EndFraction period

If these partial derivatives are changing slowly with respect to the distances x prime minus x and y prime minus y , this is a reasonable approximation. More importantly, the values of these partial derivatives give an approximation to the change in texture sample position for a shift of one pixel in the x and y directions, respectively, and thus directly yield the texture sampling rate. For example, in the previous quadrilateral example, partial-differential s slash partial-differential x equals 1 slash x Subscript normal r , partial-differential s slash partial-differential y equals 0 , partial-differential t slash partial-differential x equals 0 , and partial-differential t slash partial-differential y equals 1 slash y Subscript normal r .

The key to finding the values of these derivatives in the general case lies in values from the RayDifferential structure, which was defined in Section 3.6.1. This structure is initialized for each camera ray by the Camera::GenerateRayDifferential() method; it contains not only the ray being traced through the scene but also two additional rays, one offset horizontally one pixel sample from the camera ray and the other offset vertically by one pixel sample. All the geometric ray intersection routines use only the main camera ray for their computations; the auxiliary rays are ignored (this is easy to do because RayDifferential is a subclass of Ray).

We can use the offset rays to estimate the partial derivatives of the mapping p left-parenthesis x comma y right-parenthesis from image position to world-space position and the partial derivatives of the mappings u left-parenthesis x comma y right-parenthesis and v left-parenthesis x comma y right-parenthesis from left-parenthesis x comma y right-parenthesis to left-parenthesis u comma v right-parenthesis parametric coordinates, giving the partial derivatives of rendering-space positions partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y and the derivatives of left-parenthesis u comma v right-parenthesis parametric coordinates partial-differential u slash partial-differential x , partial-differential v slash partial-differential x , partial-differential u slash partial-differential y , and partial-differential v slash partial-differential y . In Section 10.2, we will see how these can be used to compute the screen-space derivatives of arbitrary quantities based on normal p Subscript or left-parenthesis u comma v right-parenthesis and consequently the sampling rates of these quantities. The values of these derivatives at the intersection point are stored in the SurfaceInteraction structure.

<<SurfaceInteraction Public Members>>+= 
Vector3f dpdx, dpdy; Float dudx = 0, dvdx = 0, dudy = 0, dvdy = 0;

The SurfaceInteraction::ComputeDifferentials() method computes these values. It is called by SurfaceInteraction::GetBSDF() before the Material’s GetBxDF() method is called so that these values will be available for any texture evaluation routines that are called by the material.

Ray differentials are not available for all rays traced by the system—for example, rays starting from light sources traced for photon mapping or bidirectional path tracing. Further, although we will see how to compute ray differentials after rays undergo specular reflection and transmission in Section 10.1.3, how to compute ray differentials after diffuse reflection is less clear. In cases like those as well as the corner case where one of the differentials’ directions is perpendicular to the surface normal, which leads to undefined numerical values in the following, an alternative approach based on approximating the ray differentials of a ray from the camera to the intersection point is used.

<<SurfaceInteraction Method Definitions>>= 
void SurfaceInteraction::ComputeDifferentials(const RayDifferential &ray, Camera camera, int samplesPerPixel) { if (ray.hasDifferentials && Dot(n, ray.rxDirection) != 0 && Dot(n, ray.ryDirection) != 0) { <<Estimate screen-space change in normal p Subscript using ray differentials>> 
<<Compute auxiliary intersection points with plane, px and py>> 
Float d = -Dot(n, Vector3f(p())); Float tx = (-Dot(n, Vector3f(ray.rxOrigin)) - d) / Dot(n, ray.rxDirection); Point3f px = ray.rxOrigin + tx * ray.rxDirection; Float ty = (-Dot(n, Vector3f(ray.ryOrigin)) - d) / Dot(n, ray.ryDirection); Point3f py = ray.ryOrigin + ty * ray.ryDirection;
dpdx = px - p(); dpdy = py - p();
} else { <<Approximate screen-space change in normal p Subscript based on camera projection>> 
camera.Approximate_dp_dxy(p(), n, time, samplesPerPixel, &dpdx, &dpdy);
} <<Estimate screen-space change in left-parenthesis u comma v right-parenthesis >> 
<<Compute bold upper A Superscript upper T Baseline bold upper A and its determinant>> 
Float ata00 = Dot(dpdu, dpdu), ata01 = Dot(dpdu, dpdv); Float ata11 = Dot(dpdv, dpdv); Float invDet = 1 / DifferenceOfProducts(ata00, ata11, ata01, ata01); invDet = IsFinite(invDet) ? invDet : 0.f;
<<Compute bold upper A Superscript upper T Baseline bold b for x and y >> 
Float atb0x = Dot(dpdu, dpdx), atb1x = Dot(dpdv, dpdx); Float atb0y = Dot(dpdu, dpdy), atb1y = Dot(dpdv, dpdy);
<<Compute u and v derivatives with respect to x and y >> 
dudx = DifferenceOfProducts(ata11, atb0x, ata01, atb1x) * invDet; dvdx = DifferenceOfProducts(ata00, atb1x, ata01, atb0x) * invDet; dudy = DifferenceOfProducts(ata11, atb0y, ata01, atb1y) * invDet; dvdy = DifferenceOfProducts(ata00, atb1y, ata01, atb0y) * invDet;
<<Clamp derivatives of u and v to reasonable values>> 
dudx = IsFinite(dudx) ? Clamp(dudx, -1e8f, 1e8f) : 0.f; dvdx = IsFinite(dvdx) ? Clamp(dvdx, -1e8f, 1e8f) : 0.f; dudy = IsFinite(dudy) ? Clamp(dudy, -1e8f, 1e8f) : 0.f; dvdy = IsFinite(dvdy) ? Clamp(dvdy, -1e8f, 1e8f) : 0.f;
}

The key to estimating the derivatives is the assumption that the surface is locally flat with respect to the sampling rate at the point being shaded. This is a reasonable approximation in practice, and it is hard to do much better. Because ray tracing is a point-sampling technique, we have no additional information about the scene in between the rays we have traced. For highly curved surfaces or at silhouette edges, this approximation can break down, though this is rarely a source of noticeable error.

For this approximation, we need the plane through the point normal p Subscript intersected by the main ray that is tangent to the surface. This plane is given by the implicit equation

a x plus b y plus c z plus d equals 0 comma

where a equals bold n Subscript Baseline Subscript x , b equals bold n Subscript Baseline Subscript y , c equals bold n Subscript Baseline Subscript z , and d equals minus left-parenthesis bold n Subscript Baseline dot normal p right-parenthesis . We can then compute the intersection points normal p Subscript x and normal p Subscript y between the auxiliary rays r Subscript x and r Subscript y and this plane (Figure 10.3). These new points give an approximation to the partial derivatives of position on the surface partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y , based on forward differences:

StartFraction partial-differential normal p Over partial-differential x EndFraction almost-equals normal p Subscript x Baseline minus normal p Subscript Baseline comma StartFraction partial-differential normal p Over partial-differential y EndFraction almost-equals normal p Subscript y Baseline minus normal p Subscript Baseline period

Figure 10.3: By approximating the local surface geometry at the intersection point with the tangent plane through normal p , approximations to the points at which the auxiliary rays r Subscript x and r Subscript y would intersect the surface can be found by finding their intersection points with the tangent plane normal p Subscript x and normal p Subscript y .

Because the differential rays are offset one pixel sample in each direction, there is no need to divide these differences by a normal upper Delta value, since normal upper Delta equals 1 .

<<Estimate screen-space change in normal p Subscript using ray differentials>>= 
<<Compute auxiliary intersection points with plane, px and py>> 
Float d = -Dot(n, Vector3f(p())); Float tx = (-Dot(n, Vector3f(ray.rxOrigin)) - d) / Dot(n, ray.rxDirection); Point3f px = ray.rxOrigin + tx * ray.rxDirection; Float ty = (-Dot(n, Vector3f(ray.ryOrigin)) - d) / Dot(n, ray.ryDirection); Point3f py = ray.ryOrigin + ty * ray.ryDirection;
dpdx = px - p(); dpdy = py - p();

The ray–plane intersection algorithm described in Section 6.1.2 gives the t value where a ray described by origin normal o and direction bold d intersects a plane described by a x plus b y plus c z plus d equals 0 :

t equals StartFraction minus left-parenthesis left-parenthesis a comma b comma c right-parenthesis dot normal o right-parenthesis minus d Over left-parenthesis a comma b comma c right-parenthesis dot bold d EndFraction period

To compute this value for the two auxiliary rays, the plane’s d coefficient is computed first. It is not necessary to compute the a , b , and c coefficients, since they are available in n. We can then apply the formula directly.

<<Compute auxiliary intersection points with plane, px and py>>= 
Float d = -Dot(n, Vector3f(p())); Float tx = (-Dot(n, Vector3f(ray.rxOrigin)) - d) / Dot(n, ray.rxDirection); Point3f px = ray.rxOrigin + tx * ray.rxDirection; Float ty = (-Dot(n, Vector3f(ray.ryOrigin)) - d) / Dot(n, ray.ryDirection); Point3f py = ray.ryOrigin + ty * ray.ryDirection;

For cases where ray differentials are not available, we will add a method to the Camera interface that returns approximate values for partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y at a point on a surface in the scene. These should be a reasonable approximation to the differentials of a ray from the camera that found an intersection at the given point. Cameras’ implementations of this method must return reasonable results even for points outside of their viewing volumes for which they cannot actually generate rays.

<<Camera Interface>>+= 
void Approximate_dp_dxy(Point3f p, Normal3f n, Float time, int samplesPerPixel, Vector3f *dpdx, Vector3f *dpdy) const;

CameraBase provides an implementation of an approach to approximating these differentials that is based on the minimum of the camera ray differentials across the entire image. Because all of pbrt’s current camera implementations inherit from CameraBase, the following method takes care of all of them.

<<CameraBase Public Methods>>+= 
void Approximate_dp_dxy(Point3f p, Normal3f n, Float time, int samplesPerPixel, Vector3f *dpdx, Vector3f *dpdy) const { <<Compute tangent plane equation for ray differential intersections>> 
Point3f pCamera = CameraFromRender(p, time); Transform DownZFromCamera = RotateFromTo(Normalize(Vector3f(pCamera)), Vector3f(0, 0, 1)); Point3f pDownZ = DownZFromCamera(pCamera); Normal3f nDownZ = DownZFromCamera(CameraFromRender(n, time)); Float d = nDownZ.z * pDownZ.z;
<<Find intersection points for approximated camera differential rays>> 
Ray xRay(Point3f(0,0,0) + minPosDifferentialX, Vector3f(0,0,1) + minDirDifferentialX); Float tx = -(Dot(nDownZ, Vector3f(xRay.o)) - d) / Dot(nDownZ, xRay.d); Ray yRay(Point3f(0,0,0) + minPosDifferentialY, Vector3f(0,0,1) + minDirDifferentialY); Float ty = -(Dot(nDownZ, Vector3f(yRay.o)) - d) / Dot(nDownZ, yRay.d); Point3f px = xRay(tx), py = yRay(ty);
<<Estimate partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y in tangent plane at intersection point>> 
Float sppScale = GetOptions().disablePixelJitter ? 1 : std::max<Float>(.125, 1 / std::sqrt((Float)samplesPerPixel)); *dpdx = sppScale * RenderFromCamera(DownZFromCamera.ApplyInverse(px - pDownZ), time); *dpdy = sppScale * RenderFromCamera(DownZFromCamera.ApplyInverse(py - pDownZ), time);
}

This method starts by orienting the camera so that the camera-space z axis is aligned with the vector from the camera position to the intersection point. It then uses lower bounds on the spread of rays over the image that are provided by the camera to find approximate differential rays. It then intersects these rays with the tangent plane at the intersection point. (See Figure 10.4.)

Figure 10.4: CameraBase::Approximate_dp_dxy() effectively reorients the camera to point at the provided intersection point. In camera space, the ray to the intersection then has origin left-parenthesis 0 comma 0 comma 0 right-parenthesis and direction left-parenthesis 0 comma 0 comma 1 right-parenthesis . The extent of ray differentials on the tangent plane defined by the surface normal at the intersection point can then be found.

Figure 10.5: Visualization of the Ratio of Filter Areas Estimated by Regular Ray Differentials to Areas Estimated by CameraBase::Approximate_dp_dxy(). We represent the filter area as the product max left-parenthesis StartAbsoluteValue partial-differential u slash partial-differential x EndAbsoluteValue comma StartAbsoluteValue partial-differential u slash partial-differential y EndAbsoluteValue right-parenthesis max left-parenthesis StartAbsoluteValue partial-differential v slash partial-differential x EndAbsoluteValue comma StartAbsoluteValue partial-differential v slash partial-differential y EndAbsoluteValue right-parenthesis and visualize the base 2 logarithm of the ratio of areas computed by the two techniques. Log 2 ratios greater than 0 indicate that the camera-based approximation estimated a larger filter area.

There are a number of sources of error in this approximation. Beyond the fact that it does not account for how light was scattered at intermediate surfaces for multiple-bounce ray paths, there is also the fact that it is based on the minimum of the camera’s differentials for all rays. In general, it tries to underestimate those derivatives rather than overestimate them, as we prefer aliasing over blurring here. The former error can at least be addressed with additional pixel samples. In order to give a sense of the impact of some of these approximations, Figure 10.5 has visualization that compares the local area estimated by those derivatives at intersections to the area computed using the actual ray differentials generated by the camera.

For the first step of the algorithm, we have an intersection point in rendering space p that we would like to transform into a coordinate system where it is along the z axis with the camera at the origin. Transforming to camera space gets us started and an additional rotation that transforms the vector from the origin to the intersection point to be aligned with z finishes the job. The d coefficient of the plane equation can then be found by taking the dot product of the transformed point and surface normal. Because the x and y components of the transformed point are equal to 0, the dot product can be optimized to be a single multiply.

<<Compute tangent plane equation for ray differential intersections>>= 
Point3f pCamera = CameraFromRender(p, time); Transform DownZFromCamera = RotateFromTo(Normalize(Vector3f(pCamera)), Vector3f(0, 0, 1)); Point3f pDownZ = DownZFromCamera(pCamera); Normal3f nDownZ = DownZFromCamera(CameraFromRender(n, time)); Float d = nDownZ.z * pDownZ.z;

Camera implementations that inherit from CameraBase and use this method must initialize the following member variables with values that are lower bounds on each of the respective position and direction differentials over all the pixels in the image.

<<CameraBase Protected Members>>+= 
Vector3f minPosDifferentialX, minPosDifferentialY; Vector3f minDirDifferentialX, minDirDifferentialY;

The main ray in this coordinate system has origin left-parenthesis 0 comma 0 comma 0 right-parenthesis and direction left-parenthesis 0 comma 0 comma 1 right-parenthesis . Adding the position and direction differential vectors to those gives the origin and direction of each differential ray. Given those, the same calculation as earlier gives us the t values for the ray–plane intersections for the differential rays and thence the intersection points.

<<Find intersection points for approximated camera differential rays>>= 
Ray xRay(Point3f(0,0,0) + minPosDifferentialX, Vector3f(0,0,1) + minDirDifferentialX); Float tx = -(Dot(nDownZ, Vector3f(xRay.o)) - d) / Dot(nDownZ, xRay.d); Ray yRay(Point3f(0,0,0) + minPosDifferentialY, Vector3f(0,0,1) + minDirDifferentialY); Float ty = -(Dot(nDownZ, Vector3f(yRay.o)) - d) / Dot(nDownZ, yRay.d); Point3f px = xRay(tx), py = yRay(ty);

For an orthographic camera, these differentials can be computed directly. There is no change in the direction vector, and the position differentials are the same at every pixel. Their values are already computed in the OrthographicCamera constructor, so can be used directly to initialize the base class’s member variables.

<<Compute minimum differentials for orthographic camera>>= 
minDirDifferentialX = minDirDifferentialY = Vector3f(0, 0, 0); minPosDifferentialX = dxCamera; minPosDifferentialY = dyCamera;

All the other cameras call FindMinimumDifferentials(), which estimates these values by sampling at many points across the diagonal of the image and storing the minimum of all the differentials encountered. That function is not very interesting, so it is not included here.

<<Compute minimum differentials for PerspectiveCamera>>= 

Given the intersection points px and py, partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y can now be estimated by taking their differences with the main intersection point. To get final estimates of the partial derivatives, these vectors must be transformed back out into rendering space and scaled to account for the actual pixel sampling rate. As with the initial ray differentials that were generated in the <<Scale camera ray differentials based on image sampling rate>> fragment, these are scaled to account for the pixel sampling rate.

<<Estimate partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y in tangent plane at intersection point>>= 
Float sppScale = GetOptions().disablePixelJitter ? 1 : std::max<Float>(.125, 1 / std::sqrt((Float)samplesPerPixel)); *dpdx = sppScale * RenderFromCamera(DownZFromCamera.ApplyInverse(px - pDownZ), time); *dpdy = sppScale * RenderFromCamera(DownZFromCamera.ApplyInverse(py - pDownZ), time);

A call to this method takes care of computing the partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y differentials in the ComputeDifferentials() method.

<<Approximate screen-space change in normal p Subscript based on camera projection>>= 
camera.Approximate_dp_dxy(p(), n, time, samplesPerPixel, &dpdx, &dpdy);

We now have both the partial derivatives partial-differential normal p slash partial-differential u and partial-differential normal p slash partial-differential v as well as, one way or another, partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y . From them, we would now like to compute partial-differential u slash partial-differential x , partial-differential u slash partial-differential y , partial-differential v slash partial-differential x , and partial-differential v slash partial-differential y . Using the chain rule, we can find that

( partial-differential normal p slash partial-differential y has a similar expression with partial-differential u slash partial-differential x replaced by partial-differential u slash partial-differential y and partial-differential v slash partial-differential x replaced by partial-differential v slash partial-differential y .)

Equation (10.1) can be written as a matrix equation where the two following matrices that include partial-differential normal p Subscript have three rows, one for each of normal p Subscript ’s x , y , and z components:

This is an overdetermined linear system since there are three equations but only two unknowns, partial-differential u slash partial-differential x and partial-differential v slash partial-differential x . An effective solution approach in this case is to apply linear least squares, which says that for a linear system of the form bold upper A bold x equals bold b with bold upper A and bold b known, the least-squares solution for bold x is given by

bold x equals left-parenthesis bold upper A Superscript upper T Baseline bold upper A right-parenthesis Superscript negative 1 Baseline bold upper A Superscript upper T Baseline bold b period

In this case, bold upper A equals Start 1 By 2 Matrix 1st Row 1st Column partial-differential normal p slash partial-differential u 2nd Column partial-differential normal p slash partial-differential v EndMatrix , bold b equals Start 1 By 1 Matrix 1st Row partial-differential normal p slash partial-differential x EndMatrix , and bold x equals Start 1 By 2 Matrix 1st Row 1st Column partial-differential u slash partial-differential x 2nd Column partial-differential v slash partial-differential x EndMatrix Superscript upper T .

<<Estimate screen-space change in left-parenthesis u comma v right-parenthesis >>= 
<<Compute bold upper A Superscript upper T Baseline bold upper A and its determinant>> 
Float ata00 = Dot(dpdu, dpdu), ata01 = Dot(dpdu, dpdv); Float ata11 = Dot(dpdv, dpdv); Float invDet = 1 / DifferenceOfProducts(ata00, ata11, ata01, ata01); invDet = IsFinite(invDet) ? invDet : 0.f;
<<Compute bold upper A Superscript upper T Baseline bold b for x and y >> 
Float atb0x = Dot(dpdu, dpdx), atb1x = Dot(dpdv, dpdx); Float atb0y = Dot(dpdu, dpdy), atb1y = Dot(dpdv, dpdy);
<<Compute u and v derivatives with respect to x and y >> 
dudx = DifferenceOfProducts(ata11, atb0x, ata01, atb1x) * invDet; dvdx = DifferenceOfProducts(ata00, atb1x, ata01, atb0x) * invDet; dudy = DifferenceOfProducts(ata11, atb0y, ata01, atb1y) * invDet; dvdy = DifferenceOfProducts(ata00, atb1y, ata01, atb0y) * invDet;
<<Clamp derivatives of u and v to reasonable values>> 
dudx = IsFinite(dudx) ? Clamp(dudx, -1e8f, 1e8f) : 0.f; dvdx = IsFinite(dvdx) ? Clamp(dvdx, -1e8f, 1e8f) : 0.f; dudy = IsFinite(dudy) ? Clamp(dudy, -1e8f, 1e8f) : 0.f; dvdy = IsFinite(dvdy) ? Clamp(dvdy, -1e8f, 1e8f) : 0.f;

bold upper A Superscript upper T Baseline bold upper A is a 2 times 2 matrix with elements given by dot products of partial derivatives of position:

bold upper A Superscript upper T Baseline bold upper A equals Start 2 By 2 Matrix 1st Row 1st Column partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential u 2nd Column partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential v 2nd Row 1st Column partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential v 2nd Column partial-differential normal p slash partial-differential v dot partial-differential normal p slash partial-differential v EndMatrix period

Its inverse is

left-parenthesis bold upper A Superscript upper T Baseline bold upper A right-parenthesis Superscript negative 1 Baseline equals StartFraction 1 Over StartAbsoluteValue bold upper A Superscript upper T Baseline bold upper A EndAbsoluteValue EndFraction Start 2 By 2 Matrix 1st Row 1st Column partial-differential normal p slash partial-differential v dot partial-differential normal p slash partial-differential v 2nd Column minus partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential v 2nd Row 1st Column minus partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential v 2nd Column partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential u EndMatrix period

Note that in both matrices the two off-diagonal entries are equal. Thus, the fragment that computes the entries of bold upper A Superscript upper T Baseline bold upper A only needs to compute three values. The inverse of the matrix determinant is computed here as well. If its value is infinite, the linear system cannot be solved; setting invDet to 0 causes the subsequently computed derivatives to be 0, which leads to point-sampled textures, the best remaining option in that case.

<<Compute bold upper A Superscript upper T Baseline bold upper A and its determinant>>= 
Float ata00 = Dot(dpdu, dpdu), ata01 = Dot(dpdu, dpdv); Float ata11 = Dot(dpdv, dpdv); Float invDet = 1 / DifferenceOfProducts(ata00, ata11, ata01, ata01); invDet = IsFinite(invDet) ? invDet : 0.f;

The bold upper A Superscript upper T Baseline bold b portion of the solution is easily computed. For the derivatives with respect to screen-space x , we have the two-element matrix

bold upper A Superscript upper T Baseline bold b equals Start 1 By 2 Matrix 1st Row 1st Column partial-differential normal p slash partial-differential u dot partial-differential normal p slash partial-differential x 2nd Column partial-differential normal p slash partial-differential v dot partial-differential normal p slash partial-differential x EndMatrix period

The solution for screen-space y is analogous.

<<Compute bold upper A Superscript upper T Baseline bold b for x and y >>= 
Float atb0x = Dot(dpdu, dpdx), atb1x = Dot(dpdv, dpdx); Float atb0y = Dot(dpdu, dpdy), atb1y = Dot(dpdv, dpdy);

The solution to Equation (10.2) for each partial derivative can be found by taking the product of Equations (10.3) and (10.4). We will gloss past the algebra; its result can be directly expressed in terms of the values computed so far.

<<Compute u and v derivatives with respect to x and y >>= 
dudx = DifferenceOfProducts(ata11, atb0x, ata01, atb1x) * invDet; dvdx = DifferenceOfProducts(ata00, atb1x, ata01, atb0x) * invDet; dudy = DifferenceOfProducts(ata11, atb0y, ata01, atb1y) * invDet; dvdy = DifferenceOfProducts(ata00, atb1y, ata01, atb0y) * invDet;

In certain tricky cases (e.g., with highly distorted left-parenthesis u comma v right-parenthesis parameterizations or at object silhouette edges), the estimated partial derivatives may be infinite or have very large magnitudes. It is worth clamping them to reasonable values in that case to prevent overflow and not-a-number values in subsequent computations that are based on them.

<<Clamp derivatives of u and v to reasonable values>>= 
dudx = IsFinite(dudx) ? Clamp(dudx, -1e8f, 1e8f) : 0.f; dvdx = IsFinite(dvdx) ? Clamp(dvdx, -1e8f, 1e8f) : 0.f; dudy = IsFinite(dudy) ? Clamp(dudy, -1e8f, 1e8f) : 0.f; dvdy = IsFinite(dvdy) ? Clamp(dvdy, -1e8f, 1e8f) : 0.f;

10.1.2 Ray Differentials at Medium Transitions

Now is a good time to take care of another detail related to ray differentials: recall from Section 9.1.5 that materials may return an unset BSDF to indicate an interface between two scattering media that does not itself scatter light. In this case, it is necessary to spawn a new ray in the same direction, but past the intersection on the surface. In this case we would like the effect of the ray differentials to be the same as if no scattering had occurred. This can be achieved by setting the differential origins to the points given by evaluating the ray equation at the intersection t (see Figure 10.6).

Figure 10.6: When a ray intersects a surface that delineates the boundary between two media, a new ray is spawned on the other side of the boundary. If the origins of this ray’s differentials are set by evaluating the ray equation for the original differentials at the intersection t , then the new ray will represent the same footprint as the original one when it subsequently intersects a surface.

<<SurfaceInteraction Method Definitions>>+=  
void SurfaceInteraction::SkipIntersection(RayDifferential *ray, Float t) const { *((Ray *)ray) = SpawnRay(ray->d); if (ray->hasDifferentials) { ray->rxOrigin = ray->rxOrigin + t * ray->rxDirection; ray->ryOrigin = ray->ryOrigin + t * ray->ryDirection; } }

10.1.3 Ray Differentials for Specular Reflection and Transmission

Given the effectiveness of ray differentials for finding filter regions for texture antialiasing for camera rays, it is useful to extend the method to make it possible to determine texture-space sampling rates for objects that are seen indirectly via specular reflection or refraction; objects seen in mirrors, for example, should not have texture aliasing, identical to the case for directly visible objects. Igehy (1999) developed an elegant solution to the problem of how to find the appropriate differential rays for specular reflection and refraction, which is the approach used in pbrt.

Figure 10.7 illustrates the difference that proper texture filtering for specular reflection and transmission can make: it shows a glass ball and a mirrored ball on a plane with a texture map containing high-frequency components. Ray differentials ensure that the images of the texture seen via reflection and refraction from the balls are free of aliasing artifacts. Here, ray differentials eliminate aliasing without excessively blurring the texture.

Figure 10.7: (a) Tracking ray differentials for reflected and refracted rays ensures that the image map texture seen in the balls is filtered to avoid aliasing. The left ball is glass, exhibiting reflection and refraction, and the right ball is a mirror, just showing reflection. Note that the texture is well filtered over both of the balls. (b) shows the aliasing artifacts that are present if ray differentials are not used.

Figure 10.8: The specular reflection formula gives the direction of the reflected ray at a point on a surface. An offset ray for a ray differential r prime (dashed line) will generally intersect the surface at a different point and be reflected in a different direction. The new direction is affected by the different surface normal at the point as well as by the offset ray’s different incident direction. The computation to find the reflected direction for the offset ray in pbrt estimates the change in reflected direction as a function of image-space position and approximates the ray differential’s direction with the main ray’s direction added to the estimated change in direction.

To compute the reflected or transmitted ray differentials at a surface intersection point, we need an approximation to the rays that would have been traced at the intersection points for the two offset rays in the ray differential that hit the surface (Figure 10.8). The new ray for the main ray is found by sampling the BSDF, so here we only need to compute the outgoing rays for the r Subscript x and r Subscript y differentials. This task is handled by another SurfaceInteraction::SpawnRay() variant that takes an incident ray differential as well as information about the BSDF and the type of scattering that occurred.

<<SurfaceInteraction Method Definitions>>+=  
RayDifferential SurfaceInteraction::SpawnRay( const RayDifferential &rayi, const BSDF &bsdf, Vector3f wi, int flags, Float eta) const { RayDifferential rd(SpawnRay(wi)); if (rayi.hasDifferentials) { <<Compute ray differentials for specular reflection or transmission>> 
<<Compute common factors for specular ray differentials>> 
Normal3f n = shading.n; Normal3f dndx = shading.dndu * dudx + shading.dndv * dvdx; Normal3f dndy = shading.dndu * dudy + shading.dndv * dvdy; Vector3f dwodx = -rayi.rxDirection - wo, dwody = -rayi.ryDirection - wo;
if (flags == BxDFFlags::SpecularReflection) { <<Initialize origins of specular differential rays>> 
rd.hasDifferentials = true; rd.rxOrigin = p() + dpdx; rd.ryOrigin = p() + dpdy;
<<Compute differential reflected directions>> 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); rd.rxDirection = wi - dwodx + 2 * Vector3f(Dot(wo, n) * dndx + dwoDotn_dx * n); rd.ryDirection = wi - dwody + 2 * Vector3f(Dot(wo, n) * dndy + dwoDotn_dy * n);
} else if (flags == BxDFFlags::SpecularTransmission) { <<Initialize origins of specular differential rays>> 
rd.hasDifferentials = true; rd.rxOrigin = p() + dpdx; rd.ryOrigin = p() + dpdy;
<<Compute differential transmitted directions>> 
<<Find oriented surface normal for transmission>> 
if (Dot(wo, n) < 0) { n = -n; dndx = -dndx; dndy = -dndy; }
<<Compute partial derivatives of mu >> 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); Float mu = Dot(wo, n) / eta - AbsDot(wi, n); Float dmudx = dwoDotn_dx * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n)); Float dmudy = dwoDotn_dy * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n));
rd.rxDirection = wi - eta * dwodx + Vector3f(mu * dndx + dmudx * n); rd.ryDirection = wi - eta * dwody + Vector3f(mu * dndy + dmudy * n);
}
} <<Squash potentially troublesome differentials>> 
if (LengthSquared(rd.rxDirection) > 1e16f || LengthSquared(rd.ryDirection) > 1e16f || LengthSquared(Vector3f(rd.rxOrigin)) > 1e16f || LengthSquared(Vector3f(rd.ryOrigin)) > 1e16f) rd.hasDifferentials = false;
return rd; }

It is not well defined what the ray differentials should be in the case of non-specular scattering. Therefore, this method handles the two types of specular scattering only; for all other types of rays, approximate differentials will be computed at their subsequent intersection points with Camera::Approximate_dp_dxy().

<<Compute ray differentials for specular reflection or transmission>>= 
<<Compute common factors for specular ray differentials>> 
Normal3f n = shading.n; Normal3f dndx = shading.dndu * dudx + shading.dndv * dvdx; Normal3f dndy = shading.dndu * dudy + shading.dndv * dvdy; Vector3f dwodx = -rayi.rxDirection - wo, dwody = -rayi.ryDirection - wo;
if (flags == BxDFFlags::SpecularReflection) { <<Initialize origins of specular differential rays>> 
rd.hasDifferentials = true; rd.rxOrigin = p() + dpdx; rd.ryOrigin = p() + dpdy;
<<Compute differential reflected directions>> 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); rd.rxDirection = wi - dwodx + 2 * Vector3f(Dot(wo, n) * dndx + dwoDotn_dx * n); rd.ryDirection = wi - dwody + 2 * Vector3f(Dot(wo, n) * dndy + dwoDotn_dy * n);
} else if (flags == BxDFFlags::SpecularTransmission) { <<Initialize origins of specular differential rays>> 
rd.hasDifferentials = true; rd.rxOrigin = p() + dpdx; rd.ryOrigin = p() + dpdy;
<<Compute differential transmitted directions>> 
<<Find oriented surface normal for transmission>> 
if (Dot(wo, n) < 0) { n = -n; dndx = -dndx; dndy = -dndy; }
<<Compute partial derivatives of mu >> 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); Float mu = Dot(wo, n) / eta - AbsDot(wi, n); Float dmudx = dwoDotn_dx * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n)); Float dmudy = dwoDotn_dy * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n));
rd.rxDirection = wi - eta * dwodx + Vector3f(mu * dndx + dmudx * n); rd.ryDirection = wi - eta * dwody + Vector3f(mu * dndy + dmudy * n);
}

A few variables will be used for both types of scattering, including the partial derivatives of the surface normal with respect to x and y on the image and partial-differential bold n Subscript slash partial-differential x and partial-differential bold n Subscript slash partial-differential y , which are computed using the chain rule.

<<Compute common factors for specular ray differentials>>= 
Normal3f n = shading.n; Normal3f dndx = shading.dndu * dudx + shading.dndv * dvdx; Normal3f dndy = shading.dndu * dudy + shading.dndv * dvdy; Vector3f dwodx = -rayi.rxDirection - wo, dwody = -rayi.ryDirection - wo;

For both reflection and transmission, the origin of each differential ray can be found using the already-computed approximations of how much the surface position changes with respect to left-parenthesis x comma y right-parenthesis position on the image plane partial-differential normal p slash partial-differential x and partial-differential normal p slash partial-differential y .

<<Initialize origins of specular differential rays>>= 
rd.hasDifferentials = true; rd.rxOrigin = p() + dpdx; rd.ryOrigin = p() + dpdy;

Finding the directions of these rays is slightly trickier. If we know how much the reflected direction omega Subscript normal i changes with respect to a shift of a pixel sample in the x and y directions on the image plane, we can use this information to approximate the direction of the offset rays. For example, the direction for the ray offset in x is

omega Subscript Baseline almost-equals omega Subscript normal i Baseline plus StartFraction partial-differential omega Subscript normal i Baseline Over partial-differential x EndFraction period

Recall from Equation (9.1) that for a normal bold n Subscript and outgoing direction omega Subscript normal o the direction for perfect specular reflection is

omega Subscript normal i Baseline equals minus omega Subscript normal o Baseline plus 2 left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis bold n Subscript Baseline period

The partial derivatives of this expression are easily computed:

StartLayout 1st Row 1st Column StartFraction partial-differential omega Subscript normal i Baseline Over partial-differential x EndFraction 2nd Column equals StartFraction partial-differential Over partial-differential x EndFraction left-parenthesis minus omega Subscript normal o Baseline plus 2 left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis bold n Subscript Baseline right-parenthesis 2nd Row 1st Column Blank 2nd Column equals minus StartFraction partial-differential omega Subscript normal o Baseline Over partial-differential x EndFraction plus 2 left-parenthesis left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis StartFraction partial-differential bold n Subscript Baseline Over partial-differential x EndFraction plus StartFraction partial-differential left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over partial-differential x EndFraction bold n Subscript Baseline right-parenthesis period EndLayout

Using the properties of the dot product, it can further be shown that

StartFraction partial-differential left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over partial-differential x EndFraction equals StartFraction partial-differential omega Subscript normal o Baseline Over partial-differential x EndFraction dot bold n Subscript Baseline plus omega Subscript normal o Baseline dot StartFraction partial-differential bold n Subscript Baseline Over partial-differential x EndFraction period

The value of partial-differential omega Subscript normal o slash partial-differential x has already been computed from the difference between the direction of the ray differential’s main ray and the direction of the r Subscript x offset ray, and all the other necessary quantities are readily available from the SurfaceInteraction.

<<Compute differential reflected directions>>= 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); rd.rxDirection = wi - dwodx + 2 * Vector3f(Dot(wo, n) * dndx + dwoDotn_dx * n); rd.ryDirection = wi - dwody + 2 * Vector3f(Dot(wo, n) * dndy + dwoDotn_dy * n);

A similar process of differentiating the equation for the direction of a specularly transmitted ray, Equation (9.4), gives the equation to find the differential change in the transmitted direction. pbrt computes refracted rays as

omega Subscript normal i Baseline equals minus StartFraction 1 Over eta EndFraction omega Subscript normal o Baseline plus left-bracket StartFraction 1 Over eta EndFraction left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis minus cosine theta Subscript normal i Baseline right-bracket bold n Subscript Baseline comma

where bold n Subscript is flipped if necessary to lie in the same hemisphere as omega Subscript normal o , and where eta is the relative index of refraction from omega Subscript normal o ’s medium to omega Subscript normal i ’s medium.

If we denote the term in brackets by mu , then we have omega Subscript normal i Baseline equals minus left-parenthesis 1 slash eta right-parenthesis omega Subscript normal o Baseline plus mu bold n Subscript . Taking the partial derivative in x , we have

StartFraction partial-differential omega Subscript normal i Baseline Over partial-differential x EndFraction equals minus StartFraction 1 Over eta EndFraction StartFraction partial-differential omega Subscript o Baseline Over partial-differential x EndFraction plus mu StartFraction partial-differential bold n Subscript Baseline Over partial-differential x EndFraction plus StartFraction partial-differential mu Over partial-differential x EndFraction bold n Subscript Baseline period

Using some of the values found from computing specularly reflected ray differentials, we can find that we already know how to compute all of these values except for partial-differential mu slash partial-differential x .

<<Compute differential transmitted directions>>= 
<<Find oriented surface normal for transmission>> 
if (Dot(wo, n) < 0) { n = -n; dndx = -dndx; dndy = -dndy; }
<<Compute partial derivatives of mu >> 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); Float mu = Dot(wo, n) / eta - AbsDot(wi, n); Float dmudx = dwoDotn_dx * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n)); Float dmudy = dwoDotn_dy * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n));
rd.rxDirection = wi - eta * dwodx + Vector3f(mu * dndx + dmudx * n); rd.ryDirection = wi - eta * dwody + Vector3f(mu * dndy + dmudy * n);

Before we get to the computation of mu ’s partial derivatives, we will start by reorienting the surface normal if necessary so that it lies on the same side of the surface as omega Subscript normal o . This matches pbrt’s computation of refracted ray directions.

<<Find oriented surface normal for transmission>>= 
if (Dot(wo, n) < 0) { n = -n; dndx = -dndx; dndy = -dndy; }

Returning to mu and considering partial-differential mu slash partial-differential x , we have

StartFraction partial-differential mu Over partial-differential x EndFraction equals StartFraction partial-differential Over partial-differential x EndFraction StartFraction 1 Over eta EndFraction left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis minus StartFraction partial-differential Over partial-differential x EndFraction cosine theta Subscript normal i Baseline equals StartFraction 1 Over eta EndFraction StartFraction partial-differential left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over partial-differential x EndFraction minus StartFraction partial-differential cosine theta Subscript normal i Baseline Over partial-differential x EndFraction period

Its first term can be evaluated with already known values. For the second term, we will start with Snell’s law, which gives

cosine theta Subscript normal i Baseline equals StartRoot 1 minus StartFraction 1 minus left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis squared Over eta squared EndFraction EndRoot period

If we square both sides of the equation and take the partial derivative partial-differential slash partial-differential x , we find

StartLayout 1st Row 1st Column 2 cosine theta Subscript normal i Baseline StartFraction partial-differential cosine theta Subscript normal i Baseline Over partial-differential x EndFraction 2nd Column equals StartFraction partial-differential Over partial-differential x EndFraction left-parenthesis 1 minus StartFraction 1 minus left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis squared Over eta squared EndFraction right-parenthesis 2nd Row 1st Column Blank 2nd Column equals StartFraction partial-differential Over partial-differential x EndFraction left-parenthesis StartFraction left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis squared Over eta squared EndFraction right-parenthesis equals StartFraction 2 left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over eta squared EndFraction StartFraction partial-differential left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over partial-differential x EndFraction period EndLayout

We now can solve for partial-differential cosine theta Subscript normal i slash partial-differential x :

StartFraction partial-differential cosine theta Subscript normal i Baseline Over partial-differential x EndFraction equals StartFraction 1 Over 2 cosine theta Subscript normal i Baseline EndFraction StartFraction 2 left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over eta squared EndFraction StartFraction partial-differential left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over partial-differential x EndFraction period

Putting it all together and simplifying, we have

StartFraction partial-differential mu Over partial-differential x EndFraction equals StartFraction partial-differential left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over partial-differential x EndFraction left-parenthesis StartFraction 1 Over eta EndFraction plus StartFraction 1 Over eta squared EndFraction StartFraction left-parenthesis omega Subscript normal o Baseline dot bold n Subscript Baseline right-parenthesis Over left-parenthesis omega Subscript normal i Baseline dot bold n Subscript Baseline right-parenthesis EndFraction right-parenthesis period

The partial derivative in y is analogous and the implementation follows.

<<Compute partial derivatives of mu >>= 
Float dwoDotn_dx = Dot(dwodx, n) + Dot(wo, dndx); Float dwoDotn_dy = Dot(dwody, n) + Dot(wo, dndy); Float mu = Dot(wo, n) / eta - AbsDot(wi, n); Float dmudx = dwoDotn_dx * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n)); Float dmudy = dwoDotn_dy * (1/eta + 1/Sqr(eta) * Dot(wo, n) / Dot(wi, n));

If a ray undergoes many specular bounces, ray differentials sometimes drift off to have very large magnitudes, which can leave a trail of infinite and not-a-number values in their wake when they are used for texture filtering calculations. Therefore, the final fragment in this SpawnRay() method computes the squared length of all the differentials. If any is greater than 10 Superscript 16 , the ray differentials are discarded and the RayDifferential hasDifferentials value is set to false. The fragment that handles this, <<Squash potentially troublesome differentials>>, is simple and thus not included here.

10.1.4 Filtering Texture Functions

To eliminate texture aliasing, it is necessary to remove frequencies in texture functions that are past the Nyquist limit for the texture sampling rate. The goal is to compute, with as few approximations as possible, the result of the ideal texture resampling process, which says that in order to evaluate a texture function upper T at a point left-parenthesis x comma y right-parenthesis on the image without aliasing, we must first band-limit it, removing frequencies beyond the Nyquist limit by convolving it with the sinc filter:

upper T Subscript normal b Baseline left-parenthesis x comma y right-parenthesis equals integral Subscript negative normal infinity Superscript normal infinity Baseline integral Subscript negative normal infinity Superscript normal infinity Baseline normal s normal i normal n normal c left-parenthesis x Superscript prime Baseline right-parenthesis normal s normal i normal n normal c left-parenthesis y Superscript prime Baseline right-parenthesis upper T prime left-parenthesis f left-parenthesis x minus x Superscript prime Baseline comma y minus y Superscript prime Baseline right-parenthesis right-parenthesis normal d x prime normal d y Superscript prime Baseline comma

where, as in Section 10.1.1, f left-parenthesis x comma y right-parenthesis maps pixel locations to points in the texture function’s domain. The band-limited function upper T Subscript normal b in turn should then be convolved with the pixel filter g left-parenthesis x comma y right-parenthesis centered at the left-parenthesis x comma y right-parenthesis point on the screen at which we want to evaluate the texture function:

upper T Subscript normal i normal d normal e normal a normal l Baseline left-parenthesis x comma y right-parenthesis equals integral Subscript minus normal y normal upper W normal i normal d normal t normal h slash 2 Superscript normal y normal upper W normal i normal d normal t normal h slash 2 Baseline integral Subscript minus normal x normal upper W normal i normal d normal t normal h slash 2 Superscript normal x normal upper W normal i normal d normal t normal h slash 2 Baseline g left-parenthesis x prime comma y Superscript prime Baseline right-parenthesis upper T Subscript normal b Baseline left-parenthesis x minus x Superscript prime Baseline comma y minus y Superscript prime Baseline right-parenthesis normal d x prime normal d y Superscript prime Baseline period

This gives the theoretically perfect value for the texture as projected onto the screen.

In practice, there are many simplifications that can be made to this process. For example, a box filter may be used for the band-limiting step, and the second step is usually ignored completely, effectively acting as if the pixel filter were a box filter, which makes it possible to do the antialiasing work completely in texture space. (The EWA filtering algorithm in Section 10.4.4 is a notable exception in that it assumes a Gaussian pixel filter.)

Assuming box filters then if, for example, the texture function is defined over parametric left-parenthesis u comma v right-parenthesis coordinates, the filtering task is to average it over a region in left-parenthesis u comma v right-parenthesis :

upper T Subscript normal b normal o normal x Baseline left-parenthesis x comma y right-parenthesis equals StartFraction 1 Over left-parenthesis u 1 minus u 0 right-parenthesis left-parenthesis v 1 minus v 0 right-parenthesis EndFraction integral Subscript v 0 Superscript v 1 Baseline integral Subscript u 0 Superscript u 1 Baseline upper T left-parenthesis u prime comma v Superscript prime Baseline right-parenthesis normal d u prime normal d v Superscript prime Baseline period

The extent of the filter region can be determined using the derivatives from the previous sections—for example, setting

u 0 equals u minus minus one-half max left-parenthesis StartFraction normal d u Over normal d x EndFraction comma StartFraction normal d u Over normal d y EndFraction right-parenthesis normal a normal n normal d u 1 equals u plus minus one-half max left-parenthesis StartFraction normal d u Over normal d x EndFraction comma StartFraction normal d u Over normal d y EndFraction right-parenthesis

and similarly for v 0 and v 1 to conservatively specify the box’s extent.

The box filter is easy to use, since it can be applied analytically by computing the average of the texture function over the appropriate region. Intuitively, this is a reasonable approach to the texture filtering problem, and it can be computed directly for many texture functions. Indeed, through the rest of this chapter, we will often use a box filter to average texture function values between samples and informally use the term filter region to describe the area being averaged over. This is the most common approach when filtering texture functions.

Even the box filter, with all of its shortcomings, gives acceptable results for texture filtering in many cases. One factor that helps is the fact that a number of samples are usually taken in each pixel. Thus, even if the filtered texture values used in each one are suboptimal, once they are filtered by the pixel reconstruction filter, the end result generally does not suffer too much.

An alternative to using the box filter to filter texture functions is to use the observation that the effect of the ideal sinc filter is to let frequency components below the Nyquist limit pass through unchanged but to remove frequencies past it. Therefore, if we know the frequency content of the texture function (e.g., if it is a sum of terms, each one with known frequency content), then if we replace the high-frequency terms with their average values, we are effectively doing the work of the sinc prefilter.

Finally, for texture functions where none of these techniques is easily applied, a final option is supersampling—the function is evaluated and filtered at multiple locations near the main evaluation point, thus increasing the sampling rate in texture space. If a box filter is used to filter these sample values, this is equivalent to averaging the value of the function. This approach can be expensive if the texture function is complex to evaluate, and as with image sampling, a very large number of samples may be needed to remove aliasing. Although this is a brute-force solution, it is still more efficient than increasing the image sampling rate, since it does not incur the cost of tracing more rays through the scene.