Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Advanced_Renderman_Book[torrents.ru]

.pdf
Скачиваний:
1714
Добавлен:
30.05.2015
Размер:
38.84 Mб
Скачать

4 Geometric Primitives

Figure 4.5 Polygon mesh and subdivision mesh renderings of the same object.

This gives subdivision meshes the ability to model fillets, rounds, and blends, and to do other modeling tasks often associated with trim curves.

These enhancements are specified by component tags. A component of a subdivision mesh is either a face, a vertex, or a chain of edges. Components of the subdivision mesh may be tagged to have various user-defined properties. Each tag has four parts: an identifying name, a pair of numbers that identify the number of integer and floating-point arguments to the tag (some tags have fixed numbers of arguments, others have variable-length argument lists), followed by the zero or more actual integer arguments, and the zero or more floating-point arguments. Several tags are currently defined, and more may be added over time.

SubdivisionMesh scheme nverts vertids tags nargs intargs floatargs parameterlist

The string parameter scheme specifies the mathematical subdivision scheme to be used by the primitive. There are several such schemes defined in the literature, but currently PRMan only implements "catmull -clark". The subdivision mesh itself is made up of facets, very similar to those in Poi ntsPol ygons, with an integer nverts array that contains the number of vertices in each face and an integer vertids array containing the index of each vertex.

The string array tags contains the list of tags that are associated with this mesh. The number of integer and floating-point arguments provided for each tag is listed as a pair in the integer nargs array. The intargs and floatargs arrays contain the actual integer and floating-point argument data for the tags. The currently defined tags include:

"hole": specifies that certain faces are holes. This tag has one integer argument for each face that is a hole and no floating-point arguments.

"corner": specifies that certain vertices are semisharp corners. This tag has one integer argument for each vertex that is a semisharp corner and equally many floating-point arguments specifying a sharpness at each such corner.

4.7

Reference Geometry

The sharpness of a vertex (or of an edge) ranges from completely smooth at 0.0 to essentially infinitely sharp (discontinuous) at values exceeding 10.0.

o "crease": specifies that a certain chain of edges should be a crease. This tag has any number of integer arguments, listing the vertices that make up the chain of edges, and one floating-point argument, specifying a sharpness for the edges in the crease. There may be multiple "crease" tags on a subdivision mesh that form independent creases on the primitive. It is an error to specify two sequential vertices in an edge chain that do not form an edge of one or more of the mesh faces.

o "interpol ateboundary": specifies that the subdivision mesh should interpolate all boundary faces to their edges. This tag has no integer arguments and no floating-point arguments.

The parameterlist must include at least position ("P") information. As with polygons, subdivision mesh primitive variables of type "vertex" and "varying" occur with the same frequency-one per mesh vertex. Primitive variables of type "uniform" happen once per face. Naturally, primitive variables of type "constant" appear exactly once.

Any subdivision face that is a quadrilateral, and whose four corners each touch four faces (as if it were a vertex in a standard rectangular patch mesh) is, in fact, equivalent to a B-spline patch. However, despite this, subdivision meshes are not parametric surfaces. After a single subdivision step, every face that is not already quadrilateral becomes a set of quadrilateral subfaces. Each of these subfaces then has a locally consistent internal biparametric space. However, there is no guaranteed parametric continuity across faces, so the (u, v) parameters seen by shaders can change abruptly across geometrically smooth face edges.

4.7Reference Geometry

Every geometric primitive (except Procedural, of course) has a parameter list with which it can identify primitive variables that are interesting to the shaders attached to it. The most obvious examples of primitive variables, such as per-vertex color ("Cs") or per-vertex normals ("N"), have special meaning to the shaders because they provide data for the Shading Language's global variables that are used to override the default values of those variables. But the system was designed to be extensible by the modeler, to add new types of data that the renderer did not already know about. And while it might seem like modeler-extensible variables are "interesting," perhaps it is not at all clear exactly what they are for. One of the best examples is the concept of reference geometry.

Imagine this situation: A character in the scene is modeled using patches or subdivision surfaces with many skin and clothing control points, subtly placed, to get a complex, interesting, continuous surface geometry. The shaders for the character

4 Geometric Primitives

are also relatively complex and use significant amounts of texture mapping to get color, surface properties, displacements, dirt, and other features into the image. Some textures might be 2D parametric maps, others 3D solid procedural textures or 3D projected texture maps.

