Tải bản đầy đủ (.pdf) (860 trang)

physical based rendering from theory to implementation

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (9.99 MB, 860 trang )

DRAFT (4 November 2003) — Do Not Distribute
c
2003 Matt Pharr and Greg Humphreys
1 Introduction 1
1.1 Approaching the System 1
1.2 Rendering and the Ray–Tracing Algorithm 5
1.3 System Overview 5
1.4 How To Proceed Through This Book 21
2 Geometry and Transformations 25
2.1 Vectors 27
2.2 Points 33
2.3 Normals 34
2.4 Rays 35
2.5 Three-dimensional bounding boxes 38
2.6 Transformations 41
2.7 Applying Transforms 52
2.8 Differential Geometry 57
3 Shapes 63
iv Contents
3.1 Basic Shape Interface 63
3.2 Spheres 68
3.3 Cylinders 78
3.4 Disks 82
3.5 Other Quadrics 85
3.6 Triangles and Meshes 87
3.7 ***ADV***: Subdivision Surfaces 98
4 Primitives and Intersection Acceleration 129
4.1 Geometric Primitives 130
4.2 Aggregates 135
4.3 Grid Accelerator 138


4.4 Kd-Tree Accelerator 152
5 Color and Radiometry
177
5.1 Spectral Representation 177
5.2 Basic Radiometry 185
5.3 Working with Radiometric Integrals 190
5.4 Surface Reflection 194
6 Camera Models
201
6.1 Camera Model 201
6.2 Projective Camera Models 205
6.3 Environment Camera 217
7 Sampling and Reconstruction 221
7.1 Fourier Theory 222
7.2 Sampling Theory 225
7.3 Image Sampling Interface 236
7.4 Stratified Sampling 242
7.5 ***ADV***: Low-Discrepancy Sequences 252
7.6 ***ADV***: Best-Candidate Sampling Patterns 265
7.7 Image Reconstruction 279
8 Film and the Imaging Pipeline 293
8.1 Film Interface 294
8.2 Image Film 295
8.3 ***ADV***: Perceptual Issues and Tone Mapping 303
8.4 Device RGB Conversion and Output 322
Contents v
9 Reflection Models 329
9.1 Basic Interface 334
9.2 Specular Reflection and Transmission 337
9.3 Lambertian Reflection 351

9.4 Microfacet Models 352
9.5 Lafortune Model 362
9.6 Fresnel Incidence Effects 364
10 Materials 369
10.1 BSDFs 369
10.2 Material Interface and Bump Mapping 374
10.3 Matte 381
10.4 Plastic 382
10.5 Translucent 383
10.6 Glass 384
10.7 Mirror 385
10.8 Shiny Metal 386
10.9 Diffuse Substrate 387
10.10 Measured Data 388
10.11 Uber Material 390
11 Texture
393
11.1 Texture Interface and Basic Textures 394
11.2 Sampling and Anti-Aliasing 397
11.3 Texture Coordinate Generation 405
11.4 Interpolated Textures 410
11.5 Image Texture 412
11.6 Solid and Procedural Texturing 431
11.7 Noise 440
12 ***ADV***: Volume Scattering 457
12.1 ***ADV***: Volume Scattering Processes 458
12.2 ***ADV***: Phase Functions 463
12.3 ***ADV***: Volume Interface and Homogeneous Volumes 465
12.4 ***ADV***: Varying-Density Volumes 468
12.5 ***ADV***: Volume Aggregates 472

vi Contents
13 Light Sources 477
13.1 Light Interface 478
13.2 Point Lights 480
13.3 Distant Lights 489
13.4 Area Lights 490
13.5 Infinite Area Lights 493
14 Monte Carlo Integration: Basic Concepts 497
14.1 Background and Probability Review 498
14.2 The Monte Carlo Estimator 501
14.3 The Inversion Method for Sampling Random Variables 503
14.4 Transforming Between Different Distribution Functions 506
14.5 The Rejection Method 507
14.6 Transformation in Multiple Dimensions 509
14.7 2D Sampling with Multi-Dimensional Transformation 511
15 Monte Carlo Integration II: Variance Reduction
521
15.1 Analytic Integration Techniques 522
15.2 Careful Sample Placement 526
15.3 Sampling Reflection Functions 531
15.4 Sampling Light Sources 542
15.5 Sampling Volume Scattering 556
15.6 Russian Roulette 558
16 Light Transport
561
16.1 Direct Lighting 563
16.2 The Light Transport Equation 573
16.3 Path Tracing 582
16.4 ***ADV***: Bidirectional Path Tracing 589
16.5 Irradiance Caching 596

