Re-rendering with PRMan

Re-rendering with PRMan

May, 2011 (Updated May, 2013)

Introduction

RenderMan Pro Server provides two complimentary techniques for re-rendering and editing production-quality scenes. One provides unabridged editing capabilities and quick startup times; in exchange, it is less capable of handling highly complex scenes and potentially takes longer to produce final-quality pixels. The other provides a restricted set of editing operations and requires a potentially lengthy preprocessing step in exchange for fast computation of final-quality pixels.

This document gives an overview of both methods, along with a discussion of application and pipeline integration issues common to the two. Details of the editing API can be found in the Re-rendering reference section.


Ray-traced Re-rendering

PRMan's raytrace hider allows users to bypass the REYES front-end and trace primary rays directly, for both re-rendering and final frame renders. In contrast to the REYES re-renderer, discussed below, the raytrace re-renderer renders the entire scene from scratch on every edit. The primary advantage of this is that there are no inherent constraints on the class of edits that can be applied to the scene. The primary disadvantage is that the entire scene must fit in memory to achieve reasonable render times.

As of PRMan 18, there are two "integration modes" supported by the raytrace hider - "string integrationmode" ["distribution" | "path"] - which allow you to choose whether to use distribution ray tracing or path tracing. The default is "distribution" (ray tracing), but "path" is cooler.

Getting Your RIB Working with Ray-traced Re-rendering

Of paramount importance in setting up your re-rendering pipeline is the proper Hider setup. Be sure to include your Hider line before RiEditWorldBegin, e.g.:

Hider "raytrace" "string integrationmode" ["path"] "int maxsamples" [256]

[Note: the "distribution" integration mode also supports "minsamples".]

Beyond that, no change to shaders or RIB files are necessary. RiEditWorldBegin will accept the name of an existing RIB file, loading the scene in the file and starting an initial, unedited, render of the scene. The ray tracing system allows the user to select which types of rays (e.g. diffuse, specular, transmission, camera) can hit a particular gprim. By default all gprims are visible to camera rays. In regular REYES rendering, camera ray visibility alone is not sufficient for a gprim to be added to the ray tracing database (because camera visibility is handled by the REYES algorithm). In contrast, when the raytrace hider is invoked, all gprims visible to camera rays are added to the ray tracing database.

サンプル

Path tracing:

Hider "raytrace" "string integrationmode" "path"
                 "int maxsamples" 256
                 "int incremental" 1
Option "rerender" "int[2] lodrange" [0 2]
Option "bucket" "string order" "spiral"
EditWorldBegin "bakedb"
               "string rerenderer" "raytrace"
# ...
EditWorldEnd

Fixed sampling (distribution):

Option "bucket" "string order" "spiral"
Option "rerender" "int[2] lodrange" [0 3]
Hider "raytrace" "string integrationmode" "distribution"
                 "string samplemode" "fixed"
                 "string screenorder" "bucket"
EditWorldBegin "bakedb"
               "string rerenderer" "raytrace"
# ...
EditWorldEnd

Adaptive sampling (distribution):

PixelVariance 0.001
Option "bucket" "string order" "spiral"
Option "rerender" "int[2] lodrange" [0 3]
Hider "raytrace" "string integrationmode" "distribution"
                 "string samplemode" "adaptive"
                 "int minsamples" 8
                 "string screenorder" "bucket"
EditWorldBegin "bakedb"
               "string rerenderer" "raytrace"
# ...
EditWorldEnd

There are, additionally, Python script demos included in the examples directory that ships with RenderMan Pro Server (located in $RMANTREE/lib/examples/rerender).

Additional Information

Please consult the Re-レンダリング documentation in The RenderMan Interface section of the documentation for complete details regarding the procedures for editing and re-rendering a scene, as well as the Raytrace Hider documentation.


REYES Re-rendering

RenderMan Pro Server 14 introduced a re-rendering mode that accelerates re-shading of production-quality scenes. Our implementation employs a deep-framebuffer approach that requires an initial pre-rendering pass to bake subsample visibility results as well as partial results from shading calculations. Because visibility information is baked, re-rendering imposes several limitations on the kinds of edits that can be made during a re-rendering session. We disallow editing of scene elements that affect visibility including camera settings, displacements and geometric primitives.

Re-rendering streams the baked results through the shading system and efficiently restarts shader systems from the bake-point to completion. Re-rendered results are identical to full renders and all features of the prman shading system are available, including raytracing, DSO shadeops, and deep shadows. Re-rendering streams cached data on-demand, allowing rendering of arbitrarily complex scenes and short start-up times. レンダリング is fully multithreaded with progressive refinement, providing interactive feedback on complex scenes. The renderer also provides an optimization for relighting that assumes lighting results can be combined linearly. In this mode, re-rendering is constrained to a single light, and only the parts of the scene affected by that light are re-rendered.

A Guided Tour

The following Python script illustrates the major components of a REYES re-rendering session in prman: the options to tell prman to bake a database for re-rendering, the APIs to launch a separate prman process and communicate with it, and the new Ri calls to load and edit a baked scene.

import time
import prman
arglist = []
prman.Init(arglist)
ri = prman.Ri()

# The light we will be editing
def doLight1(ri, angle):
    ri.TransformBegin()
    ri.Rotate(angle, 0,1,0)
    ri.Rotate(45, 1,0,0)
    ri.Translate(0,0,-7)
    ri.LightSource("shadowspot", {ri.HANDLEID: "light1",
                                  "float coneangle": .7,
                                  "float intensity": 20,
                                  })
    ri.TransformEnd()

# Here's a simple scene description
def doScene(ri):
    # Do not use the multires driver when baking. There is a known issue
    # (to be fixed) that prman will not exit while the multires driver window
    # stays open. This script would hang until the user closed the window.
    ri.Display("example.tif", "framebuffer", "rgba")
    ri.Format(512, 512, 1)
    ri.シェーディングRate(1)
    ri.Projection(ri.PERSPECTIVE, {ri.FOV: 35})
    ri.Translate(0, 0, 20)
    ri.Rotate(-25, 1, 0, 0)

    ri.WorldBegin()

    ri.LightSource("ambientlight", {"float intensity": .1,
                                    ri.HANDLEID: "l1"})

    # Issue the light we'll be editing
    doLight1(ri, 0)

    # The floor
    ri.カラー([1, 1, 1])
    ri.TransformBegin()
    ri.Scale(6, 1, 6)
    ri.Surface( "matte" )
    ri.Patch( "bilinear", {"P": [-1, 0, -1,  -1, 0, 1,
                                  1, 0, -1,   1, 0, 1]})
    ri.TransformEnd()

    # A teapot
    ri.カラー([.2, .5, 1])
    ri.Surface("rmarble")
    ri.Rotate(-90, 1, 0, 0)
    ri.Translate(0, 1, 0)
    ri.ジオメトリ("teapot")
    ri.WorldEnd()

# Step 1: Bake the scene for re-rendering
print "perform pre-render bake...."

# Launch a prman process with a control channel
filename = "launch:prman? -ctrl $ctrlin $ctrlout"
ri.Begin(filename)

# The options to turn on baking for re-rendering.
ri.Option("render", {"int rerenderbake": 1})
ri.Option("render", {"string rerenderbakedbdir": "bakedb"})

# Issue the scene to be baked
doScene(ri)

# RiEnd will block until the render is finished
print "waiting for bake render to finish...."
ri.End()

# Step 2: Re-render and edit the baked scene
max = 720
if max > 0:
    print "initializing re-rendering...."

    # Launch a prman process with a control channel
    filename = "launch:prman? -ctrl $ctrlin $ctrlout -dspy $dspyin $dspyout"
    ri.Begin(filename)

    # It's preferable not to use 'it', 'x11', or 'windows' as
    # your framebuffer. The higher performance option is 'multires'.
    ri.Display("teapot", "multires", "rgb")

    # Load the baked scene
    ri.EditWorldBegin("bakedb")

    # Optional: Wait for the load and it's initial display before
    # issuing edits. This is here simply to illustrate the use of
    # a "finishing" flush.
    #print "waiting for initial display of editing session...."
    #ri.ArchiveRecord("structure", ri.STREAMMARKER + "_initial")
    #prman.RicFlush("_initial", 1, ri.FINISHRENDERING)

    # Spin the light around the teapot
    print "now we're ready to send edits...."
    for i in range(0,max,10):
        # We can interrupt rendering by annotating the Ri
        # stream and flushing the renderer to the desired
        # point.

        # Mark a point in the stream with a special archive record.
        print "edit %d/%d" % (i,max)
        ri.ArchiveRecord("structure", ri.STREAMMARKER + `i` )

        # Issue a non-blocking flush to the marked point. Tell the
        # renderer to skip any previous edits it's processing and
        # advance to this point in the Ri stream.
        prman.RicFlush( "%d" % i, 0, ri.SUSPENDRENDERING)

        # Issue the edit
        ri.EditBegin("attribute", {"string editlights": "light1"})
        doLight1(ri,i)
        ri.EditEnd()

        # Optional: Give the renderer time to complete a few levels
        # of progressive refinement before we interrupt it with
        # a new edit. Uncomment this to slow down updates. Depending
        # on the speed of your system and the complexity of the scene,
        # these parameters may need adjusting.
        #while prman.RicGetProgress() < 15:
        #    time.sleep(.05)

    print "waiting for last edit to finish...."
    ri.EditWorldEnd()

    print "editing done, goodbye"
    ri.End()

Getting Your RIB Working with REYES Re-rendering

To enable re-rendering:

  • Recompile shaders The "_lightingstart()" analysis discussed below has improvements in the 14 shader compiler that re-rendering relies upon.

  • Baking options If you choose not to use the Python script above as a test harness, you must add to your RIB file the two options:

    # Baking options
    Option "render" "int rerenderbake" [1]
    Option "render" "string rerenderbakedbdir" "bake"
    

Some scenes will work with nearly out of the box. However, there are restrictions that you may need to work around. See the Restrictions, Constraints, and Known Issues section, below, for details.

The REYES Re-rendering Pipeline

Re-rendering with prman is a three step process: shader analysis, baking for rerendering, and rerendering.

Shader Analysis First, surface shaders are analyzed to determine which parts of the shaders are lighting-independent. A special shadeop, _lightingstart(), is inserted into the shaders automatically by the shader compiler. Details of the use of this shadeop are given in the Traditional shaders section.

Baking for re-rendering Simply add the two baking options given above to the top of your RIB and render with prman as usual. In addition to rendering the scene, prman will create the directory specified by rerenderbakedbdir and write cached data into it.

Re-rendering and editing The canonical structure of an editing session is:

// Specify the display for interactive rendering
RiDisplay( "rerendering", "multires", "rgb" );

// This loads a world that has been baked into the directory
// "bake" and puts prman in re-rendering mode.
RiEditWorldBegin( "bake", RI_NULL );

  // Start an edit
  RiEditBegin( ... )
    // Apply some part of the edit to a particular attribute scope
    RiEditAttributeBegin( ... )
    ...
    RiEditAttributeEnd()
  // End the edit and re-render the world
  RiEditEnd()

// Finish the re-rendering session and delete the world
RiEditWorldEnd()

The details of editing are explained below.

The Relighting Algorithm

The REYES re-rendering algorithm is focused on relighting: editing and re-rendering the parts of shaders that calculate the contributions of lights and the surface response to those lights. Displacement and pattern generation are fixed during re-rendering. This happens in two phases, baking and re-rendering.

Baking happens during a regular render pass. Displacement shaders are executed. Then, surface shaders are excuted up to the point that lighting calculations start. This is determined differently for traditional shaders and shader objects. Details follow. The live variables in a shader (those accessed after the baking point) are saved to disk. The surface shader completes and the final color of the surface is also saved to disk. Atmosphere, visible point, and imager shaders are executed as normal and a final image is produced. As of release 15, visible point shaders and imager shaders are not run during rerendering. They will be supported in a future point release of version 15. See the known issues section of the re-rendering reference documentation for details.

Re-rendering happens during a special invocation of prman. The world to be re-rendered is passed to RiEditWorldBegin, and a series of RiEditBegin \ RiEditEnd blocks are issued to perform edits. Details are given below. During re-rendering displacement shaders are not run. Surface shaders are restarted at the point of baking. The shader is run to completion as normal. When editing a light, the scene is first rendered with only that one light enabled. The color generated by the surface shader is saved. This surface cache is subtracted from the cache of all lights generated during baking. This is now a cache of all lights but the active one. The light is then rendered with an edit applied, the result is added to the cache of all other lights, and the sum of the two is displayed as the new image. In this way, the scene can be re-rendered at the cost of rendering with a single light.