Then the character animates. As the character's skeleton moves, the skin and clothing move to conform. As the skin and clothing control points are animated, these surfaces stretch and fold, in order to appear natural. This type of controlpoint animation is sometimes called deformation. Notice, however, that the texture mapping, which is dependent on either the 3D position of the surface or the 2D parametric lengths of the patches, will change. Unless the animation is done extremely carefully, and sometimes even then, parametric textures will stretch in unnatural ways as the surface moves. 3D textures have no chance of staying put, because they depend on the positions of the vertices in object (or perhaps shader) space, and the control points are moving in those spaces. What is needed is a fixed, unmoving frame of reference, that can be used as the parameters for the texture mapping, regardless of how the character deforms. This is exactly what reference geometry provides.

Reference geometry is implemented through the use of primitive variables. We define a primitive variable, often called "Pref", as a "vertex point". This means that it has exactly the same type as "P". For every value of "P" on every primitive, there will be a corresponding "Pref". We create a static version of the model in some reference pose, and those same control-point positions will be given as the "Pref" data on every frame of the animation. Shading proceeds as normal, except that it uses "Pref" as the source of texture coordinates instead of using "P". This way, the shading on every frame operates on the same data, and the textures will appear the same on every frame. The texture will stick to the character.

The advantage of using a "vertex" point instead of a "varyi ng" point in this application is that "vertex" points interpolate identically to the position (using cubic or nonrational or subdivision math, as appropriate). In this way they track the surface in the way that people expect that something embedded in the surface would track. "varying" points are interpolated bilinearly in parametric space, which in this application is like a polygonal approximation of the surface. The texture wavers as the bilinear approximation deviates from the true surface.

Of course, on a NURBS or other primitive where the vertices are defined by homogeneous point data "Pw", the reference position will also want to be of type "vertex hpoint", rather than "vertex point", so that they undergo the same homogeneous transformations that the vertices undergo.

4.8Constructive Solid Geometry

Some modelers use a technique for modeling objects known as constructive solid geometry (CSG). In the CSG modeling paradigm, all objects are solid-they have

4.8

Constructive Solid Geometry

Figure 4.6 The three basic CSG operations: union, difference, and intersection.

volume as opposed to being shells that surround volume. Specifically, CSG categorizes a certain region of space as being "inside" the solid, and the rest of space is "outside," by the obvious definition. Complex solid objects are created as Boolean combinations of simpler solid objects. Boolean operators define the new insides and outsides by combining volumes. For example, a pipe might be defined by subtracting a thin solid cylinder from a slightly thicker solid cylinder. CSG is often used in mechanical modeling because of its obvious relationship with manufacturing processes with which real-world parts are constructed.

CSG modeling systems have several Boolean operators that work on solid objects, the same way that mathematical set operators work on Venn diagrams. Solid objects can be fused together with the "union" operator. One can be cut out of another with the "difference" operator. The "intersection" operator is a spatial AND operation. These operators are illustrated in Figure 4.6. Complex CSG objects-say, an internal combustion engine-are created by complex hierarchical trees of Boolean CSG operations.

In RenderMan, CSG trees are defined by using a special form of attribute hierarchy that identifies the Boolean operator to be applied at each node of the tree and specifically identifies the leaf nodes. The leaf node is a set of geometric primitives that fit together to define the boundary of a single simple solid object.

Sol idBegin operation

SolidBegin starts a solid block, which is a node in a CSG tree. The parameter operation is a string that defines the relationship among the children of this node. Four CSG operations are defined:

o "primitive": defines a CSG tree leaf node. All primitives inside this node define a single solid object

o "union": points are inside the result if they are inside any of the children

o"difference": points are inside the result if they are inside the first child and are outside all of the rest of the children

4 Geometric Primitives

o "intersection": points are inside the result if they are inside all of the children RenderMan CSG trees can have any fan-out (that is, a node can have any number of children nodes). For the difference operator, the second and subsequent children are subtracted from the first child. For other operators, the order of the children is not important.

SolidEnd

Solid End ends a solid block and closes that node in the CSG tree.

It is extremely important that every "primitive" (leaf) object in the Boolean hierarchy be "closed." That is, it has a definite inside and definite outside, and there are no "holes" in the definition that make it ambiguous whether certain points are in the interior or in the exterior. In RenderMan, few individual geometric primitives have this property of closure, so RenderMan allows a CSG primitive to be created out of a set of geometric primitives, which, when taken together, create a closed region.

