lrtuses ray tracing algorithms to determine which objects are visible at particular sample points on the imageplane as well as how much light those objects reflect back to the image.. Co
Trang 1DRAFT (4 November 2003) — Do Not Distribute
Trang 31 Introduction 1
1.2 Rendering and the Ray–Tracing Algorithm 5
Trang 47.5 ***ADV***: Low-Discrepancy Sequences 252
7.6 ***ADV***: Best-Candidate Sampling Patterns 265
8.3 ***ADV***: Perceptual Issues and Tone Mapping 303
Trang 511.1 Texture Interface and Basic Textures 394
12.1 ***ADV***: Volume Scattering Processes 458
12.3 ***ADV***: Volume Interface and Homogeneous Volumes 465
Trang 614.1 Background and Probability Review 498
14.3 The Inversion Method for Sampling Random Variables 503
14.4 Transforming Between Different Distribution Functions 506
14.6 Transformation in Multiple Dimensions 509
14.7 2D Sampling with Multi-Dimensional Transformation 511
16.6 Particle Tracing and Photon Mapping 608
Trang 7Contents vii
Trang 9[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
Trang 11Rendering is a fundamental component of computer graphics At the highest level of tion, rendering describes the process of converting a description of a three-dimensional scene into
abstrac-an image Algorithms for abstrac-animation, geometric modeling, texturing, abstrac-and other areas of computergraphics all must feed their results through some sort of rendering process so that the results of theirwork are made visible in an image Rendering has become ubiquitous; from movies to games andbeyond, 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 problemssuch as determining which objects are visible from a given viewpoint As these problem have beensolved and as richer and more realistic scene descriptions have become available, modern renderinghas 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 Theinterdisciplinary 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 sourcecode for a complete rendering system All of the images in this book, including the ones on the frontand 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 tointroduce ideas in computer science and computer graphics Often, some of the subtleties of an
Trang 12addition of new techniques, yet the trade-offs in this design space are rarely discussed.
lrtand 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 wouldcapture taking a photograph of the scene, or as the task of generating an image that evokes the sameresponse 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 ofthe 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 areasonably 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 exhaustivelycover 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 finiteelement radiosity algorithms), or because we believed that the pedagogical value of explaining thealgorithm 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 readercan follow up on topics that are of interest Many other areas of rendering, such as interactiverendering, 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 puter graphics classes This book assumes existing knowledge of computer graphics at the level
com-of an introductory college-level course, though certain key concepts from such a course will bepresented again here, such as basic vector geometry and transformations For students who do nothave 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 toexplaining 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,
Trang 13Overview and Goals xiii
software developers in industry, and individuals interested in the fun of writing their own renderingsystems 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
lrtalso includes implementations of a number of newer and/or difficult-to-implement algorithmsand 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 Wehope that it will also be useful for this audience to see one way to organize a complete non-trivialrendering system
lrtis 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 oncomputers follow the path of infinitesimal rays of light through the scene up to the first surface thatthey intersect This gives a very basic method for finding the first visible object as seen from anyparticular position and direction It is the basis for many rendering algorithms
lrtwas 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-qualitycommercial 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 quitedifficult to retrofit such functionality to a rendering system after it has been implemented, as thesefeatures can have subtle implications for all components of the system
Our second goal means that we tried to choose algorithms, data structures, and rendering niques with care Since their implementations will be examined by more readers than those in mostrendering 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
tech-a single person to understtech-and completely We htech-ave implemented lrtwith a plug-in architecture,with a core of basic glue that pushes as much functionality as possible out to external modules Theresult is that one doesn’t need to understand all of the various plug-ins in order to understand thebasic structure of the system This makes it easier to delve in deeply to parts of interest and skipothers, without losing sight of how the overall system fits together
There is a tension between the goals of being both complete and illustrative Implementingand describing every useful technique that would be found in a production rendering system wouldnot only make this book extremely long, but it would make the system more complex than mostreaders would be interested in In cases wherelrtlacks such a useful feature, we have attempted
to design the architecture so that feature could be easily added without altering the overall systemdesign Exercises at the end of each chapter suggest programming projects that add new features tothe system
Trang 14based renderer, there is certainly an error in one of them Finally, we believe that this based approach to rendering is valuable because it is rigorous When it is not clear how a particularcomputation 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 manyminutes or hours in the course of generating an image, efficiency is clearly important However, we
physically-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 wedid make some effort to optimize the parts of the system where most of the computation occurs Forthis reason as well as portability, lrtis not presented as a parallel or multi-threaded application,although parallelizinglrtwould not be very difficult
In the course of presenting lrt and discussing its implementation, we hope to convey somehard-learned lessons from some years of rendering research and development There is more towriting a good renderer than stringing together a set of fast algorithms; making the system bothflexible and robust is the hard part The system’s performance must degrade gracefully as moregeometry is added to it, as more light sources are added, or as any of the other axes of complexityare 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 rendererand using it to create an image that couldn’t be generated before is a great pleasure Our mostfundamental 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 youprogress through the book Exercises at the end of each chapter suggest modifications to make tothe system that will help you better understand its inner workings and more complex projects toextend the system to add new features
We have also created a web site to go with this book, located atwww.pharr.org/lrt There youwill find errata and bug fixes, updates tolrt’s source code, additional scenes to render, supplementalutilities, and new plug-in modules If you come across a bug inlrtor an error in this text that isnot listed at the web site, please report it to the e-mail addresslrtbugs@pharr.org
Trang 15
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 TEX ting system was written with this system and has been published as a series of books (Knuth1993a; Knuth 1986) More recently, Knuth has published a collection of graph algorithms in
typeset-The Stanford Graphbase (Knuth 1993b) typeset-These programs are enjoyable to read and are
respec-tively excellent presentations of modern automatic typesetting and graph algorithms The website
www.literateprogramming.comhas pointers to many articles about literate programming, literateprograms to download as well as a variety of literate programming systems; many refinements havebeen 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 theimplementation of thelccC 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
Trang 17
This chapter provides a high-level top-down description of lrt’s basic tecture It starts by explaining more about the literate programming approach andhow to read a literate program We then briefly describe our coding conventionsbefore moving forward into the high-level operation of lrt, where we describewhat happens during rendering by walking through the process of howlrtcom-putes the color at a single point on the image Along the way we introduce some ofthe major classes and interfaces in the system Subsequent chapters will describethese and other classes and their methods in detail
1.1.1 Literate Programming
In the course of the development of the TEX typesetting system, Donald Knuthdeveloped 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
Trang 18first is a set of mechanisms for mixing English text with source code This makesthe 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 itpossible to describe the operation of the program in a very logical manner Knuthnamed his literate programming system websince literate programs tend to havethe form of a web: various pieces are defined and inter-related in a variety of wayssuch that programs are written in a structure that is neither top-down nor bottom-up
As a simple example, consider a functionInitGlobals()that is responsible forinitializing all of the program’s global variables If all of the variable initializationsare presented to the reader at once, InitGlobals() might be a large collection
of variable assignments the meanings of which are unclear because they do notappear anywhere near the definition or use of the variables A reader would need tosearch through the rest of the entire program to see where each particular variablewas 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 bebetter to present the initialization code near the code that actually declares and usesthe global
In a literate program, then, one can instead writeInitGlobals()like this:
frag-function TheInitGlobals()function itself includes another fragment, InitializeGlobal Variables At this point, no text has been added to the initialization frag-ment However, when we introduce a new global variableErrorCountsomewherelater in the program, we can now write:
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, theliterate programming system will substitute the codeErrorCount = 0;inside the
Trang 19Sec 1.1] Approaching the System 3
definition of the InitGlobals() function Later on, we may introduce another
globalFragmentsProcessed, and we can append it to the fragment:
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
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:
void func(int x, int y, double *data) {
if (x < y) {
}
}
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
Trang 20Appendix A.1 reviews the parts of the standard library that lrtuses in multipleplaces; otherwise we will point out and document unusual library routines as theyare used.
Types, objects, functions, and variables are named to indicate their scope; classesand functions that have global scope all start with capital letters (The system uses
no global variables.) The names of small utility classes, module-localstaticables, and private member functions start with lower-case letters
vari-We will occasionally omit short sections oflrt’s source code from this ment For example, when there are a number of cases to be handled, all with nearlyidentical code, we will present one case and note that the code for the remainingcases has been elided from the text
On current CPU architectures, the slowest mathematical operations are vides, square-roots, and trigonometric functions Addition, subtraction, andmultiplication are generally ten to fifty times faster than those operations.Code changes that reduce the number of the slower mathematical operationscan help performance substantially; for example, replacing a series of di-
di-vides by a value v with the computing the value 1
v and then multiplying by
that value
Declaring short functions asinline can speed up code substantially, both
by removing the run-time overhead of performing a function call (whichmay involve saving values in registers to memory) as well as by giving thecompiler larger basic blocks to optimize
As the speed of CPUs continues to grow more quickly than the speed atwhich data can be loaded from main memory into the CPU, waiting forvalues from memory is becoming a major performance barrier Organiz-ing algorithms and data structures in ways that give good performance frommemory caches can speed up program execution much more than reducing
Trang 21Sec 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.
lrtis written using an plug-in architecture Thelrtexecutable 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 funcorganiza-tionality 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 thelrtexecutable is in thecore/directory lrtsupports 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
lrthas 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
Trang 22scene, their material properties, the lights that illuminate them, where the virtualcamera is positioned in the scene, and parameters to all of the other algorithmsthat 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 interfacethatlrtprovides to allow the scene to be described A number of example scenesare provided in theexamples/ directory in the lrtdistribution and Appendix Chas a reference guide to the scene description format
Once the scene has been specified, the main rendering loop begins This isthe second main phase of execution, and is the one wherelrtusually spends themajority of its running time Most of the chapters in this book describe code thatwill execute during this phase This step is managed by the Scene::Render()
method, which will be the focus of Section 1.3.3 lrtuses ray tracing algorithms
to determine which objects are visible at particular sample points on the imageplane as well as how much light those objects reflect back to the image Computingthe light arriving at many points on the image plane gives us a representation of theimage of the scene
Finally, once the second phase has finished computing the image sample butions, the third phase of execution handles post-processing the image before it iswritten to disk (for example, mapping pixel values to the range 0255 if necessaryfor 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
contri-in memory is de-allocated The renderer will then resume procesisng statementsfrom the scene description file until no more remain, allowing the user to specifyanother 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, whichcan be described by its 3D origin and direction There are two main tasks that raytracing algorithms perform: to determine the first geometric object that is visiblealong a determine whether any geometric objects intersect a ray The first task
Trang 23Sec 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, andapi.h, which defines routines related to processing the scene
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 Sceneobject 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
Trang 24lrtCleanup() 706
lrtInit() 706
is read from standard input Otherwise we loop through the command line ments, processing each input filename in turn No other command line argumentsare supported
ParseFile("-");
If a particular input file can’t be opened, theError()routine reports this mation to the user Error()is like the printf() function in that it first takes aformat string that can include escape codes like%s,%d,%f, etc., which have valuessupplied for them via a variable argument list after the format string
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 manageother parts of the rendering process, these are all collected together in theSceneob-ject, which is allocated by theGraphicsOptions::MakeScene() method in Sec-tion B.4 TheSceneclass is declared incore/scene.hand defined incore/scene.cpp
class Scene {public:
};
Trang 25Sec 1.3] System Overview 9
We don’t include the implementation of theScene 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 aPrimitive, which
col-lects a lower-levelShape that strictly specifies its geometry, and aMaterialthat
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 aggregatePrimitive,aggregate, that stores
them ina a 3D data structure that makes ray tracing faster by substantially reducing
the number of unnecessary ray intersection tests
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.lrtsupports a single global light list that holds
all of the lights in the scene using thevectorclass 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 inlrt, so we only have
this global list
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
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 SurfaceIntegrators compute reflected light
from geometric surfaces, while VolumeIntegrators 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
byVolumeRegionobjects, which are defined in Chapter 12 Both types of
integra-tors are described and implemented in Chapter 16
SurfaceIntegrator *surfaceIntegrator;
VolumeIntegrator *volumeIntegrator;
Trang 26Figure 1.3: Class relationships for main rendering loop, which is in the
Scene::Render() method in core/scene.cpp The Sampler provides a quence of sample values, one for each image sample to be taken The Camera
se-turns a sample into a corresponding ray from the film plane and theIntegratorscompute the radiance along that ray arriving at the film The sample and its ra-diance are given to the Film, which stores their contribution in an image Thisprocess repeats until theSamplerhas provided as many samples as are necessary
to generate the final image
The goals of theSampler are subtle, but its implementation can substantiallyaffect the quality of the images that the system generates First, the sampler isrepsonsible for choosing the points on the image plane from which rays are tracedinto the scene to compute final pixel values Second, it is responsible for supplyingsample positions that are used by the integrators in their light transport computa-tions For example, some integrators need to choose sample points on light sources
as part of the process of computing illumination at a point Generating good tributions of samples is an important part of the rendering process and is discussed
dis-in Chapter 7
Sampler *sampler;
1.3.3 Main Rendering Loop
After theScenehas been allocated and initialized, itsRender()method is invoked,starting the second phase oflrt’s execution, the main rendering loop For each of aseries of positions on the image plane, this method uses the camera and the sampler
to generate a ray out into the scene and then uses the integrators to compute thelight arriving along the ray at the image plane This value is passed along to thefilm, which records its contribution Figure 1.3 summarizes the main classes used
in this method and the flow of data among them
Trang 27Sec 1.3] System Overview 11
}
Before rendering starts, this method allocates aSampleobject for theSampler
to use to store sample values for each image sample Because the number and
types of samples that need to be generated for each image sample are partially
dependent on the integrators, Sample constructor takes pointers to them so that
they can inform theSampleobject about their sample needs See Section 7.3.1 for
more information about how integrators request particular sets of samples at this
point
Sample *sample = new Sample(surfaceIntegrator, volumeIntegrator, this);
The only other task to complete before rendering can begin is to call thePreprocess()
methods of the integrators, which gives them an opportunity to do any
scene-dependent precomputation that thay may need to do Because information like the
number of lights in the scene, their power and the geometry of the scene aren’t
known when the integrators are originally created, the Preprocess() method
gives them an opportunity to do final initialization that depends on this
informa-tion For example, thePhotonIntegratorin Section 16.6 uses this opportunity to
create data structures that hold a representation of the distribution of illumination
in the scene
surfaceIntegrator->Preprocess(this);
volumeIntegrator->Preprocess(this);
TheProgressReporterobject tells the user how far through the rendering
pro-cess we are aslrtruns It takes the total number of work steps as a parameter,
so that it knows the total amount of work to be done After its creation, the main
render loop begins Each time through the loopSampler::GetNextSample() is
called and theSampler initializessample with the next image sample value,
re-turningfalse when there are no more samples The fragments in the loop body
find the corresponding camera ray and hand it off to the integrators to compute its
contribution, and finally updating the image with the result
ProgressReporter progress(sampler->TotalSamples(), "Rendering");
while (sampler->GetNextSample(sample)) {
}
Trang 28RayDifferential ray;
Float rayWeight = camera->GenerateRay(*sample, &ray);
In order to get better results from some of the texture functions defined in ter 11, it is useful to determine the rays that theCamerawould generate for samples
Chap-offset one pixel in the x and y direction on the image plane This information will
later allow us to compute how quickly a texture is varying with respect to the pixelspacing when projected onto the image plane, so that we can remove detail from itthat can’t be represented in the image being generated Doing so eliminates a wideclass of image artifacts due to aliasing While theRayclass just holds the originand direction of a single ray,RayDifferential inherits fromRay so that it alsohas those member variables, but it also holds two additional Rays, rxand rytohold these neighbors
the strength of this light is radiance; it is described in detail in Section 5.2 The symbol for radiance is L, thus the name of the method These radiance values are
represented with theSpectrumclass, the abstraction that defines the representation
of general energy distributions by wavelength–in other words, color
In addition to returning the ray’s radiance,Scene::L()sets thealphavariable
passed to it to the alpha value for this ray Alpha is an extra component beyond
color that encodes opacity If the ray hits an opaque object, alpha will be one,indicating that nothing behind the intersection point is visible If the ray passed
Trang 29Sec 1.3] System Overview 13
through something partially transparent, like fog, but never hit an opaque object
alphawill be between zero and one If the ray didn’t hit anything,alpha is zero
Computing alpha values here and storing an alpha value with each pixel can be
useful for a variety of post-processing effects; for example, we can composite a
rendered object on top of a photograph, using the pixels in the image of the
pho-tograph wherever the rendered image’s alpha channel is zero, using the rendered
image where its alpha channel is one, and using a mix of the two for the remaining
pixels
Finally, an assertion checks that the returned spectral radiance value doesn’t
have any floating-point “not a number” components; these are a common
side-effect of bugs in other parts of the system, so it’s helpful to catch them immediately
here
Float alpha;
Spectrum Ls = 0.f;
if (rayWeight > 0.f) Ls = rayWeight * L(ray, sample, &alpha);
After we have the ray’s contribution, we can update the image TheFilm::AddSample()
method updates the pixels in the image given the results from this sample The
de-tails of this process are explained in Section 7.7
camera->film->AddSample(*sample, ray, Ls, alpha);
BSDFs describe material properties at a single point on a surface; they will be
described in more detail later in this section Inlrt, it’s necessary to dynamically
allocate memory to store the BSDFs used to compute the contribution of sample
value here In order to avoid the overhead of calling the system’s memory
allo-cation and freeing routines multiple times for each of them, the BSDF class uses
the MemoryArena class to manage pools of memory for BSDFs Section 10.1.1
describes this in more detail Now that the contribution for this sample has been
computed, it’s necessary to tell theBSDF class that all of the BSDFmemory
allo-cated for the sample we just finished is no longer needed, so that it can be reused
for the next sample
BSDF::FreeAll();
So that it’s easy for various parts oflrtto gather statistics on things that may
be meaningful or interesting to the user, a handful of statistics-tracking classes are
defined in Appendix A.2.3.StatsCounteroverloads the++operator for indicating
that the counter should be incremented TheProgressReporter class indicates
how many steps out of the total have been completed with a row of plus signs
Trang 30vari-First is theScene::Intersect() method, which traces the given ray into thescene and returns a boolean value indication whether it intersected any of theprimitives If so, it returns information about the closest intersection point in the
Intersectionstructure defined in Section 4.1
bool Intersect(const Ray &ray, Intersection *isect) const {return aggregate->Intersect(ray, isect);
}
A closely-related method isScene::IntersectP(), which checks for any tersection along a ray, again returning a boolean result Because it doesn’t returninformation about the geometry at the intersection point and because it doesn’t need
in-to search for the closest intersection, it can be more efficient thanScene::Intersect()
for rays where this additional information isn’t needed
bool IntersectP(const Ray &ray) const {return aggregate->IntersectP(ray);
}
Another useful geometric method, Scene::WorldBound(), returns a 3D boxthat bounds the extent of the geometry in the scene We won’t include its straight-forward implementation here
const BBox &WorldBound() const;
TheScene’s method to compute the radiance along a ray,Scene::L(), uses a
SurfaceIntegratorto compute reflected radiance from the first surface that thegiven ray intersects and stores the result inLs It then uses the volume integrator’s
Trang 31Sec 1.3] System Overview 15
Transmittance()method to compute how much of that light is extinguished
be-tween the point on the surface and the camera due attenuation and scattering of
light by participating media, if any Participating media may also increase light
along the ray; theVolumeIntegrator’s L()method computes how much light is
added along the ray due to volumetric light sources and scattering from particles
in the media Section 16.7 describes the theory of attenuation and scattering from
participating media in detail The net effect of these interactions is returned by this
method
Spectrum Scene::L(const RayDifferential &ray,
const Sample *sample, Float *alpha) const {
Spectrum Ls = surfaceIntegrator->L(this, ray,
It’s also useful to compute the attenuation of a ray in isolation; the Scene’s
Transmittance() method returns the reduction in radiance along the ray due to
participating media
Spectrum Scene::Transmittance(const Ray &ray) const {
return volumeIntegrator->Transmittance(this, ray,
NULL, NULL);
}
1.3.5 Whitted Integrator
Chapter 16 has the implementations of many different surface and volume
integra-tors, giving differing levels of accuracy using a variety of algorithms to compute
the results Here we will present a classic surface integrator based on Whitted’s
ray tracing algorithm This integrator accurately computes reflected and
transmit-ted light from specular surfaces like glass, mirrors, and water, though it doesn’t
account for indirect lighting effects The more complex integrators later in the
book build on the ideas in this integrator to implement more sophisticated light
Trang 32is itself a Primitive) The accelerator will perform ray–primitive intersectiontests with the Primitives that the ray potentially intersects, and these will lead
to the Shape::Intersect() routines for the corresponding shapes Once the
Intersection is returned to the integrator, it gets the material properties at theintersection point in the form of aBSDFand uses theLights in theSceneto deter-mine the illumination there This gives the information needed to compute reflectedradiance at the intersection point back along the ray
Spectrum WhittedIntegrator::L(const Scene *scene,
const RayDifferential &ray, const Sample *sample,Float *alpha) const {
}return L;
}
For the integrator to determine what primitive is hit by a ray, it calls theScene::Intersect()
Trang 33Sec 1.3] System Overview 17
370 BSDF
16 WhittedIntegrator
method,
If the ray passed to the integrator’sL()method intersects a geometric primitive,
the reflected radiance is given by the sum of directly emitted radiance from the
object if it is itself emissive, and the reflected radiance due to reflection of light
from other primitives and light sources that arrives at the intersection point This
idea is formalized by the equation below, which says that outgoing radiance from
a point p in direction ωo, Lo pωo , is the sum of emitted radiance at that point
in that direction, Le pωo, plus the incident radiance from all directions on the
sphere S2 around p scaled by a function that describes how the surface scatters
light from the incident directionωito the outgoing directionωo, f pωoωi , and
a cosine term We will show a more complete derivation of this equation later, in
Sections 5.4.1 and 16.2
S2Li pωi f pωoωicosθidωi
Solving this integral analytically is in general not possible for anything other
than the simplest of scenes, so integrators must either make simplifying
assump-tions or use numerical integration techniques TheWhittedIntegrator ignores
incoming light from most of the directions and only evaluates Li pωi for the
di-rections to light sources and for the didi-rections of specular reflection and refraction
Thus, it turns the integral into a sum over a small number of directions
The Whitted integrator works by recursively evaluating radiance along reflected
and refracted ray directions We keep track of the depth of recursion in the
vari-ablerayDepthand after a predetermined recursion depth,maxDepth, we stop
trac-ing reflected and refracted rays By default the maximum recursion depth is five
Otherwise, in a scene like a box where all of the walls were mirrors, the
recur-sion might never terminate These member variables are initialized in the trivial
WhittedIntegratorconstructor, which we will not include in the text
int maxDepth;
mutable int rayDepth;
The Compute emitted and reflected light at ray intersection point fragment is
the heart of the Whitted integrator
if (rayDepth++ < maxDepth) {
}
rayDepth;
To compute reflected light, the integrator must have a representation of the local
light scattering properties of the surface at the intersection point as well as a way
to determine the distribution of illumination arriving at that point
To represent the scattering properties at a point on a surface, lrtuses a class
called BSDF, which stands for “Bidirectional Scattering Distribution Function”
Trang 34Figure 1.5: Basic setting for the Whitted integrator: p is the ray intersection point
and n is the surface normal there The direction in which we’d like to compute
reflected radiance is ωo; its is the vector pointing in the opposite direction of theray,-ray.d
These functions take an incoming direction and an outgoing direction and return avalue that indicates the amount of light that is reflected from the incoming direc-tion to the outgoing direction (actually, BSDF’s usually vary as a function of thewavelength of light, so they really return aSpectrum) lrtprovides built-inBSDF
classes for several standard scattering functions used in computer graphics ples of BSDFs include Lambertian reflection and the Torrance-Sparrow microfacetmodel; these and other BSDFs are implemented in Chapter 9
Exam-TheBSDFat a surface point provides all information needed to shade that point,but BSDFs may vary across a surface Surfaces with complex material properties,such as wood or marble, have a different BSDF at each point Even if wood ismodelled as perfectly diffuse, for example, the diffuse color at each point willdepend on the wood’s grain These spatial variations of shading parameters aredescribed with Textures, which in turn may be described procedurally or stored
in image maps; see Chapter 11
The Intersection::GetBSDF() method returns a pointer to the BSDF at theintersection point on the object
BSDF *bsdf = isect.GetBSDF(ray);
There are a few quantities that we’ll make use of repeatedly in the fragments tocome Figure 1.5 illustrates them p the world-space position of the ray–primitive
intersection and n is the surface normal at the intersection point The normalized
direction from the hit point back to the ray origin is stored inwo; becauseCamerasare responsible for normalizing the direction component of the rays they generate,there’s no need to re-noralize it here (Normalized directions inlrtare generallydenoted by theωsymbol, sowois a shorthand we will commonly use forωo, theoutgoing direction of scattered light.)
const Point &p = bsdf->dgShading.p;
const Normal &n = bsdf->dgShading.nn;
Vector wo = -ray.d;
Trang 35Sec 1.3] System Overview 19
If the ray happened to hit geometry that is itself emissive, we compute its emitted
radiance by calling theIntersection’sLe()method This gives us the first term
of the outgoing radiance equation above If the object is not emissive, this method
will return a black spectrum
L += isect.Le(wo);
For each light, the integrator computes the amount of illumination falling on the
surface at the point being shaded by calling the light’s dE() method, passing it
the position and surface normal for the point on the surface E is the symbol for
the physical quantity irradiance, and differential irradiance, dE, is the appropriate
measure of incident illumination here–radiometric concepts such as energy and
differential irradiance are discussed in Chapter 5 This method also returns the
direction vector from the point being shaded to the light source, which is stored in
the variablewi
TheLight::dE()method also returns aVisibilityTester object, which is a
closure representing additional computation to be done to determine if any
prim-itives block the light from the light source Specifically, theSpectrum that is
re-turned from Light::dE() doesn’t account for any other objects blocking light
between the light source and the surface To verify that there are no such
occlud-ers, a shadow ray must be traced between the point being shaded and the point on
the light to verify that the path is clear Because ray tracing is relatively expensive,
we would like to defer tracing the ray until we are sure that the BSDF indicates that
some of the light from the direction ωo will be scattered in the directionωo For
example, if the surface isn’t transmissive, then light arriving at the back side of the
surface doesn’t contribute to reflection TheVisibilityTester encapsulates the
state needed to record which ray needs to be traced to do this check (In a similar
manner, the attenuation along the ray to the light source due to participating media
is ignored until explicitly evaluated via theTransmittance()method.)
To evaluate the contribution to the reflection due to the light, the integrator
mul-tipliesdEby the value that the BSDF returns for the fraction of light that is scattered
from the light direction to the outgoing direction along the ray This represents this
light’s contribution to the reflected light in the integral over incoming directions,
which is added to the total of reflected radiance stored inL After all lights have
been considered, the integrator has computed total reflection due to direct lighting:
light that arrives at the surface directly from emissive objects (as opposed to light
that has reflected off other objects in the scene before arriving at the point.)
Spectrum f = bsdf->f(wo, wi);
if (!f.Black() && visibility.Unoccluded(scene))
L += f * dE * visibility.Transmittance(scene);
}
Trang 36in the reflected and refracted directions and the returned radiance values are scaled
by the value of the surface’s BSDF and added to the radiance scattered from theoriginal point
TheBSDFhas a method that returns an incident ray direction for a given outgoingdirection and a given mode of light scattering at a surface Here, we are only inter-ested in perfect specular reflection and transmission, so we use theBSDF *flags to
BSDF::Sample f()to indicate that glossy and diffuse reflection should be ignoredhere Thus, the two calls toSample f() below check for specular reflection andtransmission and initializewiwith the appropriate direction and return the BSDF’svalue for the directions ωoωi If the value of the BSDF is non-zero, the inte-grator calls theScene’s radiance function L()to get the incoming radiance alongthe ray, which leads to a call back to theWhittedIntegrator’sL()method Bycontinuing this process recursively multiple reflection and refraction are accountedfor
One important detail in this process is how ray differentials for the reflected andtransmitted rays are found; just as having an approximation to the screen-space area
of a directly-visible object is cruicial for anti-aliasing textures on the object, if wecan approximate the screen-space area of objects that are seen through reflection
or refraction, we can reduce aliasing in their textures as well The fragments thatimplement the computations to find the ray differentials for these rays are described
in Section 10.2.2
To compute the cosine term of the reflection integral, the integrator calls the
Dot()function, which returns the dot product between two vectors If the vectorsare normalized, as bothwiand nare here, this is equal to the cosine of the anglebetween them
Trang 37Sec 1.4] How To Proceed Through This Book 21
Spectrum f = bsdf->Sample_f(wo, &wi,
BxDFType(BSDF_REFLECTION | BSDF_SPECULAR));
if (!f.Black()) {
L += scene->L(rd, sample) * f * AbsDot(wi, n);
}
f = bsdf->Sample_f(wo, &wi,
BxDFType(BSDF_TRANSMISSION | BSDF_SPECULAR));
if (!f.Black()) {
L += scene->L(rd, sample) * f * AbsDot(wi, n);
We have written this text assuming it will be read in roughly front-to-back order
We have tried to minimize the number of forward references to ideas and interfaces
that haven’t yet been introduced, but assume that the reader is acquianted with the
content before any particular point in the text Because of the modular nature
of the system, the most improtant thing to be able to understand an individual
section of code is that the reader be familiar with the low-level classes likePoint,
Ray, Spectrum, etc., the interfaces defined by the abstract base classes listed in
Figure 1.1, and the main rendering loop inScene::Render()
Given that knowledge, for example, the reader who doesn’t care about precisely
how a camera model based on a perspective projection matrix maps samples to
rays can skip over the implementation of that camera and can just remember that
theCamera::GenerateRay() method somehow turns aSampleinto aRay
Fur-thermore, some sections go into depth about advanced topics that some readers
may wish to skip over (particularly on a first reading); these sections are denoted
by an asterisk
The book is divdided into four main sections of a few chapters each First,
chapters two through four define the main geometric functinoality in the system
Chapter two has the low-level classes likePoint,Ray, andBBox; chapter three
de-fines theShape interface, has implementations of a number of shapes, and shows
how to perform ray–shape intersection tests; and chapter four has the
implemen-tations of the acceleration structures for speeding up ray tracing by avoiding tests
with primitives that a ray can be shown to definitely not intersect
The second main section covers the image formation process First, chapter five
introduces the physical units used to measure light and the Spectrum class that
represents wavelength-varying distributions (i.e color) Chapter six defines the
Camerainterface and has a few different camera implementations TheSampler
classes that place samples on the image plane are the topic of chapter seven and
Trang 38imple-
In a seminal early paper, Arthur Appel first described the basic idea of ray ing to solve the hidden surface problem and to compute shadows in polygonalscenes (Appel 1968) Goldstein and Nagle later showed how ray tracing could be
trac-used to render scenes with quadric surfaces (Goldstein and Nagel 1971) (XXX first direct rendering of curved surfaces? XXX) Kay and Greenberg described
a ray tracing approach to rendering transparency (Kay and Greenberg 1979), andWhitted’s seminal CACM paper described the general recursive ray tracing algo-rithm we have outlined in this chapter, accurately simulating reflection and refrac-tion from specular surfaces and shadows from point light sources (Whitted 1980).Notable books on physically-based rendering and image synthesis include Co-
hen and Wallace’s Radiosity and Realistic Image Synthesis (Cohen and Wallace 1993) and Sillion and Puech’s Radiosity and Global Illumination (Sillion and Puech
1994) which primarily describe the finite-element radiosity method; Glassner’s
Principles of Digital Image Synthesis (Glassner 1995), an encyclopediac two-volume
summary of theoretical foundations for realistic rendering; and Illumination and
Color in Computer Generated Imagery (Hall 1989), one of the first books to present
rendering in a physically-based framework XXX Advanced Globillum Book XXX
Many papers have been written that describe the design and implementation ofother rendering systems One type of renderer that has been written about is ren-derers for entertainment and artistic applications The REYES architecture, whichforms the basis for Pixar’s RenderMan renderer, was first described by Cook et
al (Cook, Carpenter, and Catmull 1987); a number of improvements to the originalalgorithm are summarized in (Apodaca and Gritz 2000) Gritz and Hahn describe
Trang 39Additional Reading 23
the BMRT ray tracer (Gritz and Hahn 1996), though mostly focus on the details
of implementing a ray tracer that supports the RenderMan interface The renderer
in the Maya modeling and animation system is described by Sung et al (Sung,
Craighead, Wang, Bakshi, Pearce, and Woo 1998)
Kirk and Arvo’s paper on ray tracing system design was the first to suggest
many design principles that have now become classic in renderer design (Kirk and
Arvo 1988) The renderer was implemented as a core kernel that encapsulated the
basic rendering algorithms and interacted with primitives and shading routines via
a carefully-constructed object-oriented interface This approach made it easy to
extend the system with new primitives and acceleration methods
The Introduction to Ray Tracing book, which describes the state-of-the-art in
ray tracing in 1989, has a chapter by Heckbert that sketches the design of a basic
ray tracer (?) Finally, Shirley’s recent book gives an excellent introduction to ray
tracing and includes the complete source code to a basic ray tracer XXX cite XXX
Researchers at Cornell university have developed a rendering testbed over many
years; its overall structure is described by Trumbore et al (Trumbore, Lytle, and
Greenbert 1993) Its predecessor was described by Hall and Greenberg (Hall and
Greenberg 1983) This system is a loosely-coupled set of modules and libraries,
each designed to handle a single task (ray–object intersection acceleration, image
storage, etc), and written in a way that makes it easy to combine appropriate
mod-ules together to investigate and develop new rendering algorithms This testbed has
been quite successful, serving as the foundation for much of the rendering research
done at Cornell
Another category of renderer focuses on physically-based rendering, like lrt
One of the first renderers based fundamentally on physical quantities is Radiance,
which has been used widely in lighting simulation applications Ward describes its
design and history in a paper and a book (Ward 1994b; Larson and Shakespeare
1998) Radiance is designed in the Unix style, as a set of interacting programs, each
handling a different part of the rendering process (This type of rendering
architec-ture, interacting separate programs, was first described by Duff (Duff 1985).)
Glassner’s Spectrum rendering architecture also focuses on physically-based
rendering (Glassner 1993), appraoched through a signal-processing based
formu-lation of the problem It is an extensible system built with a plug-in architecture;
lrt’s approach of using parameter/value lists for initializing plug-in objects is
sim-ilar to Spectrum’s One notable feature of Spectrum is that all parameters that
de-scribe the scene can be animated in a variety of ways
Slusallek and Seidel describe the architecture of the Vision rendering system,
which is also physically based and was designed to be extensible to support a wide
variety of light transport algorithms (Slusallek and Siedel 1995; Slusallek and
Sei-del 1996; Slusallek 1996) In particular, it has the ambitious goal of supporting
both Monte Carlo and finite-element based light transport algorithms Because
lrtwas designed with the fundamental expectation that Monte Carlo algorithms
would be used, its design could be substantially more straightforward
The RenderPark rendering system also supports a variety of physically-based
rendeirng algorithms, including both Monte Carlo and finite element approaches
It was developed by by Philippe Bekaert, Frank Suykens de Laet, Pieter Peers, and
Vincent Masselus, and is available fromhttp://www.cs.kuleuven.ac.be/cwis/research/graphics/RENDERPARK/.The source code to a number of other ray tracers and renderers is available on
Trang 40brary is the third edition of Stroustroup’s The C++ Programming Language(Stroustrup
of lrtwith debugging symbols and set up your debugger to run lrtwith
the XXXX.lrt scene Set a breakpoint in theScene::Render()method andtrace through the process of how a ray is generated, how its radiance value
is computed, and how its contribution is added to the image
As you gain more understanding of how the details of the system work, turn to this and more carefully trace through particular parts of the process
... Sample(surfaceIntegrator, volumeIntegrator, this);The only other task to complete before rendering can begin is to call thePreprocess()
methods of the integrators, which gives them an opportunity to. .. film to make adjustments to the
image before writing it to disk
Integrators handle the task of simulating the propagation of light in the scene
from the light sources to the... directions,
which is added to the total of reflected radiance stored inL After all lights have
been considered, the integrator has computed total reflection due to direct lighting: