②
One nice property of mesh-based shapes like triangle meshes and
subdivision surfaces is that the shape’s vertices can be transformed into
world space, so that it isn’t necessary to transform rays into object space
before performing ray intersection tests. Interestingly enough, it is
possible to do the same thing for ray–quadric intersections.
The implicit forms of the quadrics in this chapter were all of the form
where some of the constants were zero. More generally, we can
define quadric surfaces by the equation
where most of the parameters don’t directly correspond to the
earlier
. In this form, the quadric can be represented by a
symmetric matrix :
Given this representation, first show that the matrix representing a
quadric transformed by the matrix is
To do so, show that for any point where , if we
apply a transformation to and compute , we’d like to find
so that .
Next, substitute the ray equation into the earlier more general quadric equation
to compute coefficients for the quadratic equation in
terms of entries of the matrix to pass to the Quadratic() function.
Now implement this approach in pbrt and use it instead of the original
quadric intersection routines. Note that you will still need to transform
the resulting world space hit points into object space to test against
, if it is not , and so on. How does performance compare to the
original scheme?
① Improve the object space bounding box routines for the quadrics
to properly account for , and compute tighter bounding boxes
when possible. How much does this improve performance when rendering
scenes with partial quadric shapes?
② There is room to optimize the implementations of the
various quadric primitives in pbrt in a number of ways. For example, for
complete spheres some of the tests in the intersection routine related to
partial spheres are unnecessary. Furthermore, some of the quadrics have
calls to trigonometric functions that could be turned into simpler
expressions using insight about the geometry of the particular primitives.
Investigate ways to speed up these methods. How much does doing so improve
the overall run time of pbrt when rendering scenes with quadrics?
① Currently pbrt recomputes the partial derivatives
and for triangles every time they are needed, even though they are
constant for each triangle. Precompute these vectors and analyze the
speed/storage trade-off, especially for large triangle meshes. How do
the depth complexity of the scene and the size of triangles in the image
affect this trade-off?
② Implement a general polygon primitive that supports an
arbitrary number of vertices and convex or concave polygons as a new
Shape in pbrt. You can assume that a valid polygon has been
provided and that all of the vertices of the polygon lie on the same plane,
although you might want to issue a warning when this is not the case.
An efficient technique for computing ray–polygon intersections is to find
the plane equation for the polygon from its normal and a point on the
plane. Then compute the intersection of the ray with that plane and
project the intersection point and the polygon vertices to 2D. Then apply
a 2D point-in-polygon test to determine if the point is inside the polygon.
An easy way to do this is to effectively do a 2D ray-tracing computation,
intersect the ray with each of the edge segments, and count how many it
goes through. If it goes through an odd number of them, the point is
inside the polygon and there is an intersection. See
Figure 3.47 for an illustration of this idea.
You may find it helpful to read the article by Haines (1994) that surveys a
number of approaches for efficient point-in-polygon tests. Some of the
techniques described there may be helpful for optimizing this test.
Furthermore, Section 13.3.3 of Schneider and Eberly (2003) discusses
strategies for getting all the corner cases right: for example, when the 2D
ray is aligned precisely with an edge or passes through a vertex of the
polygon.
Figure 3.47: A ray–polygon intersection test can be
performed by finding the point where the ray intersects the polygon’s
plane, projecting the hit point and polygon vertices onto an axis-aligned
plane, and doing a 2D point-in-polygon test there.
② Constructive solid geometry (CSG) is a classic solid
modeling technique, where complex shapes are built up by considering the
union, intersection, and differences of more primitive shapes. For
example, a sphere could be used to create pits in a cylinder if a shape was
modeled as the difference of a cylinder and set of spheres that partially
overlapped it. See Hoffmann (1989) for further information about CSG.
Add support for CSG to pbrt and render images that demonstrate
interesting shapes that can be rendered using CSG. You may want to read
Roth (1982), which first described how ray tracing could be used to render
models described by CSG, as well as Amanatides and Mitchell (1990), which
discusses precision-related issues for CSG ray tracing.
② Procedurally described parametric surfaces: Write a
Shape that takes a general mathematical expression of the form
that describes a parametric surface as a
function of . Evaluate the given function at a grid of
positions, and create a triangle mesh that approximates the given
surface.
② Adaptive curve refinement: adjust the number of levels of
recursive refinement used for intersection with Curve shapes based on
the on-screen area that they cover. One approach is to take advantage of
the RayDifferential class, which represents the image space area that
a given ray represents. (However, currently, only Rays—not
RayDifferentials—are passed to the Shape::Intersect()
method, so you’d need to modify other parts of the system to make ray
differentials available.) Alternatively, you could modify the Camera
to provide information about the projected length of vectors between points
in world space on the image plane and make the camera available during
Curve creation.
③ Almost all methods for subdivision surfaces are based on
either refining a mesh of triangles or a mesh of quadrilaterals. If a
rendering system only supports one type of mesh, meshes of the other type
are typically tessellated to make faces of the expected type in a
preprocessing step. However, doing this can introduce artifacts in the
final subdivision surface. Read Stam and Loop’s paper on a hybrid
subdivision scheme that supports meshes with both quadrilateral and
triangular faces (Stam and Loop 2003), and implement their method.
Demonstrate cases where the subdivision surface that your implementation
creates does not have artifacts that are present in the output from pbrt’s
current subdivision implementation.
② The smoothness of subdivision surfaces isn’t always
desirable. Sometimes it is useful to be able to flag some edges of a
subdivision control mesh as “creases” and apply different subdivision
rules there to preserve a sharp edge. Extend the subdivision surface
implementation in this chapter so that some edges can be denoted as
creases, and use the boundary subdivision rules to compute the positions of
vertices along those edges. Render images showing the difference this
makes.
③
Adaptive subdivision: a weakness of the subdivision surface
implementation in Section 3.8 is that each face is always
refined a fixed number of times: this may mean that some faces are
underrefined, leading to visible faceting in the triangle mesh, and some
faces are overrefined, leading to excessive memory use and rendering time.
With adaptive subdivision, individual faces are no longer subdivided once a
particular error threshold has been reached.
An easy error threshold to implement computes the face normals of each face
and its directly adjacent faces. If they are sufficiently close to each
other (e.g., as tested via dot products), then the limit surface for that
face will be reasonably flat and further refinement will likely make little
difference to the final surface. Alternatively, you might want to
approximate the area that a subdivided face covers on the image plane and
continue subdividing until this area becomes sufficiently small. This
approximation could be done using ray differentials; see
Section 10.1.1 for an explanation of
how to relate the ray differential to the screen space footprint.
The trickiest part of this exercise is that some faces that don’t need
subdivision due to the flatness test will still need to be subdivided in
order to provide vertices so that neighboring faces that do need to
subdivide can get their vertex one-rings. In particular, adjacent faces
can differ by no more than one level of subdivision. You may find it
useful to read recent papers by Patney et al. (2009) and Fisher et al. (2009) for discussion of how to avoid cracks in adaptively
subdivided meshes.
③ Ray-tracing point-sampled geometry: extending methods for
rendering complex models represented as a collection of point samples
(Levoy and Whitted 1985; Pfister et al. 2000; Rusinkiewicz and Levoy 2000), Schaufler and Jensen (2000) have described a method for intersecting rays with
collections of oriented point samples in space. They probabilistically
determined that an intersection has occurred when a ray approaches a
sufficient local density of point samples and computes a surface normal
with a weighted average of the nearby samples. Read their paper and extend
pbrt to support a point-sampled geometry shape. Do any of pbrt’s basic
interfaces need to be extended or generalized to support a shape like this?
③ Deformation motion blur: the TransformedPrimitive in
Section 4.1.2 of Chapter 4 supports animated
shapes via transformations of primitives that vary over time. However,
this type of animation isn’t general enough to represent a triangle mesh
where each vertex has a position given at the start time and another one at
the end time. (For example, this type of animation description can be used
to describe a running character model where different parts of the body
are moving in different ways.)
Implement a more general Triangle shape that supports specifying
vertex positions at the start and end of frame and interpolates between
them based on the ray time passed to the intersection methods. Be sure to
update the bounding routines appropriately.
Triangle meshes with very large amounts of motion may exhibit poor
performance due to triangles sweeping out very large bounding boxes and
thus many ray–triangle intersections being performed that don’t hit the
triangle. Can you come up with approaches that could be used to reduce the
impact of this problem?
③ Implicit functions: just as implicit definitions of the
quadric shapes are a useful starting point for deriving ray-intersection
algorithms, more complex implicit functions can also be used to define
interesting shapes. In particular, difficult-to-model organic shapes,
water drops, and so on can be well represented by implicit surfaces.
Blinn (1982a) introduced the idea of directly rendering implicit surfaces,
and Wyvill and Wyvill (1989) gave a basis function for implicit surfaces
with a number of advantages compared to Blinn’s.
Implement a method for finding ray intersections with general implicit
surfaces and add it to pbrt. You may wish to read papers by Kalra and Barr (1989) and Hart (1996) for methods for ray tracing them. Mitchell’s
algorithm for robust ray intersections with implicit surfaces using interval
arithmetic gives another
effective method for finding these intersections (Mitchell 1990), and
more recently Knoll et al. (2009) described refinements to this idea. You
may find an approach along these lines easier to implement than the others.
See Moore’s book on interval arithmetic as needed for reference
(Moore 1966).
③ L-systems: a very successful technique for procedurally
modeling plants was introduced to graphics by Alvy Ray Smith (1984), who
applied Lindenmayer systems (L-systems) to model branching plant
structures. Prusinkiewicz and collaborators have generalized this approach
to encompass a much wider variety of types of plants and effects that
determine their appearance (Prusinkiewicz 1986; Prusinkiewicz et al. 1994;
Deussen et al. 1998; Prusinkiewicz et al. 2001).
L-systems describe the
branching structure of these types of shapes via a grammar. The grammar
can be evaluated to form expressions that describe a topological
representation of a plant, which can then be translated into a geometric
representation. Add an L-system primitive to pbrt that takes a grammar
as input and evaluates it to create the shape it describes.
① Given an arbitrary point , what bound on the error
from applying a scale transformation of is given by
Equation (3.16)? How much error is actually
introduced?
② The quadric shapes all use the EFloat class for
their intersection tests in order to be able to bound the error in the
computed value so that intersections behind the ray origin aren’t
incorrectly reported as actual intersections. First, measure the
performance difference when using regular Floats for one or more
quadrics when rendering a scene that includes those shapes. Next, manually
derive conservative error bounds for values computed by those shapes as
was done for triangles in Section 3.9.6.
Implement your method. You may find it useful to use the EFloat
class to empirically test your derivation’s correctness. Measure the
performance difference with your implementation.
② One detail thwarts the watertightness of the current
Triangle shape implementation: the translation and shearing of triangle vertices
introduces round-off error, which must be accounted for in the extent of
triangles’ bounding boxes; see Section 3.3 of Woop et al. (2013) for
discussion (and a solution). Modify pbrt to incorporate a solution to
this shortcoming. Can you find scenes where small image errors are
eliminated thanks to your fix?
③ Rendering in camera space: because floating-point arithmetic
provides more precision near the origin than farther away from it,
transforming the scene to a coordinate system with the camera at the origin
can reduce the impact of error due to insufficient floating-point precision. (For
example, consider the difference between rendering a scene in world space with the camera at
, looking at a unit sphere two units away versus a
scene with the camera at the origin, also looking at a sphere two units
away: many more bits of precision are available to represent intersection
points in the latter case.)
Modify pbrt to primarily perform rendering computations in camera space,
rather than world space, as it does currently. You’ll need to modify the Camera
implementations to return camera-space rays and modify shapes to transform
incoming rays from camera space to object space. You’ll want to transform
the vertices of TriangleMeshes all the way to camera space. Measure
the performance of your implementation versus an unmodified version of
pbrt and render a variety of scenes with both systems. (In particular,
make sure you test some scenes that have the camera far from the
world space origin.) What sort of image differences do you see?