It is important to notice that, even though CSG operates on "solid objects," and in theory exposes the inner mass of these objects by cutting their surfaces away with other objects, the reality is that there are no inner masses to expose. After all, RenderMan primitives are thin sheets, which means that they are just the shells that surround some empty space. Instead, the surface of the "exposed cutaway" is actually just a section of the cutting object. In the pipe example described earlier, the inner surface of the pipe is not the exposed interior of the large cylinder, but is actually the inside-out surface of the smaller cylinder. In fact, every point on the final surface of a CSG object is actually some part of one of the original leaf geometric primitives. RenderMan renders CSG primitives so that the visible attributes of every point on the final object are the attributes of the geometric primitive that caused that surface. This can be confusing when complex CSG trees are involved, but there is interesting descriptive power in making the cutting and the cut objects have different appearance parameters, so that the holes appear to have different properties than the bulk exterior. For example, the cutting object can be made of metal to represent the inside of a block of steel, while the cut object is given the properties of a painted coating.

Handling Complexity

in Photorealistic

Scenes

One defining characteristic of photorealistic images is extreme visual complexity. The real world is a very complex and visually interesting place, and the photographs that record it contain the subtleties that help you recognize it as real. Computer graphics simulations of that world need to be of equal visual complexity, or it will be patently obvious to the most casual observer that they are not real. This visual complexity can arise from three sources: complicated geometric models, detailed surface material properties, and subtle lighting calculations. A RenderMan scene created for feature-film special effects usually has all three of these. Later chapters will examine shading and lighting. This chapter will examine the problems we encounter creating scene databases with enormous amounts of geometry and will provide a few solutions.

116 5 Handling Complexity in Photorealistic Scenes

5.1 Procedural Primitives

RenderMan procedural primitives are user-defined extensions to the scene description capability of the RenderMan Interface. They provide a simple but powerful mechanism for the modeler to state that a piece of geometry exists at a particular position in the scene, without immediately specifying the complete dataset for that object.

The main advantage that this presents is to support data amplification. The idea behind data amplification is that a simple data description acts as input to a process that creates large amounts of data. That output data might be partially random or completely deterministic. However, it is generally most interesting when the process that creates the output data does so late in the game, with some cognizance of the amount of data that is necessary to fulfill the purpose. In other words, it tailors its quantity of output to the specific need. This makes data amplification more powerful than simply accessing a gigantic pregenerated dataset.

The classic example of a procedural primitive is a fractal mountain. A procedure takes as arguments a triangle and a few simple parameters and recursively subdivides the triangle into smaller and smaller triangles with perturbed vertex positions and orientations, eventually generating a mountain range. Importantly, because the procedure creates objects that are self-similar, this process only needs to run until the individual triangles are too small to be seen. Below the threshold of visual acuity, there is no benefit to creating more data. The process therefore runs for a short time and produces a compact dataset when the mountain is at the horizon in the background of a scene and runs for a long time generating very complex data when we are on the mountain examining the ground at our feet. Figure 5.1 shows how the visible size on-screen can be used to control this data generation process.

From the modeler's point of view, there are two great advantages to using procedural primitives. First, the modeler can create whole objects or simple geometric primitives that the renderer does not already understand and effectively extend the renderer. RenderMan doesn't have a built-in notion of a Mountain primitive, but with a small bit of coding, one can be created in this way. Second, the modeler can hide a tremendous amount of geometric complexity behind a simple data structure, using data amplification to create exactly the right amount of data at the right time, rather than precomputing a huge and potentially overspecified dataset during the original scene-description generation.

When trying to render complex scenes for feature-film special effects or similar projects, this second advantage is the more important one. The complete scene description for such scenes often includes large-scale sets, large numbers of highly detailed objects, and great depth complexity, not all of which are actually onscreen and visible in any given frame. Delaying the generation and loading of these largescale models until the renderer has determined that they are visible saves enormous amounts of CPU time, memory, and disk space. The initial placeholder descriptions are extremely small but provide enough data to determine if the model is necessary or not and even give an estimate as to the size of the model on-screen. This way,

5.1Procedural Primitives

Figure 5.1 A procedural mountain primitive can generate more data when the on-screen view is large than when it is small, speeding the rendering while still providing enough visual detail in the image.

model generation is delayed until the object is confirmed as necessary and the required amount of detail is known.

5.1.1The C Procedural Interface

In the C API, RenderMan procedural primitives are implemented by having the modeling application provide subroutines that manipulate a private data structure that the renderer knows nothing about. These data contain geometry descriptions that are manipulated solely by the procedural primitive subroutines. The procedural primitive subroutines do whatever they want to that data (often following the lead of the other primitives in the renderer and recursively subdividing it into smaller, simpler primitives of the same type), the only caveat being that ultimately it must be converted into normal RenderMan API calls.

There are two entry points for each procedural primitive, the subdivide method and the free method. Each instance of a procedural primitive contains three important pieces of data: pointers to the appropriate methods, a blind (to the renderer) data pointer that points at data allocated by (and meaningful to) the methods, and a bounding box that completely contains any geometry of the primitive.

