Overview
This document covers a variety of topics related to working with pbrt-v3, the rendering system described in the third edition of Physically Based Rendering: From Theory to Implementation, by Matt Pharr, Wenzel Jakob, and Greg Humphreys. Because most users of pbrt are also developers who also work with the system’s source code, this guide also includes coverage of a number of topics related to the system’s structure and organization.
If you find errors in this text or have ideas for topics that should be discussed, please either submit a bug in the pbrt issue tracker, or send an email to [email protected].
Note: this document is still a work in progress; a number of sections are either unwritten or incomplete.
Changes from pbrt-v2
The system has seen many changes since the second edition. To figure out how to use the new features, you may want to look at the example scene files and read through the source code to figure out the parameters and details of the following.
- Bidirectional path tracing:
Integrator "bdpt"
does proper bidirectional path tracing with multiple importance sampling. - Metropolis sampling:
Integrator "mlt"
uses the bidirectional path tracer with Hachisuka et al.'s “Multiplexed Metropolis Light Transport” technique. - Improved numerical robustness for intersection calculations: epsilons are small and provably conservative.
- Subsurface scattering: all new implementation, integrated into the path
tracing integrator. See the
head
directory in the scenes distribution. - Curve shape: thin ribbons described by bicubic Bezier curves. Great for hair, fur, and grass.
- PLY mesh support: meshes in PLY format can be used directly:
Shape "plymesh" "string filename" "mesh.ply"
- Realistic camera model: tracing rays through lenses to make images!
- Participating media: the boundaries of shapes are now used to delineate the extent of regions of participating media in the scene.
- New samplers: a much-improved Halton sampler, and an all-new Sobol’ sampler are both quite effective for path tracing and bidirectional path tracing.
- Fourier representation of measured materials: an implementation of Jakob
et al’s A Comprehensive Framework for Rendering Layered
Materials.
- New versions of the BSDF files it uses can be generated with layerlab.
- Improved microfacet models: specular transmission through microfacets, and Heitz’s improved importance sampling.
- No external dependencies: thanks to Lode Vandevenne’s lodepng, Diego Nehab’s rply, and Emil Mikulic’s TARGA library, no external libraries need to be compiled to build pbrt. The only slightly bigger dependency is OpenEXR, and its build system is fully integrated with that of pbrt.
Many other small things have been improved (parallelization scheme, image updates, statistics system, overall cleanliness of interfaces); see the source code (and the book) for details.
File format
We’ve tried to keep the scene description file format as unchanged from pbrt-v2 as much as possible. However, progress in other parts of the system required changes to the scene description format.
First, the “Renderer”, “SurfaceIntegrator”, and “VolumeIntegrator” directives have all been unified under “Integrator”; only a single integrator now needs to be specified.
The following specific implementations were removed:
- Accelerator “grid”
- Use “bvh” or “kdtree” instead.
- Material “measured”, “shinymetal”
- The new “fourier” material should now be used for measured BRDFs.
- Use “uber” in place of “shinymetal”.
- VolumeRegion: all
- Use the new participating media representation described above
- SurfaceIntegrator: photonmap, irradiancecache, igi, dipolesubsurface,
ambientocclusion, useprobes, diffuseprt, glossyprt
- Use “sppm” for the “photonmap”.
- The “path” integrator now handles subsurface scattering directly.
- The others aren’t as good as path tracing anyway. :-)
- VolumeIntegrator: single, emission
- Use the “volpath” path tracing integrator.
- Sampler: bestcandidate
- Use any other sampler.
- Renderer: all
- Use “Integrator”, as described above.
Working with the code
Building pbrt
Please see the README.md file in the pbrt-v3 distribution for general information about how to check out and compile the system.
Porting to different targets
pbrt should compute out of the box for semi-modern versions of Linux, FreeBSD, OpenBSD, OS X, and Windows. A C++ compiler with good support for C++11 is required. (Therefore, pbrt definitely won’t compile with any versions of MSVC earlier than 2013, any versions of g++ before 4.8, or any versions of clang before 3.1).
The CMakeLists.txt file does its best to automatically determine the capabilities and limitations of the system's compiler and to determine which header files are available. If pbrt doesn't build out of the box on your system and you're able to figure out the changes needed in the CMakeLists.txt file, we'd be delighted to receive a github pull request. Alternatively, open a bug in the issue tracker that includes the compiler output from your failed build and we'll try to help get it running.
Note that if extensive changes to pbrt are required to build it on a new target, we may not accept the pull request, as it’s also important that the source code on github be as close as possible to the source code in the physical book. Thus, for example, we wouldn’t be interested in a pull request that removed most of the usage of C++11 to get the system to build with MSVC 2012 or earlier.
Branches
We maintain two branches of the pbrt-v3 system. The first, “book” corresponds as closely as possible to the source code as printed in the physical book, Physically Based Rendering. The only differences between the two come from cases where there was a bug in the code as published.
The second branch is available in “master”. It includes all of the bug fixes in the “book” branch, but also includes some useful new functionality that isn’t in the code as presented in the book. In general, we don’t want this branch to diverge from the contents of the book too much, but given a sufficiently useful new feature and in particular, given one that can be added without substantially changing the structure of the rest of the system, we’ll add it to this branch. (It’s likely that most of this additional functionality will be included in an eventual fourth edition of the book.) This added code is much more extensively commented than the code that is documented in the book.
Here is the main new functionality in the “master” branch:
- New files
src/core/lightdistrib.*
that provide a better approach for sampling light sources with scenes that have thousands of lights than the methods for sampling lights described in the book. We added this functionality in order to be able to efficiently render the “Measure One” scenes in the pbrt scenes distribution, as they include tens of thousands of emitters; the preexisting sampling methods were unable to render these scenes without a prohibitive number of samples per pixel. - Use of the Google logging library for assertions and runtime logging (described in more detail in the next section).
- The "pixelbounds" parameter for many integrators (described in the Debugging section below).
- Support for two-sided area light sources via the DiffuseAreaLight "boolean twosided" parameter (rather than always emitting from the side oriented with the surface normal).
- A number of performance optimizations to the Curve shape intersection routines.
- Support for Curves described using the b-spline basis as well as support for 2nd degree spline curves (in addition to 3rd degree as before).
- A new "hair" Material and BxDF for rendering hair.
- An implementation of the "Disney" BSDF as described here and here.
Assertions and logging
The original version of pbrt (as still present in the “book” branch) uses a
custom Assert()
macro for runtime assertions. This macro is disabled for
release builds, as some of its checks require meaningful amounts of
computation.
In the “master” branch, we have replaced Assert()
with
glog, the Google Logging system. It
includes both improved assertion checks as well as the ability to log
system execution; we have found the logging functionality to be frequently
useful when debugging the system.
For assertions, a variety of checks are available. The simplest is
CHECK()
, which is essentially a replacement for Assert()
(or regular
assert()
for that matter); an error and the current stack
trace are printed. There is also a DCHECK()
, which is similar,
but is only enabled for debug builds. Furthermore, additional information
about the failure can be provided using C++ output streams:
CHECK(foo.x == bar.y) << "Unexpected inequality: foo = " << foo << ", bar = " << bar;
When possible, it’s better to use macros that separately take the value
being checked and the expected value. For example, CHECK_EQ()
takes two values and tests whether they are equal, including the two values
in the error message if they are not. Similarly, CHECK_NE()
checks for inequality,
and CHECK_LT()
, CHECK_LE()
, CHECK_GT()
,
and CHECK_GE()
check for less than, less than or equal,
greater than, and greater than or equal, respectively. There are
debug-only variants of these that also are prefixed with “D”.
The glog system also includes a rich logging infrastructure, which makes it possible information about “interesting” program events. In addition to the in addition to the string provided by the user, a fine-grained time stamp and thread id are included in the log, which are also often helpful:
LOG(INFO) << "Starting film tile " << tileBounds;
There are four logging levels: INFO
, WARNING
, ERROR
, and FATAL
.
Normally logs are stored in the system temporary directory (either as
specified by the TMPDIR
environment variable, or in a system default like
/tmp
), but this can be overridden using
pbrt’s --logdir
command-line option. pbrt has the command
line option --logtostderr
, which causes logging information to
be printed to standard error at runtime.
(Note that on OSX, TMPDIR
is set to a unique per-session
temporary directory; you won’t find your logs if you look for them
in /tmp
.)
We have removed the Info()
and Severe()
functions from pbrt (which were essentially used for this form of logging).
However, we have retained
Warning()
and Error()
: the distinction is that those two continue to be
used for user-facing error messages (parameters not recognized in input
files and so forth), while the logging functionality is used only for
information that is only useful for developers.
Sometimes verbose logging is useful for debugging. glog also offers
VLOG()
, which takes an integer logging level as an argument. Then, the
--v
command-line option to pbrt can be used to enable various verbose
logging levels.
Debugging
Debugging a ray-tracer can be its own special kind of fun. When the system crashes, it may take hours of computation to reach the point where the crash occurs. When an incorrect image is generated, chasing down why it is that the image is usually-but-not-always correct can be a very tricky exercise.
When trouble strikes, it’s usually best to start by rendering the scene again using a debug build of pbrt. Debug builds of the system not only include debugging symbols and aren’t highly optimized (so that the program can be effectively debugged in a debugger), but also include more runtime assertions, which may help narrow down the issue. We find that debug builds are generally three to five times slower than optimized builds; thus, if you’re not debugging, make sure you’re not using a debug build! (See the pbrt-v3 README.md file for information about how to create a debug build.)
(Depending on how easy it is to work with multi-threaded programs in your
debugger, you may find it helpful to give pbrt --nthreads=1
as a
command-line argument when debugging. Program execution will be even
slower, but tracing program execution in a single thread is often
easier. However, if a bug disappears when you use a single thread, you may
have a data race or other multithreading-related issue.)
One of the best cases is if an assertion is failing. This at least gives an initial clue to the problem–assuming that the assertion is not itself buggy, then the debugging task is just to figure out the sequence of events that led to it failing. In general, we’ve found that taking the time to add carefully-considered assertions to the system more than pays off in terms of reducing future time working backward when things go wrong.
If the system crashes outright (e.g., with a segmentation violation), then the issue is likely corruption in the memory heap or another problem related to dynamic memory management. For these sorts of bugs, we've found valgrind and address sanitizer to be effective.
If the crash happens intermittently and especially if it doesn't present
itself when running with a single thread (--nthreads=1
), the
issue is likely due to a race condition or other issue related to
parallel execution. We have found that the
helgrind tool
to be very useful for debugging threading-related bugs. (See
also Thread
Sanitizer.)
For help with chasing down more subtle errors, many of the classes in the
system both have implementations of operator<<
for printing to C++
ostreams and also have a ToString()
method that returns a std::string
describing the values stored in an object. Both of these can be useful when
printing out debugging information, possibly in conjunction with the
logging system described in Section [Assertions and Logging]. (Note,
however, that output will be garbled if multiple threads concurrently print
to std::cout or std::cerr; either use a single thread, or use the logging
mechanism.) If you find it useful to add these methods to a class that
doesn’t currently have them implemented, please send us a pull request with
the corresponding changes so that they can be made available to other pbrt
users.
For images of complex scenes with many samples per pixel, it may be
necessary for pbrt to run for a long time before it gets to the point where
a bug manifests itself, which in turn can make debugging slow going. If
you can figure out the pixel coordinates of the pixel where a bug occurs,
there is a “pixelbounds” parameter that limits rendering to a given range
of pixels that can greatly speed up this process; this parameter is
supported by all of the integrators other than SPPMIntegrator
and
MLTIntegrator
.
The pixel bounds are specified by four integer values, corresponding (in
order) to starting x, ending x, starting y, and ending y pixel
coordinates. If specified, pbrt will do minimal work for the other image
pixels, but will ensure (as much as possible) that the program state is
consistent with how it would be for those pixels if the entire image was
rendered. (Note that the RandomSampler
won’t have the correct starting
state if this parameter is used, though this sampler generally shouldn’t be
used anyway.)
Unit tests
We have written unit tests for some parts of the system (primarily for new
functionality added with pbrt-v3, but some to test pre-existing
functionality). Running the pbrt_test
executable, which is built as part
of the regular build process, causes all tests to be executed. Unit tests
are written using the Google C++ Testing
Framework, which is included with
the pbrt distribution.
See the Google Test
Primer
for more information about how to write new test.
We have found these tests to be quite useful when developing new features, testing the system after code changes, and when porting the system to new targets. Over time, we plan to add more tests and are also always happy to receive pull requests with additions to the system’s tests.
Pull requests
We’re always happy to get pull requests or patches that improve pbrt. However, we are unlikely to accept pull requests that significantly change the system’s structure, as we don’t want the “master” branch to diverge too far from the contents of the book. (In this cases, however, we certainly encourage you to maintain a separate fork of the system on github and to let people know about it on the pbrt mailing list.)
Pull requests that fix bugs in the system or improve portability are always
welcome. (If you have an itch to write unit tests and add new ones to
src/tests
, we’re also happy to expand the test coverage of the system!)
Finally, if you write a converter that makes it easier to export scenes
from other file formats or modeling systems to pbrt’s format, we’d happily
include it in the pbrt distribution.
Rendering with pbrt
Example scenes
Over 8GB of example scenes that exercise a wide range of pbrt's functionality (and make pretty images) are available for download. (Many are new and weren’t available with previous versions of pbrt.)
Converting Scenes to pbrt’s format
Given an amazing scene in another 3D file format, there are a few options for converting it to be used in pbrt. (We’re always happy to have help with improvements in this area!)
Cinema 4D
The exporters/cinema4d
directory in the pbrt-v3 distribution provides an
exporter from Cinema 4D. This exporter was developed to export the amazing
“landscape” scene that is on the book’s front cover from Cinema 4D, so thus
is up to date with respect to pbrt’s material models and rendering
settings. We have seen good results with using this exporter for other
Cinema 4D scenes.
Wavefront OBJ
The pbrt-v3 distribution includes a converter from the Wavefront OBJ
format, obj2pbrt
, that is built when the rest of the system is compiled.
To run it, provide the path to an OBJ file and a filename for a new pbrt
file:
$ obj2pbrt scene.obj scene.pbrt
If there is an accompanying material description file (e.g. scene.mtl
),
the values in it will be roughly mapped to corresponding pbrt materials.
You will likely need to manually edit and tune the materials in the
generated pbrt file in order to achieve reasonably good-looking
results.
Note that OBJ files only describe scene geometry; they don’t include camera
specifications or descriptions of light sources. (Thus, the generated pbrt
input file only includes shape and material specifications that you’ll need
to add inside the WorldBegin
/WorldEnd
block of a full pbrt input file.)
Unless you have camera and light source information separately, you’ll need
to specify both on your own (see “General Tips” below for some ideas about
how to do this.)
Blender
Many very nice scenes have been modeled in Blender and are freely available. (See, for example, the BlendSwap website for many scenes that can be used via a Creative Commons license.) Our experience has been that the best approach to export scenes from Blender is to use Blender’s native OBJ export (available via the File/Export menu item) and then to use the obj2pbrt utility described above to convert to pbrt’s format.
More recently, Giulio Jiang has developed a native Blender exporter for pbrt; see also the exporter's github page.
Blender scene files may have texture maps for the scene included directly
in their .blend
file. Choose “File/External Data/Unpack into Files” in
Blender to save those files independently on disk. (Note that if the
textures aren’t PNG or TGA format, you’ll need to convert to one of those
for pbrt to be able to use them.)
Maya
PBRTForMaya, a Maya plugin to export scenes in pbrt's format, has been developed by Haarm-Pieter Duiker.
Houdini
A Houdini exporter for pbrt, an exporter from SideFX's amazing Houdini system, has been written by Jim Price.
Old exporters
The pbrt-v2 distribution includes exporters for 2010 era 3DS Max (which was used for the model used for the cover image for the second edition of the book), Blender, Mathematica, and Structure Synth. All of these are very much out of date, both due to changes over the past six years in in the systems they exported from as well as changes in pbrt. Some of these may be useful for developing updated exporters for the corresponding systems for pbrt-v3.
General tips
A scene exported using one of the above exporters is certain to not immediately render beautifully as is. Here are some suggestions for how to take an initial export and turn it into something that looks great.
First, you may find it useful to run
$ pbrt --toply scene.pbrt > newscene.pbrt
This will convert triangle meshes into more compact binary PLY files, giving you a much smaller pbrt scene file to edit.
Next, if the exporter doesn’t include camera information, the first thing
to do is to find a good view. The “environment” camera (which renders an
image in all directions) can be useful for finding a good initial position
for the camera. Keep rendering images and adjusting the camera position to
taste. (For efficiency, use as few pixel samples as you can tolerate and
learn to squint and interpret noisy renderings!) Then, you can use the
origin you’ve chosen as the basis for specifying a LookAt
transformation
for a more conventional camera model.
While placing the camera, it can be helpful to have a point light source at the camera’s position. Adding a light source like the following to your scene file does this in a way that ensures that the light moves appropriately to wherever the camera has been placed. (You may need to scale the intensity up or down for good results–remember the radius-squared falloff!
AttributeBegin
CoordSysTransform "camera"
LightSource "point" "color I" [10 10 10]
AttributeEnd
Once the camera is placed, we have found that it’s next useful to set up
approximate light sources. For outdoor scenes, a good HDR environment map
is often all that is needed for lighting. (You may want to consider using
imgtool makesky
to make a realistic HDR sky environment map.)
For indoor scenes, you may want a combination of an environment map for the
outside and point and/or area light sources for interior lights. You may
find it useful to examine the scene in the modeling system that it came
from to determine which geometry corresponds to area light sources and to
try adding AreaLightSource
properties to those. (Note that in pbrt, area
light sources only emit lights on the side that the surface normal points;
you may need a ReverseOrientation
directive to make the light come out in
the right direction.)
Given good lighting, the next step is to tune the materials (or set them
from scratch). It can be helpful to pick a material and set it to an
extreme value (such as a “matte” material that is pure red) and render the
scene; this quickly shows which geometric models have that material
associated with it. Alternatively, consider applying this
patch to your pbrt source tree; after rebuilding
pbrt, if you set the PBRT_MTL_HACK
environment variable and render the
scene, pbrt will generate a separate image for each NamedMaterial
in the scene, with a filename corresponding to the material name. Each
of these images will only include the objects with that material, which
makes it easier to see what’s what.
As you figure out which material names correspond to what geometry, watch
for objects that are missing texture maps and add Texture
specifications
for them and use them in the materials. (The good news is that such objects
generally do have correct texture coordinates with them, so this mostly
just works.)
Submitting updates
We’d love to increase the scope (and quality) of scenes available for use
with pbrt
. If you have a nice scene in pbrt
's format that you’d like to
have included in this distribution, or if you have improvements to the
current set of scenes, we’d love to have them! (Even finding additional
good camera locations for the existing scenes or generating variants of
some of the existing scenes with different lighting setups is helpful.)
We’re particularly interested in adding scenes that include complex and realistic character models as well as scenes with realistic distributions of hair or fur.
If you have a scene to share, please post the resulting file online somewhere that we can access it and send us a pointer at [email protected]).