There are two additional details. First, we acutally allow edits to multiple lights simultaneously. These lights are grouped into a single cache. Second, subsequent to running the surface shader during editing, the atmosphere shader, if present, will be run with all lights enabled. Editing scenes with atmosphere shaders may not be interactive.

Traditional Shaders

As noted above, the shader compiler analyzes surface shaders inserting a _lightingstart() shadeop just prior to the first lighting calculation, such as diffuse() or illuminance. The compiler determines the set of variables accessed in the remainder of the shader and lists the variables as arguments to _lightingstart(). During baking, _lightingstart() writes these varialbes to disk. During re-rendering, surface shader execution will begin with the _lightingstart() shadeop, loading the previously cached data from disk.

Shader Objects

Live variable analysis is not done for shader objects. Instead, baking occurs at the end of the prelighting() method. The live variables are all of the member variables of the shader object. Further, the state of any co-shader that has been executed by the shader object is also saved. During re-rendering member variables are restored, co-shader reconstructed, and execution restarted with the lighting() method. Note: we do not support re-rendering of shader objects that use the traditional method surface(). The prelighting(), lighting(), postlighting() style of object must be used.


Integrating Re-rendering into an Application

The RenderMan Interface Control developer note describes APIs that facilitate tighter integration of PRMan with user-written applications. In summary:

RiBegin() has been extended to accept a special command that launches prman as a separate, persistent process. Ri calls are routed to the prman process, and error messages and pixels are routed back to the application.

A new entry point in the Dspy API, DspyRegisterDriver() allows an application to register a direct-linked display driver for use with a "launched" prman.

The Dspy API has been extended to support multi-resolution images.

A new API name space, RenderMan Interface Control, or Ric, has been created.

RicGetProgress() returns the percentage completed of a render. This works with both a regular render and a re-render.

RicFlush() is used to interrupt the renderer.

RicProcessCallbacks() polls the queues of error messages and pixels coming from a launched prman and delivers any waiting data to the Ri error handler and user-supplied display driver.


Editing

Edit Sessions

Just as RiWorldBegin and RiWorldEnd delineate a world to be rendered, RiEditWorldBegin and RiEditWorldEnd delineate the re-rendering session of a previously baked world. An editing session may not appear within a frame. This restriction will be lifted in a later release.

Edit Blocks

Edits are enclosed in RiEditBegin()/RiEditEnd() pairs, and the scene is re-rendered during each RiEditEnd(). Edits replace an object that is already defined; they are not incremental. For instance, when a light is edited, all of the parameters and the shader space for the light must be reissued, even if only one of the parameters has changed.

The re-rendering algorithm in prman 15 is focused on relighting. The scene is rerendered based on which lights are being edited. Both the rerendering algorithm and the light to be edited are naturally expressed as parameters to the EditBegin block. The lights to be edited are specified as a colon-separated list of light names.

RiEditBegin("attribute", "string editlights", "light1:light2:light3", RI_NULL);

By default the renderer will display the combination of the lights been edited and those that are cached. The render can also display just the lights being edited, excluding the lights that are cached:

int solo = 1; RiEditBegin("attribute", "string editlights", "light1:light2:light3", "int displayonlyeditlights", &solo, RI_NULL);

The renderer will continue to maintain the proper caches regardless of display mode. One may freely switch between displaying all lights (just omit the optional parameter) and displaying the currently edited lights. To display the full results without issuing another change, send an empty edit:

int solo = 1; RiEditBegin("attribute", "int displayonlyeditlights", &solo, RI_NULL); RiEditEnd();

or

RiEditBegin("attribute", RI_NULL); RiEditEnd();

What Can Be Edited

prman does not allow edits that change visibility. For example, the camera may not be moved, gprims may not be added or deleted, and displacement shaders may not be edited.

Ri calls allowed during relighting

RiAttribute
RiAttributeBegin
RiAttributeEnd
RiCoordinateSystem
RiCoordsysTransform
RiShader
RiCropWindow
RiIdentity
RiIlluminate
RiLightSource
RiOption
RiReadArchive
RiResource
RiRotate
RiTransform
RiTransformBegin
RiTransformEnd
RiConcatTransform
ライト

ライト, referred to by their globally unique handles, are changed by issuing an RiLightSource call with the same handle. All parameters to the light must be reissued, including its transformation.