16.6 Particle Tracing and Photon Mapping 608
16.7 ***ADV***: Volume Integration 628
17 Summary and Conclusion 645
17.1 Design Retrospective 645
17.2 Major Projects 649
Contents vii
A Utilities 657
A.1 The C++ Standard Library 657
A.2 Communicating with the User 659
A.3 Memory Management 662
A.4 Mathematical Routines 674
A.5 Octrees 680
A.6 Kd-Trees 686
A.7 Image Input Output 693
A.8 Main Include File 693
B Scene Description Interface 697
B.1 Parameter Sets 699
B.2 Global Options 706
B.3 Scene Definition 712
B.4 Scene Object Creation 720
C Input File Format 721
C.1 Parameter Lists 722
C.2 Statement Types 723
C.3 Standard Plug-ins 725
D Dynamic Object Creation 737
D.1 Reading Dynamic Libraries 738
D.2 Object Creation Functions 743
E Index of Classes 773
F Index of Non-Classes 777
G Index of Members 1 783

H Index of Members 2 803
I Index of Code Chunks 823
[Just as] other information should be available to those who want
to learn and understand, program source code is the only means for
programmers to learn the art from their predecessors. It would be
unthinkable for playwrights not to allow other playwrights to read
their plays [and] only be present at theater performances where they
would be barred even from taking notes. Likewise, any good author
is well read, as every child who learns to write will read hundreds
of times more than it writes. Programmers, however, are expected to
invent the alphabet and learn to write long novels all on their own.
Programming cannot grow and learn unless the next generation of
programmers have access to the knowledge and information gathered
by other programmers before them.
— Erik Naggum
Rendering is a fundamental component of computer graphics. At the highest level of abstrac-
tion, rendering describes the process of converting a description of a three-dimensional scene into
an image. Algorithms for animation, geometric modeling, texturing, and other areas of computer
graphics all must feed their results through some sort of rendering process so that the results of their
work are made visible in an image. Rendering has become ubiquitous; from movies to games and
beyond, it has opened new frontiers for creative expression, entertainment, and visualization.
In the early years of the field, research in rendering focused on solving fundamental problems
such as determining which objects are visible from a given viewpoint. As these problem have been
solved and as richer and more realistic scene descriptions have become available, modern rendering
has grown to be built on ideas from a broad range of disciplines, including physics and astrophysics,
astronomy, biology, psychology and the study of perception, and pure and applied mathematics. The
interdisciplinary nature is one of the reasons rendering is such a fascinating area to study.
This book presents a selection of modern rendering algorithms through the documented source

code for a complete rendering system. All of the images in this book, including the ones on the front
and back covers, were rendered by this software. The system,
lrt
, is written using a programming
methodology called literate programming that mixes prose describing the system with the source
code that implements it. We believe that the literate programming approach is a valuable way to
introduce ideas in computer science and computer graphics. Often, some of the subtleties of an
xii Preface
algorithm can be missed until it is implemented; seeing someone else’s implementation is a good
way to acquire a solid understanding of an algorithm’s details. Indeed, we believe that deep under-
standing of a smaller number of algorithms provides a stronger base for further study of graphics
than superficial understanding of many.
Not only does reading an implementation help clarify how an algorithm is implemented in prac-
tice, but by showing these algorithms in the context of a complete and non-trivial software system
we are also able to address issues in the design and implementation of medium-sized rendering
systems. The design of the basic abstractions and interfaces of such a system has substantial impli-
cations for how cleanly algorithms can be expressed in it as well as how well it can support later
addition of new techniques, yet the trade-offs in this design space are rarely discussed.
lrt
and this book focus exclusively on so-called photorealistic rendering, which can be defined
variously as the task of generating images that are indistinguishable from those that a camera would
capture taking a photograph of the scene, or as the task of generating an image that evokes the same
response from a human observer when displayed as if the viewer was looking at the actual scene.
There are many reasons to focus on photorealism. Photorealistic images are necessary for much of
the rendering done by the movie special effects industry, where computer generated imagery must
be mixed seamlessly with footage of the real world. For other entertainment applications where all
of the imagery is synthetic, photorealism is an effective tool to make the observer forget that he
or she is looking at an environment that may not actually exist. Finally, photorealism gives us a
reasonably well-defined metric for evaluating the quality of the output of the rendering system.
A consequence of our approach is that this book and the system it describes do not exhaustively

cover the state-of-the-art in rendering; many interesting topics in photorealistic rendering will not
be covered either because they didn’t fit well with the architecture of the software system (e.g. finite
element radiosity algorithms), or because we believed that the pedagogical value of explaining the
algorithm was outweighed by the complexity of its implementation (e.g. Metropolis light transport).
We will note these decisions as they come up and provide pointers to further resources so the reader
can follow up on topics that are of interest. Many other areas of rendering, such as interactive
rendering, visualization, and illustrative forms of rendering (e.g. pen-and-ink styles) aren’t covered
in this book at all.
Our primary intended audience is students in upper-level undergraduate or graduate-level com-
puter graphics classes. This book assumes existing knowledge of computer graphics at the level
of an introductory college-level course, though certain key concepts from such a course will be
presented again here, such as basic vector geometry and transformations. For students who do not
have experience with programs that have tens of thousands of lines of source code, the literate pro-
gramming style gives a gentle introduction to this complexity. We have paid special attention to
explaining the reasoning behind some of the key interfaces and abstractions in the system in order
to give these readers a sense of why the system was structured the way that it was.
Our secondary, but equally important, audiences are advanced graduate students and researchers,
Overview and Goals xiii
software developers in industry, and individuals interested in the fun of writing their own rendering
systems. Though many of the ideas in this manuscript will likely be familiar to these readers, read-
ing explanations of the algorithms we describe in the literate style may provide new perspectives.
lrt
also includes implementations of a number of newer and/or difficult-to-implement algorithms
and techniques, including subdivision surfaces, Monte Carlo light transport, and volumetric scatter-
ing models; these should be of particular interest even to experienced practitioners in rendering. We
hope that it will also be useful for this audience to see one way to organize a complete non-trivial
rendering system.
lrt
is based on the ray tracing algorithm. Ray tracing is an elegant technique that has its origins
in lens-making; Gauss traced rays through lenses by hand in the 1800s. Ray tracing algorithms on

computers follow the path of infinitesimal rays of light through the scene up to the first surface that
they intersect. This gives a very basic method for finding the first visible object as seen from any
particular position and direction. It is the basis for many rendering algorithms.
lrt
was designed and implemented with three main goals in mind: it should be complete, it
should be illustrative, and it should be physically based.
Completeness implies that the system should not lack important features found in high-quality
commercial rendering systems. In particular, it means that important practical issues, such as anti-
aliasing, robustness, and the ability to efficiently render complex scenes should be addressed thor-
oughly. It is important to face these issues from the start of the system’s design, since it can be quite
difficult to retrofit such functionality to a rendering system after it has been implemented, as these
features can have subtle implications for all components of the system.
Our second goal means that we tried to choose algorithms, data structures, and rendering tech-
niques with care. Since their implementations will be examined by more readers than those in most
rendering systems, we tried to select the most elegant algorithms that we were aware of and imple-
ment them as well as possible. This goal also implied that the system should be small enough for
a single person to understand completely. We have implemented
lrt
with a plug-in architecture,
with a core of basic glue that pushes as much functionality as possible out to external modules. The
result is that one doesn’t need to understand all of the various plug-ins in order to understand the
basic structure of the system. This makes it easier to delve in deeply to parts of interest and skip
others, without losing sight of how the overall system fits together.
There is a tension between the goals of being both complete and illustrative. Implementing
and describing every useful technique that would be found in a production rendering system would
not only make this book extremely long, but it would make the system more complex than most
readers would be interested in. In cases where
lrt
lacks such a useful feature, we have attempted
to design the architecture so that feature could be easily added without altering the overall system

