Exercises

  1. One approach for reducing renderer startup time is to support a binary representation of internal data structures that can be written to disk. For example, for complex scenes, creating the ray acceleration aggregates may take more time than the initial parsing of the scene file. An alternative is to modify the system to have the capability of dumping out a representation of the acceleration structure and all of the primitives inside it after it is first created. The resulting file could then be subsequently read back into memory much more quickly than rebuilding the data structure from scratch. However, because C++ doesn’t have native support for saving arbitrary objects to disk and then reading them back during a subsequent execution of the program (a capability known as serialization or pickling in other languages), adding this feature effectively requires extending many of the objects in pbrt to support this capability on their own. One additional advantage of this approach is that substantial amounts of computation can be invested in creating high-quality acceleration structures, with the knowledge that this cost doesn’t need to be paid each time the scene is loaded into memory. Implement support for serializing the scene representation and then reusing it across multiple renderings of the scene. How is pbrt’s start-up time (up until when rendering begins) affected? What about overall rendering time?
  2. The material assigned to object instances in pbrt is the current material when the instance was defined inside the pbrtObjectBegin()/pbrtObjectEnd() block. This can be inconvenient if the user wants to use the same geometry multiple times in a scene, giving it a different material. Fix the API and the implementation of the TransformedPrimitive class so that the material assigned to instances is the current material when the instance is instantiated, or, if the user hasn’t set a material, the current material when the instance was created.
  3. Generalize pbrt’s API for specifying animation; the current implementation only allows the user to provide two transformation matrices, only at the start and end of a fixed time range. For specifying more complex motion, a more flexible approach may be useful. One improvement is to allow the user to specify an arbitrary number of keyframe transformations, each associated with an arbitrary time. More generally, the system could be extended to support transformations that are explicit functions of time. For example, a rotation could be described with an expression of the form Rotate (time * 2 + 1) 0 0 1 to describe a time-varying rotation about the z axis. Extend pbrt to support a more general matrix animation scheme, and render images showing results that aren’t possible with the current implementation. What is the performance impact of your changes for scenes with animated objects that don’t need the generality of your improvements?
  4. Extend pbrt’s API to have some retained mode semantics so that animated sequences of images can be rendered without needing to respecify the entire scene for each frame. Make sure that it is possible to remove some objects from the scene, add others, modify objects’ materials and transformations from frame to frame, and so on.
  5. In the current implementation, a unique TransformedPrimitive is created for each Shape with an animated transformation. If many shapes have exactly the same animated transformation, this turns out to be a poor choice. Consider the difference between a million-triangle mesh with an animated transformation versus a million independent triangles, all of which happen to have the same animated transformation. In the first case, all of the triangles in the mesh are stored in a single instance of a TransformedPrimitive with an animated transformation. If a ray intersects the conservative bounding box that encompasses all of the object’s motion over the frame time, then it is transformed to the mesh’s object space according to the interpolated transformation at the ray’s time. At this point, the intersection computation is no different from the intersection test with a static primitive; the only overhead due to the animation is from the larger bounding box and rays that hit the bounding box but not the animated primitive and the extra computation for matrix interpolation and transforming each ray once, according to its time. In the second case, each triangle is stored in its own TransformedPrimitive, all of which happen to have the same AnimatedTransform. Each instance of TransformedPrimitive will have a large bounding box to encompass each triangle’s motion, giving the acceleration structure a difficult set of inputs to deal with: many primitives with substantially overlapping bounding boxes. The impact on ray–primitive intersection efficiency will be high: the ray will be redundantly transformed many times by what happens to be the same recomputed interpolated transformation, and many intersection tests will be performed due to the large bounding boxes. Overall performance will be much worse than the first case. To address this case, modify the code that implements the pbrt API calls so that if independent shapes are provided with the same animated transformation, they’re all collected into a single acceleration structure with a single animated transformation. What is the performance improvement for the worst case outlined above? Is there an impact for more typical scenes with animated primitives?