RiEditBegin("attribute", "string editlights", "light1", RI_NULL);
  // specify the coordinate system for light1
  RiTransform( ... );
  Riライトource( "spotlight", RI_HANDLEID, "light1", "color lightcolor", (RtPointer)&color );
RiEditEnd();
Adding a Light

Adding a light is a two-step process. First it is declared, which creates a globally unique handle, then the attribute state of every gprim that should receive the light is modified. In Ri, a light is turned on by default in the current and enclosed scopes. At edit time, we no longer know the nesting relationship of scopes because the attribute hierarchy has been flattened. So we adopt the convention that lights created during relighting are off by default and illuminate no objects. The light list of every gprim to be illuminated by the new light must then be updated. Editing attribute state, discussed in full detail below, is done by enclosing an edit in an RiEditAttributeBegin(RtToken scopename)/ RiEditAttributeEnd block. The scope parameter is matched against the attribute identifier:name.

When adding a light, one may not specify a list of lights to edit. Adding and editing must be done in separate edit blocks.

RiEditBegin("attribute", RI_NULL);
  // create coordinate system for light2
  RiTransform( ... );
  // save this transform for future use
  RiCoordinateSystem( "light2_xform" );
  Riライトource( "pointlight", RI_HANDLEID, "light2", "color lightcolor", (RtPointer)&color );
  // shine the light on the gprim named "left_hand"
  RiEditAttributeBegin( "left_hand" );
  RiIlluminate( "light2", 1 );
  RiEditAttributeEnd();
RiEditEnd();
Deleting a Light

To delete a light, simply set its shader to null. The renderer will automatically remove the light from every light list in every gprim.

When deleting a light, one may not specify a list of lights to edit. Deleting and editing must be done in separate edit blocks.

RiEditBegin("attribute", RI_NULL);
  RiLightSource( RI_NULL, RI_HANDLEID, "light1" );
RiEditEnd();
Coordinate Systems

A coordinate system is edited by saving the current transformation with the name of an existing saved coordinate system.

RiEditBegin("attribute", RI_NULL);
  RiIdentity();
  RiTranslate( 0, 1, 0 );
  // save the coordinate system
  RiCoordinateSystem( "OneUnitInY" );
RiEditEnd();

We expect a common paradigm for editing lights will be to save the transformation for a light in a marked coordinate system. The coordinate system for a light is often created in the process of defining the hierarchical transform state for the scene, and it may be inconvenient to recreate this hierarchy when editing lights. The transformation can be saved using RiCoordinateSystem(), and recalled during edit with RiCoordSysTransform().

RiEditBegin("attribute", RI_NULL);
  // recall the coordinate system for light1
  RiCoordSysTransform( "light1_xform" );
  Riライトource( "spotlight", RI_HANDLEID, "light1", "color lightcolor", (RtPointer)&color );
RiEditEnd();

To edit the position of a light, the coordinate system can be updated as illustrated below:

RiEditBegin("attribute", RI_NULL);
  // recall the coordinate system for light1
  RiCoordSysTransform( "light1_xform" );
  // move the light
  RiTranslate( 1, 0, 0 );
  // save the coordinate system
  RiCoordinateSystem( "light1_xform" );
  // edit the light
  Riライトource( "spotlight", RI_HANDLEID, "light1", "color lightcolor", (RtPointer)&color );
RiEditEnd();
Gprim Attributes

To change the attribute of a gprim, we refer to the name of the attribute scope in which the gprim was created, the attribute identifier:name. Specifying this name in the original Ri stream is optional, and uniqueness of scope names is left to the user. If identifiers are not issued at the proper granularity, multiple gprims may receive the same name and be indistinguishable to the renderer.

To edit scoped objects, we supply a regular expression to the new call RiEditAttributeBegin(RtToken scopename). The attribute database is searched and all scopes whose attribute identifier:name match the regular expression will be edited. All subsequent Ri calls, until a matching RiEditAttributeEnd(), will modify these attribute states.

Attributes state includes:

  • light lists
  • co-shaders
  • co-shader lists
  • the shader set (surface, displacement, etc.)
  • scoped marked coordinate systems
  • user attributes
Light lists

For instance, to turn light1 off in the scope named wrist_watch:

RiEditBegin("attribute", "editlights", &wrist_light_name, RI_NULL);
  RiEditAttributeBegin( "wrist_watch" );
  RiIlluminate( "light1", 0 );
  RiEditAttributeEnd();