design. Exercises at the end of each chapter suggest programming projects that add new features to
the system.
xiv Preface
The basic foundations for physically-based rendering are the laws of physics and their mathe-
matical expression.
lrt
was designed to use the correct physical units and concepts for the quanti-
ties that it computes and the algorithms it is built from. When configured to do so,
lrt
can compute
images that are physically correct; they accurately reflect the lighting as it would be in a real-world
scene corresponding to the one given to the renderer. One advantage of the decision to use a phys-
ical basis is that it gives a concrete standard of program correctness: for simple scenes, where the
expected result can be computed in closed-form, it
lrt
doesn’t compute the same result, we know
that it must have a bug. Similarly, if different physically-based lighting algorithms in
lrt
give
different results for the same scene, or if
lrt
doesn’t give the same results as another physically
based renderer, there is certainly an error in one of them. Finally, we believe that this physically-
based approach to rendering is valuable because it is rigorous. When it is not clear how a particular
computation should be performed, physics gives an answer that guarantees a consistent result.
Efficiency was secondary to these three goals. Since rendering systems often run for many
minutes or hours in the course of generating an image, efficiency is clearly important. However, we
have mostly confined ourselves to algorithmic efficiency rather than low-level code optimization. In
some cases, obvious micro-optimizations take a back seat to clear, well-organized code, though we
did make some effort to optimize the parts of the system where most of the computation occurs. For

this reason as well as portability,
lrt
is not presented as a parallel or multi-threaded application,
although parallelizing
lrt
would not be very difficult.
In the course of presenting
lrt
and discussing its implementation, we hope to convey some
hard-learned lessons from some years of rendering research and development. There is more to
writing a good renderer than stringing together a set of fast algorithms; making the system both
flexible and robust is the hard part. The system’s performance must degrade gracefully as more
geometry is added to it, as more light sources are added, or as any of the other axes of complexity
are pushed. Numeric stability must be handled carefully; stable algorithms that don’t waste floating-
point precision are critical.
The rewards for going through the process of developing a rendering system that addresses all
of these issues are enormous–writing a new renderer or adding a new feature to an existing renderer
and using it to create an image that couldn’t be generated before is a great pleasure. Our most
fundamental goal in writing this book was to bring the opportunity to do this to a wider audience.
You are encouraged to use the system to render the example scenes on the companion CD as you
progress through the book. Exercises at the end of each chapter suggest modifications to make to
the system that will help you better understand its inner workings and more complex projects to
extend the system to add new features.
We have also created a web site to go with this book, located at
www.pharr.org/lrt
. There you
will find errata and bug fixes, updates to
lrt
’s source code, additional scenes to render, supplemental
utilities, and new plug-in modules. If you come across a bug in

lrt
or an error in this text that is
not listed at the web site, please report it to the e-mail address

.
Additional Reading xv
Donald Knuth’s article Literate Programming (Knuth 1984) describes the main ideas behind
literate programming as well as his
web
programming environment. The seminal T
E
X typeset-
ting system was written with this system and has been published as a series of books (Knuth
1993a; Knuth 1986). More recently, Knuth has published a collection of graph algorithms in
The Stanford Graphbase (Knuth 1993b). These programs are enjoyable to read and are respec-
tively excellent presentations of modern automatic typesetting and graph algorithms. The website
www.literateprogramming.com
has pointers to many articles about literate programming, literate
programs to download as well as a variety of literate programming systems; many refinements have
been made since Knuth’s original development of the idea.
The only other literate program that we are aware of that has been published as a book is the
implementation of the
lcc
C compiler, which was written by Fraser and Hansen and published as A
Retargetable C Compiler: Design and Implementation (Fraser and Hanson 1995). Say something
nice about this book
This chapter provides a high-level top-down description of
lrt
’s basic archi-

