B.1 Parameter Sets
A key problem that a rendering API must address is extensibility—as new features are added to the system, how does the user-visible API change and what parts of its implementation change? For pbrt, it’s important that developers be able to easily add new implementations of Shapes, Cameras, Integrators, and so forth. We’ve designed the API with this goal in mind.
To this end, the caller-visible API and its implementation are both as unaware as possible of what particular parameters these objects take and what their semantics are. pbrt uses the ParamSet class to bundle up parameters and their values in a generic way. For example, it might record that there is a single floating-point value named “radius” with a value of 2.5 and an array of four color values named “specular” with various SPDs. The ParamSet provides methods for both setting and retrieving values from these kinds of generic parameter lists. It is defined in core/paramset.h and core/paramset.cpp.
Most of pbrt’s API routines take a ParamSet as one of their parameters; for example, the shape creation routine, pbrtShape(), just takes a string giving the name of the shape to make and a ParamSet with parameters for it. The creation routine of the corresponding shape implementation is called with the ParamSet passed along as a parameter; it extracts values from the ParamSet to get parameters to use in a call to the class’s constructor.
A ParamSet can hold eleven types of parameters: Booleans, integers, floating-point values, points (2D and 3D), vectors (2D and 3D), normals, spectra, strings, and the names of Textures that are being used as parameters for Materials and other Textures. Internally, it stores a vector of named values for each of the different types that it stores; each parameter is represented by a pointer to a ParamSetItem of the appropriate type. A shared_ptr is used for these pointers; doing so allows a parameter to easily be stored in multiple ParamSets, which we’ll find useful in the following.
Storing parameters unsorted in vectors means that searching for a given parameter takes time, where is the number of parameters of the parameter’s type. In practice, there are just a handful of parameters to any function, so a more time-efficient representation isn’t necessary.
B.1.1 The ParamSetItem Structure
The ParamSetItem structure stores all of the relevant information about a single parameter, such as its name, its base type, and its value(s). For example (using the syntax from pbrt’s input files), the foo parameter
has a base type of float, and six values have been supplied for it. It would be represented by a ParamSetItem<Float>.
The ParamSetItem directly initializes its members from the arguments and makes a copy of the values.
The Boolean value lookedUp is set to true after the value has been retrieved from the ParamSet. This makes it possible to print warning messages if any parameters were added to the parameter set but never used, which typically indicates a misspelling in the scene description file or other user error.
B.1.2 Adding to the Parameter Set
To add an entry to the parameter set, the appropriate ParamSet method should be called with the name of the parameter, a pointer to its data, and the number of data items. These methods first remove previous values for the parameter in the ParamSet, if any.
We won’t include the rest of the methods to add data to the ParamSet, but we do include their prototypes here for reference. The erasure methods are also straightforward and won’t be included here.
A number of different methods for adding spectral data are provided, making it easy for this data to be supplied with a variety of representations. The RGB and XYZ variants take 3 floating-point values for each spectrum. AddBlackbodySpectrum() takes pairs of temperature in Kelvins and a scale factor; it uses BlackbodyNormalized() to compute the SPD, which it scales with the given scale. Finally, AddSampledSpectrumFiles() reads SPDs from files on disk; both it and AddSampledSpectrum() construct a piecewise linear SPD given pairs of wavelengths and SPD values at each wavelength.
B.1.3 Looking up Values in the Parameter Set
To retrieve a parameter value from a set, it is necessary to loop through the entries of the requested type and return the appropriate value, if any. There are two versions of the lookup method for each parameter type: a simple one for parameters that have a single data value, and a more general one that returns a pointer to the possibly multiple values of array parameter types. The first method mostly serves to reduce the amount of code needed in routines that retrieve parameter values.
The methods that look up a single item (e.g., FindOneFloat()) take the name of the parameter and a default value. If the parameter is not found, the default value is returned. This makes it easy to write initialization code like
In this case, it is not an error if the user didn’t provide a “radius” parameter value; the default value will be used instead. If calling code wants to detect a missing parameter and issue an error, the appropriate second variant of lookup method should be used, since those methods return a nullptr value if the parameter isn’t found.
We won’t include the declarations of the analogous methods for the remaining types here (FindOneInt(), FindOnePoint3f(), and so forth); they all follow the same form as FindOneFloat()—each takes a parameter name and a default value and returns a value of the corresponding type.
The second kind of lookup method returns a pointer to the data if the data is present and returns the number of values in n.
The general lookup functions for the other types follow the same form and so won’t be included here.
Because the user may misspell parameter names in the scene description file, the ParamSet also provides a ReportUnused() function, not included here, that goes through the parameter set and reports if any of the parameters present were never looked up, checking the ParamSetItem::lookedUp member variable. For any items where this variable is false, it is likely that the user has given an incorrect parameter.
The ParamSet::Clear() method clears all of the individual parameter vectors. The corresponding ParamSetItems will in turn be freed if their reference count goes to 0.