RiEditEnd();
Co-Shaders

There are three points at which co-shaders are bound: as arguments to surface shaders and shader objects, in the list of current co-shaders when a primitive is created (accessible through the getshaders() shadeop, and as parameters to lightsource shaders. Editing co-shaders

To edit a co-shader bound to a light, one must set up editing of the attribute state in which the light was issued (using RiEditAttributeBegin), issue the RiShader call for the revised co-shader, then reissue the shader for the light, even if the lightsource shader itself is not being changed.

Surface shaders
Editing surface shaders is supported in both re-rendering modes. Note that there are certain restrictions.
User attributes

To change a user attribute on the left_arm:

 RiEditBegin("attribute", "editlights", &left_arm_light_name, RI_NULL);
   RiEditAttributeBegin( "left_arm" );
   RiAttribute("user", "color armcolor", (RtPointer)&color, RI_NULL);
   RiEditAttributeEnd();
RiEditEnd();
Crop Windows

Crop window changes during editing restrict the region of the image updated during an edit. Unlike an off-line render, setting a crop window during re-rendering will not change the size of the image as presented to a display driver. Instead, a new call has been added to the display driver interface that tells the driver what the active region is. For details see: DspyImageActiveRegion

Crop windows can be used both when baking and during editing. The crop set during editing is relative to the original size of an image, not to a crop set during baking (if any).

Marking Shaders, テクスチャs, Brick Maps, and Point Clouds as Dirty

It is common, during re-rendering, for external resources to be updated. For instance shadow maps may be recreated when a light is moved, or shaders may be recompiled. We can using the existing RiResource call, which already knows how to invalidate textures (which includes shadow maps and deep shadows).

RiResource("shadow.sm", "texture", "lifetime", "obsolete");

To invalidate a brickmap or pointcloud file, use the texture3d type

RiResource("occlusion.bkm", "texture3d", "lifetime", "obsolete");
RiResource("diffuse.ptc", "texture3d", "lifetime", "obsolete");

Starting in release 16.1, shaders may be recompiled and reloaded during re-rendering. However, only the body of the shader may change; the parameter lists of the shader's methods must be the same.

RiResource("myshader", "shader", "lifetime", "obsolete");
Managing User-generated Data (RSL Plugins)

Like textures, user-generated data accessed by RSL plugins may need to be updated during editing. The plugin interface has been extended with two entries points to facilitate this: one that runs immediately before each render, and one that runs immediately after a render has completed. During a regular prman render, these occur during RiWorldEnd(). During re-rendering these occur during RiEditEnd() and during RiEditBegin if the light(s) being edited have never been edited before. In this case an initial render of the unedited scene must be completed to initialize the relighting caches. See section 3.2 of the SIMD RSL Plugins developer note for details.


Restrictions, Constraints, and Known Issues

Each re-rendering mode has certain restrictions and limitations that should be considered before being incorporated in a production pipeline. It is our intent to address these in future releases. Below is the current list of restrictions, constraints, and known issues:

  • Hider restrictions The only hiders supported are stochastic and raytrace. Sigma buffer and stitching are not supported.
  • カメラ restrictions Multi-camera rendering is not supported.
  • Graphics primitives CSG is not supported.
  • Display Progressive refinement is critical to making editing interactive. We have provided a new display driver, multires, that can quickly display the multi-resolution images produced by re-rendering. However, existing display drivers can't display multi-resolution images and will cause the re-renderer to disable progressive refinement, rendering only at the highest resolution.
  • Resizable Arrays Traditional shaders with resizeable arrays will not be baked properly, leading to a crash during re-rendering. However, shader object-based shaders do support the use of resizeable arrays.

QuickRef

The re-rendering mode of prman utilizes functions under the Ric namespace, extensions to existing Ri Procedures, as well as additional Ri Procedures. The list below includes these common interface routines required to implement re-rendering.

RiBegin
RiEditBegin
RiEditEnd
RiEditWorldBegin
RiEditWorldEnd
RicFlush
RicGetProgress
RicProcessCallbacks
Direct-linked Display Driver Registration

Appendix A - The Raytrace Re-rendering Hierarchy

images/raytraceRerenderingHierarchy.png

The Raytrace Re-rendering Hierarchy