tecture. It starts by explaining more about the literate programming approach and
how to read a literate program. We then briefly describe our coding conventions
before moving forward into the high-level operation of
lrt
, where we describe
what happens during rendering by walking through the process of how
lrt
com-
putes the color at a single point on the image. Along the way we introduce some of
the major classes and interfaces in the system. Subsequent chapters will describe
these and other classes and their methods in detail.
1.1.1 Literate Programming
In the course of the development of the T
E
X typesetting system, Donald Knuth
developed a new programming methodology based on the simple (but revolution-
ary) idea that programs should be written more for people’s consumption than for
computers’ consumption. He named this methodology literate programming. This
book (including the chapter you’re reading now) is a long literate program.
2 Introduction [Ch. 1
Literate programs are written in a meta-language that mixes a document for-
matting language (e.g. L
A
T
E
X or HTML) and a programming language (e.g. C++).
The meta-language compiler then can transform the literate program into either a
document suitable for typesetting (this process is generally called weaving, since
Knuth’s original literate programming environment was called
web

), or into source
code suitable for compilation (so-called tangling, since the resulting source code
is not generally as comprehensible to a human reader than the original literate pro-
gram was).
The literate programming meta-language provides two important features. The
first is a set of mechanisms for mixing English text with source code. This makes
the description of the program just as important as its actual source code, encour-
aging careful design and documentation on the part of the programmer. Second,
the language provides mechanisms for presenting the program code to the reader in
an entirely different order than it is supplied to the compiler. This feature makes it
possible to describe the operation of the program in a very logical manner. Knuth
named his literate programming system
web
since literate programs tend to have
the form of a web: various pieces are defined and inter-related in a variety of ways
such that programs are written in a structure that is neither top-down nor bottom-
up.
As a simple example, consider a function
InitGlobals()
that is responsible for
initializing all of the program’s global variables. If all of the variable initializations
are presented to the reader at once,
InitGlobals()
might be a large collection
of variable assignments the meanings of which are unclear because they do not
appear anywhere near the definition or use of the variables. A reader would need to
search through the rest of the entire program to see where each particular variable
was declared in order to understand the function and the meanings of the values
it assigned to the variables. As far as the human reader is concerned, it would be
better to present the initialization code near the code that actually declares and uses

the global.
In a literate program, then, one can instead write
InitGlobals()
like this:
Function Definitions
void InitGlobals() {
Initialize Global Variables
}
Here we have added text to a fragment called
Function Definitions
. (This frag-
ment will be included in a C++ source code file when the literate program is tan-
gled for the compiler.) The fragment contains the definition of the
InitGlobals()
function. The
InitGlobals()
function itself includes another fragment,
Initialize
Global Variables
. At this point, no text has been added to the initialization frag-
ment. However, when we introduce a new global variable
ErrorCount
somewhere
later in the program, we can now write:
Initialize Global Variables
ErrorCount = 0;
Here we have started to define the contents of
Initialize Global Variables
.
When our literate program is turned into source code suitable for compiling, the

literate programming system will substitute the code
ErrorCount = 0;
inside the
Sec. 1.1] Approaching the System 3
definition of the
InitGlobals()
function. Later on, we may introduce another
global
FragmentsProcessed
, and we can append it to the fragment:
Initialize Global Variables
FragmentsProcessed = 0;
The symbol after the fragment name shows that we have added to a previ-
ously defined fragment. When tangled, the result of the above fragment definitions
is the code:
void InitGlobals() {
ErrorCount = 0;
FragmentsProcessed = 0;
}
By making use of the text substitution that is made easy by fragments, we can
decompose complex functions into logically-distinct parts. This can make their
operation substantially easier to understand. We can write a function as a series of
fragments:
Function Definitions
void func(int x, int y, double *data) {
Check validity of arguments
if (x < y) {
Swap parameter values
}
Do precomputation before loop

Loop through and update data array
}
The text of each fragment is then expanded inline in
func()
for the compiler.
In the document, we can introduce each fragment and its implementation in turn–
these fragments may of course include additional fragments, etc. This style of
decomposition lets us write code in collections of just a handful of lines at a time,
making it easier to understand in detail. Another advantage of this style of pro-
gramming is that by separating the function into logical fragments, each with
a single and well-delineated purpose, each one can then be written and verified
independently–in general, we will try to make each fragment less than ten lines or
so of code, making it easier to understand its operation.
Of course, inline functions could be used to similar effect in a traditional pro-
gramming environment, but using fragments to decompose functions has a few
important advantages. The first is that all of the fragments can immediately refer
to all of the parameters of the original function as well as any function-local vari-
ables that are declared in preceeding fragments; it’s not necessary to pass them all
as parameters, as would need to be done with inline functions. Another advantage
is that one generally names fragments with more descriptive and longer phrases
than one gives to functions; this improves program readability and understandabil-
ity. Because it’s so easy to use fragments to decompose complex functions, one
does more decomposition in practice, leading to clearer code.
In some sense, the literate programming language is just an enhanced macro sub-
stitution language tuned to the task of rearranging program source code provided
4 Introduction [Ch. 1
by the user. The simplicity of the task of this program can belie how different
literate programming is from other ways of structuring software systems.
1.1.2 Coding Conventions
We have written

lrt
in C++. However, we have used a subset of the language,
both to make the code easier to understand, as well as to improve the system’s
portability. In particular, we have avoided multiple inheritance and run-time ex-
ception handling and have used only a subset of C++’s extensive standard library.
Appendix A.1 reviews the parts of the standard library that
lrt
uses in multiple
places; otherwise we will point out and document unusual library routines as they
are used.
Types, objects, functions, and variables are named to indicate their scope; classes
and functions that have global scope all start with capital letters. (The system uses
no global variables.) The names of small utility classes, module-local
static
vari-
ables, and private member functions start with lower-case letters.
We will occasionally omit short sections of
lrt
’s source code from this docu-
ment. For example, when there are a number of cases to be handled, all with nearly
identical code, we will present one case and note that the code for the remaining
cases has been elided from the text.
1.1.3 Code Optimization
As mentioned in the preface, we have tried to make
lrt
efficient by using well-
chosen algorithms rather than by having many low-level optimizations. However,
we have used a profiler to find which parts of it account for most of the execution
time and have performed local optimization of those parts when doing so didn’t
make the code confusing. We kept a handful of basic optimization principles in

mind while doing so:
On current CPU architectures, the slowest mathematical operations are di-
vides, square-roots, and trigonometric functions. Addition, subtraction, and
multiplication are generally ten to fifty times faster than those operations.
Code changes that reduce the number of the slower mathematical operations
can help performance substantially; for example, replacing a series of di-
vides by a value v with the computing the value 1 v and then multiplying by
that value.
Declaring short functions as
inline
can speed up code substantially, both
by removing the run-time overhead of performing a function call (which
may involve saving values in registers to memory) as well as by giving the
compiler larger basic blocks to optimize.
As the speed of CPUs continues to grow more quickly than the speed at
which data can be loaded from main memory into the CPU, waiting for
values from memory is becoming a major performance barrier. Organiz-
ing algorithms and data structures in ways that give good performance from
memory caches can speed up program execution much more than reducing
Sec. 1.2] Rendering and the Ray–Tracing Algorithm 5
the total number of instructions to be executed. Appendix ?? discusses gen-
eral principles for memory-efficient programming; these ideas are mostly
applied in the ray–intersection acceleration structures of Chapter 4 and the
image map representation in Section 11.5.2, though they influence many of
the design decisions throughout the system.
1.1.4 Indexing and Cross-Referencing
There are a number of features of the text designed to make it easier to navigate.
Indices in the page margins give the page number where the functions, variables,
and methods used in the code on that page are defined (if not on the current or
facing page). This makes it easier to refer back to their definitions and descriptions,