When the renderer reaches the bounding box of a particular instance of a procedural primitive, it calls the subdivide method, passing in the blind data pointer and a floating-point number that indicates the number of pixels that the bounding box covers (called the detail size). The subdivide method splits the primitive into

detailsize ) o void free

5 Handling Complexity in Photorealistic Scenes

smaller primitives. It can either generate standard RenderMan primitives, or it can generate more instances of procedural primitives with their own blind data pointers and (presumably smaller) bounding boxes, or some combination.

The renderer is permitted to subdivide the same blind pointer multiple times if it needs to (for example, if it had to discard the results and then needs them back again). The subdivision method should return identical results each time, or the renderer could become extremely confused. Or it may never call subdivide on that blind pointer, if it recognizes that the data are irrelevant to the current rendering. In any case, at some point the renderer will know that it will never need a particular blind pointer again. It will then call the free method to destroy that blind pointer. This signals the procedural primitive that it may release any data structure that the blind pointer refers to.

Because procedural primitives need their methods in order to function, they were originally only defined for the C version of the API. An application program that contained such procedural primitive methods would need to be linked with a linkable library version of the renderer in order to use them. The original C version of this API is as follows:

Ri Procedural( RtPoi nter blinddata, RtBound boundingbox, void ('subdivide ) ( ), void (free) ( ) ) ;

This RenderMan C API call creates a procedural primitive. The pointer blinddata contains any data that the modeler knows "describe" the procedural primitive. The renderer does not examine these data, but only gives them to the subdivide method when the renderer discovers (by virtue of the boundingbox) that it needs to know the details of the geometry inside the procedural primitive. When the renderer has determined that the primitive is no longer needed (because it has been subdivided, culled, or for any other reason), it will call the free method on the blinddata pointer.

The prototypes for the method calls are:

o void subdivide ( RtPointer blinddata, RtFloat

( RtPoi nter blinddata )

detailsize is the approximate size in pixels of the projection of the bounding box onto the screen, or infinity (1.060) if that size cannot be determined for any reason.

The limitation of using procedural primitives only from the C API made procedural primitives mostly uninteresting. To restore their utility and unleash their power, modern RenderMan renderers now provide three built-in procedural primitives that can be used from the stand-alone renderer executable via simple RIB directives. These are

Delayed Read-Archive: read a RIB file as late as possible

Run Program: run a program to generate the subdivided data

Dynamic Load: load object code that contains the required subroutines

Procedural Primitives

5.1.2Delayed Read-Archive

The simplest of the new RIB procedural primitives is delayed read-archive. The existing interface for "including" one RIB file into another is ReadArchive. Delayed read-archive operates exactly like ReadArchive except that the reading is delayed until the procedural primitive bounding box is reached, unlike ReadArchive, which reads RIB files immediately during parsing. The advantage of the new interface is that because the reading is delayed, memory for the read primitives is not used until the bounding box is actually reached. In addition, if the bounding box proves to be offscreen, the parsing time of the entire RIB file is saved. The disadvantage is that an accurate bounding box for the contents of the RIB file is now required.

The RIB syntax for the delayed read-archive primitive is Procedural "Del ayedReadArchive" filename boundingbox

The parameter filename is a string array with a single element, the name of a partial RIB file. The file can contain any amount of valid RIB, although it is suggested that it either be "flat" (have no hierarchy) or have some balanced hierarchy (matching Begin-End calls). As with all RIB parameters that are bounding boxes, the boundingbox is an array of six floating-point numbers, which are xmin, xmax, ymin, ymax, zmin, zmax in the current object space.

5.1.3Run Program

A more dynamic method of generating procedural primitives is to call a helper program that generates geometry on-the-fly in response to procedural primitive requests in the RIB stream. As will be seen, each generated procedural primitive is described by a request to the helper program, in the form of an ASCII data block that describes the primitive to be generated. This data block can be anything that is meaningful and adequate to the helper program, such as a sequence of a few floating-point numbers, a filename, or a snippet of code in an interpreted modeling language. In addition, the renderer supplies the detail size of the primitive's bounding box, so that the generating program can decide what to generate based on how large the object will appear on-screen.

The generation program reads the request data blocks on its standard input stream and emits RIB commands on its standard output stream. These RIB streams are read into the renderer as though they were read from a file (as with ReadArchi ve above) and may include any standard RenderMan attributes and primitives (including procedural primitive calls to itself or other helper programs). As long as any procedural primitives exist in the rendering database that require the same helper program for processing, the socket connection to the program will remain open. This means that the program should be written with a loop that accepts any number of requests and generates a RIB "snippet" for each one.

The RIB syntax for specifying a RIB-generating program procedural primitive is

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]