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, Greg Humphreys, and Wenzel Jakob. 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 authors@pbrt.org.

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. Section draft
  • 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
  • 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.

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.)

We would have hoped that the Blender exporter for LuxRender would have worked well for pbrt (LuxRender is originally based on pbrt and still has a similar input file format.) Unfortunately, our experience has been that exporting to OBJ and using obj2pbrt gives a better starting point for the scene materials. (Further, the LuxRender Blender exporter silently fails if there aren’t any lights in the scene.) We suspect that using this exporter as a starting point for a new direct Blender to pbrt exporter might be a worthwhile approach.

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 authors@pbrt.org).