especially when the book isn’t read fromt-to-back. Indices at the end of the book
collect all of these identifiers so that it’s possible to find definitions starting from
their names. Another index at the end collects all of the fragments and lists the
page they were defined on and the pages where they were used.
XXX Page number of definition(s) and use in fragments XXX
What it is, why we’re doing it, why you care.
lrt
is written using an plug-in architecture. The
lrt
executable consists of the
core code that drives the main flow of control of the system, but has no imple-
mentation of specific shape or light representations, etc. All of its code is written
in terms of the abstract base classes that define the interfaces to the plug-in types.
At run-time, code modules are loaded to provide the specific implementations of
these base classes needed for the scene being rendered. This method of organiza-
tion makes it easy to extend the system; substantial new functionality can be added
just by writing a new plug-in. We have tried to define the interfaces to the various
plug-in types so that they make it possible to write many interesting and useful ex-
tensions. Of course, it’s impossible to forsee all of the ways that a developer might
want to extend the system, so more far-reaching projects may require modifications
to the core system.
The source code to
lrt
is distributed across a small directory hierarchy. All
of the code for the
lrt
executable is in the
core/
directory.
lrt

supports twelve
different types of plug-ins, summarized in the table in Figure 1.1 which lists the
abstract base classes for the plug-in types, the directory that the implementaitions
of these types that we provide are stored in, and a reference to the section where
each interface is first defined. Low-level details of the routines that load these
modules are discussed in Appendix D.1.
1.3.1 Phases of Execution
lrt
has three main phases of execution. First, it reads in the scene description text
file provided by the user. This file specifies the geometric shapes that make up the
Camera
202
Film
294
Filter
281
Light
478
Material
375
Primitive
130
Sampler
237
Shape
63
SurfaceIntegrator
563
ToneMap
310

VolumeIntegrator
630
VolumeRegion
465
6 Introduction [Ch. 1
Base Class Directory Section
Shape shapes/
3.1
Primitive accelerators/
4.1
Camera cameras/
6.1
Film film/
8.1
Filter filters/
7.7
Sampler samplers/
7.3
ToneMap tonemaps/
8.3
Material materials/
10.2
Light lights/
13.1
SurfaceIntegrator integrators/
16
VolumeIntegrator integrators/
16
VolumeRegion volumes/
12.3

Figure 1.1:
lrt
supports twelve types of plug-in objects that are loaded at runtime
based on which implementations of them are in the scene description file. The
system can be extended with new plug-ins, without needing to be reocmpiled itself.
scene, their material properties, the lights that illuminate them, where the virtual
camera is positioned in the scene, and parameters to all of the other algorithms
that specify the renderer’s basic algorithms. Each statement in the input file has
a direct mapping to one of the routines in Appendix B that comprise the interface
that
lrt
provides to allow the scene to be described. A number of example scenes
are provided in the
examples/
directory in the
lrt
distribution and Appendix C
has a reference guide to the scene description format.
Once the scene has been specified, the main rendering loop begins. This is
the second main phase of execution, and is the one where
lrt
usually spends the
majority of its running time. Most of the chapters in this book describe code that
will execute during this phase. This step is managed by the
Scene::Render()
method, which will be the focus of Section 1.3.3.
lrt
uses ray tracing algorithms
to determine which objects are visible at particular sample points on the image
plane as well as how much light those objects reflect back to the image. Computing

the light arriving at many points on the image plane gives us a representation of the
image of the scene.
Finally, once the second phase has finished computing the image sample contri-
butions, the third phase of execution handles post-processing the image before it is
written to disk (for example, mapping pixel values to the range 0 255 if necessary
for the image file format being used.) Statistics about the various rendering algo-
rithms used by the system are then printed, and the data for the scene description
in memory is de-allocated. The renderer will then resume procesisng statements
from the scene description file until no more remain, allowing the user to specify
another scene to be rendered if desired.
The cornerstone of the techniques used to do this is the ray tracing algorithm.
Ray tracing algorithms take a geometric representation of a scene and a ray, which
can be described by its 3D origin and direction. There are two main tasks that ray
tracing algorithms perform: to determine the first geometric object that is visible
along a determine whether any geometric objects intersect a ray. The first task
Sec. 1.3] System Overview 7
8
Scene
Figure 1.2: Basic ray tracing algorithm: given a ray starting from the image plane,
the first visible object at that point can be found by determining which object first
intersects the ray. Furthermore, visibility tests between a point on a surface and a
light source can also be performed with ray tracing, givng an accurate method for
computing shadows.
is useful for solving the hidden-surface problem; if at each pixel we trace a ray
into the scene to find the closest object hit by a ray starting from that pixel, we
have found the first visible object in the pixel. The second task can be used for
shadow computations: if no other object is between a point in the scene and a point
on a light source, then illumination from the light source at that point reaches the
receiving point; otherwise, it must be in shadow. Figure 1.2 illustrates both of these
ideas.

The ability to quickly perform exact visibility tests between arbitrary points in
the scene, even in complex scenes, opens the door to many sophisticated rendering
algorithms based on these queries. Because ray tracing only requires that a particu-
lar shape representation be able to determine if a ray has intersected it (and if so, at
what distance along the ray the intersection occured), a wide variety of geometric
representations can naturally be used with this approach.
1.3.2 Scene Representation
The
main()
function of the program is in the
core/lrt.cpp
file. It uses the
system-wide header
lrt.h
, which defines widely useful types, classes, and func-
tions, and
api.h
, which defines routines related to processing the scene descrip-
tion.
lrt.cpp*
#include "lrt.h"
#include "api.h"
main program
lrt
’s
main()
function is pretty simple; after calling
lrtInit()
, which does
system-wide initialization, it parses the scene input files specified by the filenames

