A.1 Main Include File

The core/pbrt.h file is included by all other source files in the system. It contains all global function declarations and inline functions, a few macros and numeric constants, and other globally accessible data. All files that include pbrt.h get a number of other included header files from pbrt.h. This simplifies creation of new source files, almost all of which will want access to these extra headers. However, in the interest of compile time efficiency, we keep the number of these automatically included files to a minimum; the ones here are necessary for almost all modules.

<<Global Include Files>>= 
#include <algorithm> #include <cinttypes> #include <cmath> #include <iostream> #include <limits> #include <memory> #include <string> #include <vector>

Almost all floating-point values in pbrt are declared as Floats. (The only exception is a few cases where a 32-bit float or a 64-bit double is specifically needed (e.g., when saving binary values to files). Whether a Float is actually a float or a double is determined at compile time with the PBRT_FLOAT_AS_DOUBLE macro; this makes it possible to build versions of pbrt using either representation. 32-bit floats almost always have sufficient precision for ray tracing, but it’s helpful to be able to switch to double for numerically tricky situations as well as to verify that rounding error with floats isn’t causing errors for a given scene.

<<Global Forward Declarations>>+=  
#ifdef PBRT_FLOAT_AS_DOUBLE typedef double Float; #else typedef float Float; #endif // PBRT_FLOAT_AS_DOUBLE

A.1.1 Utility Functions

A few short mathematical functions are useful throughout pbrt.

Clamping

Clamp() clamps the given value val to lie between the values low and high. For convenience Clamp() allows the types of the values giving the extent to be different than the type being clamped (but its implementation requires that implicit conversion is legal to the type being clamped). By being implemented this way, the implementation allows calls like Clamp(floatValue, 0, 1) which would otherwise be disallowed by C++’s template type resolution rules.

<<Global Inline Functions>>+=  
template <typename T, typename U, typename V> inline T Clamp(T val, U low, V high) { if (val < low) return low; else if (val > high) return high; else return val; }

Modulus

Mod() computes the remainder of a slash b . pbrt has its own version of this (rather than using %) in order to provide the behavior that the modulus of a negative number is always positive or zero. Starting with C++11, the behavior of % has been specified to return a negative value or zero in this case, so that the identity (a/b)*b + a%b == a holds.

<<Global Inline Functions>>+=  
template <typename T> inline T Mod(T a, T b) { T result = a - (a/b) * b; return (T)((result < 0) ? result + b : result); }

A specialization for Floats calls out to the corresponding standard library function.

<<Global Inline Functions>>+=  
template <> inline Float Mod(Float a, Float b) { return std::fmod(a, b); }

Useful Constants

A number of constants, most of them related to pi , are used enough that it’s worth having them easily available.

<<Global Constants>>+=  
static const Float Pi = 3.14159265358979323846; static const Float InvPi = 0.31830988618379067154; static const Float Inv2Pi = 0.15915494309189533577; static const Float Inv4Pi = 0.07957747154594766788; static const Float PiOver2 = 1.57079632679489661923; static const Float PiOver4 = 0.78539816339744830961; static const Float Sqrt2 = 1.41421356237309504880;

Converting between Angle Measures

Two simple functions convert from angles expressed in degrees to radians, and vice versa:

<<Global Inline Functions>>+=  
inline Float Radians(Float deg) { return (Pi / 180) * deg; } inline Float Degrees(Float rad) { return (180 / Pi) * rad; }

Base-2 Operations

Because the math library doesn’t provide a base-2 logarithm function, we provide one here, using the identity log Subscript 2 Baseline left-parenthesis x right-parenthesis equals log x slash log 2 .

<<Global Inline Functions>>+=  
inline Float Log2(Float x) { const Float invLog2 = 1.442695040888963387004650940071; return std::log(x) * invLog2; }

It’s also useful to be able to compute an integer base-2 logarithm. Rather than computing an (expensive) floating-point logarithm and converting to an integer, it’s much more efficient to count the number of leading zeros up to the first one in the 32-bit binary representation of the value and then subtract this value from 31, which gives the index of the first bit set, which is in turn the integer base-2 logarithm. (This efficiency comes in part from the fact that most CPUs have an instruction to count these zeros.)

The code here uses the __builtin_clz() intrinsic, which is available in the g++ and clang compilers; _BitScanReverse() is used to implement similar functionality with MSVC in code that isn’t shown here.

<<Global Inline Functions>>+=  
inline int Log2Int(uint32_t v) { return 31 - __builtin_clz(v); }

There are clever tricks that can be used to efficiently determine if a given integer is an exact power of 2, or round an integer up to the next higher (or equal) power of 2. (It’s worthwhile to take a minute and work through for yourself how these two functions work.)

<<Global Inline Functions>>+=  
template <typename T> inline bool IsPowerOf2(T v) { return v && !(v & (v - 1)); }

<<Global Inline Functions>>+=  
inline int32_t RoundUpPow2(int32_t v) { v--; v |= v >> 1; v |= v >> 2; v |= v >> 4; v |= v >> 8; v |= v >> 16; return v+1; }

A variant of RoundUpPow2() for int64_t is also provided but isn’t included in the text here.

Some of the low-discrepancy sampling code in Chapter 7 needs to efficiently count the number of trailing zeros in the binary representation of a value; CountTrailingZeros() is a wrapper around a compiler-specific intrinsic that maps to a single instruction on most architectures.

<<Global Inline Functions>>+=  
inline int CountTrailingZeros(uint32_t v) { return __builtin_ctz(v); }

Interval Search

FindInterval() is a helper function that emulates the behavior of std::upper_bound(), but uses a function object to get values at various indices instead of requiring access to an actual array. This way, it becomes possible to bisect arrays that are procedurally generated, such as those interpolated from point samples. The implementation here also adds some bounds checking for corner cases (e.g., making sure that a valid interval is selected even in the case the predicate evaluates to true or false for all entries), which would normally have to follow a call to std::upper_bound().

<<Global Inline Functions>>+=  
template <typename Predicate> int FindInterval(int size, const Predicate &pred) { int first = 0, len = size; while (len > 0) { int half = len >> 1, middle = first + half; <<Bisect range based on value of pred at middle>> 
if (pred(middle)) { first = middle + 1; len -= half + 1; } else len = half;
} return Clamp(first - 1, 0, size - 2); }

<<Bisect range based on value of pred at middle>>= 
if (pred(middle)) { first = middle + 1; len -= half + 1; } else len = half;

A.1.2 Pseudo-Random Numbers

pbrt uses an implementation of the PCG pseudo-random number generator (O’Neill 2014) to generate pseudo-random numbers. This generator is one of the best random number generators currently known. Not only does it pass a variety of rigorous statistical tests that have been the bane of earlier pseudo-random number generators, but its implementation is also extremely efficient.

We wrap its implementation in a small random number generator class, RNG. Doing so allows us to use it with slightly less verbose calls throughout the rest of the system. Random number generator implementation is an esoteric art; therefore, we will not include or discuss the implementation here but will describe the APIs provided.

The RNG class provides two constructors. The first, which takes no arguments, sets the internal state to reasonable defaults. The second takes a single argument that selects a sequence of pseudo-random values.

The PCG random number generator actually allows the user to provide two 64-bit values to configure its operation: one chooses from one of 2 Superscript 63 different sequences of 2 Superscript 64 random numbers, while the second effectively selects a starting point within such a sequence. Many pseudo-random number generators only allow this second form of configuration, which alone isn’t as good: having independent non-overlapping sequences of values rather than different starting points in a single sequence provides greater non-uniformity in the generated values.

For pbrt’s needs, selecting different sequences is sufficient, so the RNG implementation doesn’t provide a mechanism to also select the starting point within a sequence.

<<RNG Public Methods>>= 
RNG(); RNG(uint64_t sequenceIndex) { SetSequence(sequenceIndex); }

RNGs shouldn’t be used in pbrt without either providing an initial sequence index via the constructor or a call to the SetSequence() method; otherwise there’s risk that different parts of the system will inadvertently use correlated sequences of pseudo-random values, which in turn could cause surprising errors.

<<RNG Public Methods>>+=  
void SetSequence(uint64_t sequenceIndex);

There are two variants of the UniformUInt32() method. The first returns a pseudo-random number in the range left-bracket 0 comma 2 Superscript 32 Baseline minus 1 right-bracket .

<<RNG Public Methods>>+=  
uint32_t UniformUInt32();

The second returns a value uniformly distributed in the range left-bracket 0 comma b minus 1 right-bracket given a bound b . The last two versions of pbrt effectively used UniformUInt32() % b for this second computation. That approach is subtly flawed—in the case that b doesn’t evenly divide 2 Superscript 32 , then there is higher probability of choosing any given value in the sub-range left-bracket 0 comma 2 Superscript 32 Baseline normal m normal o normal d b minus 1 right-bracket .

The implementation here first computes the above remainder 2 Superscript 32 Baseline normal m normal o normal d b efficiently using only 32 bit arithmetic and stores it in the variable threshold. Then, if the pseudo-random value returned by UniformUInt32() is less than threshold, it is discarded and a new value is generated. The resulting distribution of values has a uniform distribution after the modulus operation, giving a uniformly distributed sample value.

<<RNG Public Methods>>+=  
uint32_t UniformUInt32(uint32_t b) { uint32_t threshold = (~b + 1u) % b; while (true) { uint32_t r = UniformUInt32(); if (r >= threshold) return r % b; } }

UniformFloat() generates a pseudo-random floating-point number in the half-open interval left-bracket 0 comma 1 right-parenthesis .

<<RNG Public Methods>>+= 
Float UniformFloat() { return std::min(OneMinusEpsilon, UniformUInt32() * 0x1p-32f); }