given as command-line arguments, leading to the creation of a
Scene
object that
holds representations of all of the objects that describe the scene and rendering an
image of the scene. After rendering is done,
lrtCleanup()
does final cleanup
before system exits.
lrtCleanup()
706
lrtInit()
706
8 Introduction [Ch. 1
main program
int main(int argc, char *argv[]) {
Print welcome banner
lrtInit();
Process scene description
lrtCleanup();
return 0;
}
If the user ran
lrt
with no command-line arguments, then the scene description
is read from standard input. Otherwise we loop through the command line argu-
ments, processing each input filename in turn. No other command line arguments
are supported.
Process scene description
if (argc == 1) {
Parse scene from standard input

} else {
Parse scene from input files
}
The
ParseFile()
function parses a text scene description file, either from stan-
dard input or from a file on disk; it returns
false
if it was unable to open the file.
The mechanics of parsing scene description files will not be described in this book
(it is done with straightforward
lex
and
yacc
files.)
Parse scene from standard input
ParseFile("-");
If a particular input file can’t be opened, the
Error()
routine reports this infor-
mation to the user.
Error()
is like the
printf()
function in that it first takes a
format string that can include escape codes like
%s
,
%d
,

%f
, etc., which have values
supplied for them via a variable argument list after the format string.
Parse scene from input files
for (int i = 1; i < argc; i++)
if (!ParseFile(argv[i]))
Error("Couldn’t open scene description file \"%s\"\n",
argv[i]);
As the scene file is parsed, objects are created that represent the camera, lights,
and the geometric primitives in the scene. Along with other objects that manage
other parts of the rendering process, these are all collected together in the
Scene
ob-
ject, which is allocated by the
GraphicsOptions::MakeScene()
method in Sec-
tion B.4. The
Scene
class is declared in
core/scene.h
and defined in
core/scene.cpp
.
Scene Declarations
class Scene {
public:
Scene Public Methods
Scene Data
};
Sec. 1.3] System Overview 9

202
Camera
478
Light
375
Material
130
Primitive
63
Shape
563
SurfaceIntegrator
658
vector
630
VolumeIntegrator
465
VolumeRegion
We don’t include the implementation of the
Scene
constructor here; it mostly
just copies the pointers to these objects that were passed into it.
Each geometric object in the scene is represented by a
Primitive
, which col-
lects a lower-level
Shape
that strictly specifies its geometry, and a
Material
that

describes how light is reflected at points on the surface of the object (e.g. the ob-
ject’s color, whether it has a dull or glossy finish, etc.) All of these geometric
primitives are collected into a single aggregate
Primitive
,
aggregate
, that stores
them ina a 3D data structure that makes ray tracing faster by substantially reducing
the number of unnecessary ray intersection tests.
Scene Data
Primitive *aggregate;
Each light source in the scene is represented by a
Light
object. The shape
of a light and the distribution of light that it emits has a substantial effect on the
illumination it casts into the scene.
lrt
supports a single global light list that holds
all of the lights in the scene using the
vector
class from the standard library. While
some renderers support light lists that are specified per-geometric object, allowing
some lights to illuminate only some of the objects in the scene, this idea doesn’t
map well to the physically-based rendering approach taken in
lrt
, so we only have
this global list.
Scene Data
vector<Light *> lights;
The camera object controls the viewing and lens parameters such as camera

position and orientation and field of view. A
Film
member variable inside the
camera class handles image storage. The
Camera
and classes are described in
Chapter 6 and film is described in Chapter 8. After the image has been computed,
a sequence of imaging operations is applied by the film to make adjustments to the
image before writing it to disk.
Scene Data
Camera *camera;
describe this
Scene Data
VolumeRegion *volumeRegion;
Integrators handle the task of simulating the propagation of light in the scene
from the light sources to the primitives in order to compute how much light arrives
at the film plane at image sample positions. Their name comes from the fact that
their task is to evaluate the value of an integral equation that describes the distri-
bution of light in an environment.
SurfaceIntegrator
s compute reflected light
from geometric surfaces, while
VolumeIntegrator
s handle the scattering from
participating media–particles like fog or smoke in the environment that interact
with light. The properties and distribution of the participating media are described
by
VolumeRegion
objects, which are defined in Chapter 12. Both types of integra-
tors are described and implemented in Chapter 16.

Scene Data
SurfaceIntegrator *surfaceIntegrator;
VolumeIntegrator *volumeIntegrator;

×