Object Instancing

Object Instancing

May 2012 (Updated May 2014)

Introduction

PhotoRealistic RenderMan has always supported a "retained geometry" feature, also known as object instancing. The intention of this feature has always been to deliver memory savings when rendering scenes where many identical pieces of geometry are created. In practice, however, releases of PRMan prior to 17 did not deliver on this intention. In PRMan 17, the retained geometry functionality has been improved with an eye to delivering much better memory savings, particularly for scenes where many instances of the same heavy geometry are used, and for scenes with ray-traced instances. The functionality has also been improved to be more generally useful.

  • Important

    PRMan 19 introduced three new attributes for controlling tessellation. Attribute "dice" "string instancestrategy" and Attribute "dice" "float instanceworlddistancelength" control how finely object masters are tessellated - "instancestrategy" determines the dicing strategy for object instancing; the default is now "worlddistance". Users should note that the default settings for these attributes, particularly "instanceworlddistancelength", might need to be tuned for performance.


RenderMan Specification Changes

RenderMan Interface Changes

Several modifications have been made to the RenderMan API in order to support parameters passed to retained geometry.

The RiObjectBegin and RiObjectInstance routines remain unchanged but should be considered deprecated. Unlike most RenderMan interface routines, these two functions do not accept a token value pair list, and will continue not to do so for backwards compatibility. In order to pass parameters to object declarations and instances, new versions of these routines have been added: RiObjectBeginV and RiObjectInstanceV. RiObjectBegin will be equivalent to calling RiObjectBeginV with null values for the token and value parameters; likewise for RiObjectInstance and RiObjectInstanceV.

With these new routines, in the same fashion as RiLightSource, an application using the RIB client library can now override the automatic assignment of string handles by RiObjectBeginV by passing the RI_HANDLEID parameter to the call, with its associated value being the desired string handle representation for the RIB file.

Graphics State Changes

The original RenderMan specification for retained geometry only allowed a list of geometric primitives to be enclosed within RiObjectBeginV and RiObjectEnd calls. This restriction has been relaxed slightly over the years to allow for nested transformations and motion blocks. Most attributes were not allowed due to the requirement that the attributes of the retained geometry would be inherited at the time of instancing, not at the time of creation.

In order to maximize the memory efficiency of instancing, the inheritance of attribute state has been changed significantly: retained geometry now retains the attribute state at the time of creation, and will not automatically inherit the attribute state at the time of instancing. As a consequence of this change, restrictions on most attributes have been lifted: all attributes are now allowed within object definitions. RiAttributeBegin and RiAttributeEnd are also allowed within object definitions.

Note that there are several special exceptions to this rule. Some of these exceptions explicitly merge attribute states from both the object definition and the object instance.

Transformations
Transformations may be used within an object block and the transformation will be considered a relative transformation to the coordinate system active when the object is instanced. In other words, the transformation applied to the geometry will be the concatenation of the transform of the object instance combined with any transformations within the object block prior to the geometry.
User Attributes
The user attributes (any RiAttribute with the user namespace) that are available to a shader bound to instanced geometry will be considered to be a combination of those user attributes defined at the time of the RiObjectInstanceV call, along with those defined inside the object block before the geometry definition. If there are any conflicts, user attributes defined within the object block will take precedence over those defined before the instance.
Co-shaders
Co-shaders behave in similar fashion to user attributes: any co-shaders in scope at the time of the RiObjectInstanceV call will be visible, along with any defined inside the object block prior to the geometry definition. Note that any primitive variables attached to the geometry will NOT automatically be bound to the corresponding shader parameter if the coshader is outside the object definition.
ジオメトリ Orientation
The orientation of geometry (its handedness, as defined by RiOrientation and RiReverseOrientation) is derived from both the instance orientation and the master orientation, in that order. To clarify: if the master definition explicitly sets the RiOrientation, that orientation will win. If the master definition does not call RiOrientation, it inherits the orientation from the instance; it is free to reverse that inherited orientation with RiReverseOrientation.
Light Shaders
Unlike other attributes, the illumination state of retained geometry is defined solely by the illumination state at the time of instancing, and not at the time of creation. In the case of nested instances, the outermost illumination state wins. This allows for light linking to be performed solely at the level of the object instances.
Visibility and Trace Groups
For a ray to intersect a primitive the visibility and trace groups active before the primitive definition must allow the intersection. In addition, the visibility and trace groups in play at the time of the RiObjectInstanceV call must allow the intersection.
Attribute "identifier" "int id" (new behavior in PRMan 19)
Any positive value of "identifier" "id" at the time of an ObjectInstance call will override any values set inside the object master definition. This allows instances to be identified in "i"-mode AOV images by a unique integer id not associated with the object master. Otherwise the id will be inherited from the object master. 0 is considered to be a valid positive number in this case. The default value is -1.
Bxdf (RIS-specific, PRMan 19)
Any Bxdf in scope at the time of an ObjectInstance call will override any values set inside the object master definition.
Matte (RIS-specific, PRMan 20)
The matte behavior of instanced geometry is determined by the Matte attribute at the time of the ObjectInstance call.

Finally, several ambiguities about object blocks and other scopes are clarified as follows:

  • Due to relaxation of attribute state inheritance, RiObjectBeginV and RiObjectEnd calls may now no longer take place outside of RiWorldBegin and RiWorldEnd.
  • RiObjectInstanceV calls may now occur within RiObjectBeginV and RiObjectEnd blocks, allowing for nested object definitions.
  • RiObjectInstanceV calls that occur within RIB archives (including DelayedReadArchive procedurals) will work correctly, as long as the object definition has already taken place in some previous (outer) scope.

Usage Considerations

General Performance Considerations and Limitations

Memory savings when using object instancing are predicated upon being able to reuse a geometry definition amongst multiple instances. Thus, given an object definition, some overhead will be created and will have a lifetime that persists for the entire duration of the render. This retained overhead increases substantially as soon as the first instance of the object master needs to be rendered. This retained geometry definition is important especially when one considers the common usage of RiProcedurals; the renderer cannot predict whether a RiProcedural will contain an instance, and therefore cannot anticipate at master definition time any subsequent usage of all instances.

Because of this, it should be obvious that instancing delivers the best benefits when many instances of the object master will be created. The exact number depends on many factors, but certainly "many" is greater than one: it should be fully expected that if only an instance is created it will not be as efficient as simply emitting the master definition without the ObjectBegin/ObjectEnd blocks.

Moreover, some minor bookkeeping is required for each object instance (including at least several matrices). If the geometry that is being instanced is exceedingly lightweight (e.g. a single RiSphere), the cost of the bookkeeping may outweigh the benefits of sharing the geometry and instancing will not deliver any benefits. To mitigate the cost of the bookkeeping, it is always better to create an object definition that encompasses some not-insignificant amount of geometry.

The analysis of the number of copies needed before instancing provides a benefit bears extra scrutiny when we consider the standard scanline algorithm (REYES). The REYES architecture is a streaming architecture that processes buckets in order and discards (frees from memory) any geometry in completed buckets. In the situation of a single large mesh that fills the entire screen, the REYES architecture will normally discard and free large portions of that mesh as it finishes buckets. If that mesh is instead retained through an object definition, the retained geometry definition will persist for the duration of the render and the overall memory performance may suffer as a result, as compared to the non-instanced case. Even when we increase the number of copies, the fact that REYES is still aggressively freeing memory for each copy in the non-instanced case may mean that the number of copies required before instancing pays off may be high. One particular situation where REYES does very well with instancing is the case where an object definition involves very complex geometry (e.g. a very dense subdivision mesh or many such meshes) and each copy overlaps a single bucket. In such cases, the peak memory for that bucket may be very high and will be substantially reduced by the use of instancing.

Because the ray tracing subsystem does not generally free geometry (with the exception of unloadable procedurals), the use of instancing with ray tracing will generally deliver much more obvious benefits compared to the use of instancing with REYES, as REYES is already aggressively managing geometry memory. Hence, the number of copies required before instancing provides a benefit for ray tracing can generally be lower than in the REYES case.

In order to deliver maximum efficiency through reuse, object instancing imposes some limitations (other than those listed in the Known Issues, which we plan to address before release).

Several of these issues pertain to dicing. Stitching (Attribute "dice" "stitch") is not supported for instanced objects, and the value of this attribute will be ignored. The refinement() method will be ignored on instanced objects. Also, geometry that is instanced will always be diced independent of the camera - in other words, non raster-oriented dicing will be the default, as if Attribute "dice" "rasterorient" had the value 0.

Levels of Detail

When using the stochastic hider, in order to maximize the reuse of diced geometry representations between instances, the dicing of object masters is quantized as if there were a fixed (small) number of instances at various distances from the camera, rather than being computed exactly for each instance's transform. In other words: a single master definition will only give rise to a finite number of possible diced representations, and instances of that geometry will select amongst these representations one that best matches its current distance to the camera. These different representations are essentially different levels of detail, and are created on demand as required depending on the range of instances in the scene.

Generally, this will mean that instanced geometry may have a higher dicing rate than the non-instanced equivalent, and consequently may lead to more shading points. This is mainly a consideration for REYES geometry; ray-traced instanced geometry will generally end up being more finely tessellated in order to match the dicing rate of REYES, but the amount of ray-traced shading is dependent only on the number of rays intersecting the surface and will stay unchanged as long as the number of rays fired stays the same. (Of course, when using the REYES hider, the number of rays fired may increase due to REYES geometry being shaded more often.)

Note that some dicing strategies do not require multiple levels of detail as they will create identical representations regardless of the distance of the instance to the camera. For example, the worlddistance dicing strategy, enabled by

Attribute "dice" "string instancestrategy" "worlddistance"

will always create a single level of detail, shared by all instances.

Usage of a dicing strategy such as the default planarprojection strategy may incur the overhead associated with levels of detail. However, as previously hinted, these multiple copies are less necessary when using the ray trace hider; a single level of detail may be preferred for its reduction of memory overhead, even if that single level of detail is highly overtessellated for objects far away from the camera. In recognition of this workflow, a new attribute added to PRMan 19 controls whether only a single level of detail will be created:

Attribute "instance" "int singlelod" [-1]
ObjectBegin "tree"
  #...
ObjectEnd

If the value of singlelod is 0, then object masters may create multiple levels of detail if the dicing strategy requires it. If the value of singlelod is 1, then object masters will only create a single level of detail irregardless of the dicing strategy. By default, this attribute has value -1, which auto-selects the behavior: if the raytrace hider is used only single representation is used (equivalent to singlelod = 1), otherwise multiple representations may be used (equivalent to singlelod = 0).

When singlelod 1 is used in conjunction with the default planarprojection dicing strategy, the renderer must pick a single representation. Its current choice is to use either the worst case screen-filling representation, or the representation associated with the current transform of the master definition, whichever one is further away. If the master definition is not located at a suitable distance from the camera, this may be a suboptimal choice. It may well be the case that either moving the master definition relative to the camera will result in much better performance, or the use of an alternate dicing strategy such as worlddistance may be required.

Nesting

As previously mentioned, nested object definitions are fully supported. All previously mentioned rules about inheritance of attributes fully apply to nested instances. Note that instances can only refer to previously defined geometry, which explicit prohibits any cyclic definition.

The use of nested instances may facilitate the construction of instance hierarchies, which in turn allows for flexibility in terms of geometric reuse. Consider the case of rendering a forest of trees. A user could build the entire forest by instancing a single tree, with a RIB layout similar to the following:

ObjectBegin "tree"
ObjectEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree"
AttributeEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree"
AttributeEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree"
AttributeEnd
# etc ...

While this is efficient, it may quickly become obvious that every tree is identical. Even with judicious use of shader driven variation, the the trees will remain geometrically identical and the similarities may become obvious.

Alternately, several full trees could be built:

ObjectBegin "tree1"
ObjectEnd
ObjectBegin "tree2"
ObjectEnd
ObjectBegin "tree3"
ObjectEnd
# etc ...
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree1"
AttributeEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree1"
AttributeEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree2"
AttributeEnd
# etc ...

With enough tree object masters, there will be sufficient variation in the forest; however, the efficiency of instancing will be reduced because each tree master is completely different.

Nesting allows for a third approach, which increases granularity and also increases sharing. We can break the tree object masters down into subparts (branches) which are themselves also masters:

ObjectBegin "branch1"
ObjectEnd
ObjectBegin "branch2"
ObjectEnd
ObjectBegin "branch3"
ObjectEnd
ObjectBegin "tree1"
  AttributeBegin
    ConcatTransform ...
    ObjectInstance "branch1"
  AttributeEnd
  AttributeBegin
    ConcatTransform ...
    ObjectInstance "branch2"
  AttributeEnd
ObjectEnd
ObjectBegin "tree2"
  AttributeBegin
    ConcatTransform ...
    ObjectInstance "branch2"
  AttributeEnd
  AttributeBegin
    ConcatTransform ...
    ObjectInstance "branch3"
  AttributeEnd
ObjectEnd
ObjectBegin "tree3"
  AttributeBegin
    ConcatTransform ...
    ObjectInstance "branch1"
  AttributeEnd
  AttributeBegin
    ConcatTransform ...
    ObjectInstance "branch3"
  AttributeEnd
ObjectEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree1"
AttributeEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree1"
AttributeEnd
AttributeBegin
  ConcatTransform ...
  ObjectInstance "tree2"
AttributeEnd

In this scenario, the efficiency of instancing is increased because (at least in this contrived example of three branches, and two branches per tree) each tree now shares at least some portion of its geometry with every other tree in the scene. The obvious tradeoff: each tree is partially identical to every other tree in the scene. This third approach can be seen as a compromise between the first approach of completely identical masters, but with maximal reuse, and the second approach of completely different masters, but with minimal reuse.

Motion Blur

In most cases, object instancing does not restrict the use of motion blur in any way. Both deformation and transformation motion blur are allowed within an object definition. In addition, transformation motion blur may be freely applied to an object instance. There are no restrictions on the number of motion segments that may result due to concatenations of any such motion blurs.

There is one special consideration related to multi-segment motion blur. When Attribute "shade" "frequency" is set to "motionsegment", this is considered to be a request to run shading at all segment times that may arise anywhere in the union of the stack of motion blur blocks. For example, in the following situation, the sphere will be shaded at four separate times: 0, 0.25, 0.5, and 1.

Attribute "shade" "string frequency" ["motionsegment"]
MotionBegin [0 0.5 1]
  Translate 0 0 0
  Translate 0 0 1
  Translate 0 0 2
MotionEnd
MotionBegin [0 0.25 1]
  Sphere 1 -1 1 360
  Sphere 1 -1 1 360
  Sphere 1 -1 1 360
MotionEnd

However, in the case where a transformation blur sample time does not coincide with a time value for an object master that undergoes deformation, an extra shading sample will NOT be created. Instead, shading is constrained to the deformation blur times for the master and the results are interpolated. In the following example, the sphere will only be shaded at the times 0, 0.25, and 1.

ObjectBegin "movingsphere"
  MotionBegin [0 0.25 1]
    Sphere 1 -1 1 360
    Sphere 1 -1 1 360
    Sphere 1 -1 1 360
  MotionEnd
ObjectEnd
Attribute "shade" "string frequency" ["motionsegment"]
MotionBegin [0 0.5 1]
  Translate 0 0 0
  Translate 0 0 1
  Translate 0 0 2
MotionEnd
ObjectInstance "movingsphere"

In the case where the shade frequency is set to "frame", shading will only occur once per frame as usual, regardless of whether the geometry is an object instance or not.

シェーディング Considerations

While instances of the same geometry will share their geometric representations as much as possible, they will not share their shading. This is desirable because most often the different transformations applied to the geometry make any shading reuse impossible. Note that this non-sharing of shading results automatically extends to the radiosity cache.

There is one exception to this, involving displacement shaders. The renderer will reuse displaced results, particularly when ray tracing is involved, so it is a requirement that displacement shading on geometry be performed in a space that is independent of the transform applied to the instance. It should also not rely on any user attributes, per-instance primitive variables, or co-shaders that may vary instance to instance.

There are some subtleties pertaining to the default spaces in which shading occurs.

Object space for retained geometry is defined in the most straightforward way possible: the space is defined by the concatenation of all transforms applied to the geometry, including any transforms defined in the ObjectBegin/End block, and any transforms applied to the ObjectInstance.

Shader space is the transform in force when the shader is defined. As the shader is defined at the time the master is defined and reused for all instances of that master, it follows that shader space is unaffected by the instance transform. This may be surprising, because it runs counter to any assumptions about an object definition being equivalent to a macro mechanism.

To illustrate, consider the following RIB:

WorldBegin
  Translate 1 0 0
  ObjectBegin "blp_defaultsurface"
    TransformBegin
      Rotate 22.5 0 0 1
      Surface "mysurf"
    TransformEnd
    Rotate -45 0 0 1
    Patch "bilinear" "P" [-1 1 0 1 1 0 -1 -1 0 1 -1 0]
  ObjectEnd
  Translate 0 0 2
  ObjectInstance "blp_defaultsurface"
WorldEnd

In this example, shader space is defined by the transformations Translate 1 0 0 and Rotate 22.5 0 0 1; it is unaffected by the transformation Translate 0 0 2, which is applied to the instance.

シェーディング Variation and Per-Instance Primitive Variables

Even though the main purpose of instancing is to favor the reuse of copies while minimizing the variability between copies, it is still often desirable to be able to shade each variant differently. Such shading variation can be driven by several methods:

  • by performing shading calculations in object space, which is typically different from instance to instance;
  • by setting different user attributes per object instance, and driving shading calculations using these attributes (via the attribute() shadeop;
  • by declaring different co-shaders or modifying the parameters to co-shaders before each object instance, and relying upon different co-shader executions to change the result of the surface shader.

A fourth method can be accomplished by the use of per-instance primitive variables or per-master primitive variables, which are simply primvars bound directly to either the object instance or object master definition. In concrete terms, these are primitive variable values that are passed directly to RiObjectBeginV or RiObjectInstance. They may have any class, but for vertex and varying only one value should be provided. In the following example, the variable mycolor has been bound to the object master definition as a per-master primitive variable with the value [1 1 1], and has also been bound to the instance as a per-instance primitive variable with the value [0 1 0].

ObjectBegin "blp" "uniform color mycolor" [1 1 1]
  Surface "primvarcolor"
  Patch "bilinear" "P" [-1 1 0 1 1 0 -1 -1 0 1 -1 0]
    "varying color mycolor" [0 1 0 1 0 0 1 1 0 0 0 1]
  Translate 2 0 0
  Surface "anotherprimvarcolor"
  Patch "bilinear" "P" [-1 1 0 1 1 0 -1 -1 0 1 -1 0]
    "varying color mycolor" [0 1 0 1 0 0 1 1 0 0 0 1]
ObjectEnd
ObjectInstance "blp"
Translate 0 2 0
ObjectInstance "blp" "uniform color mycolor" [0 1 0]

Note that the Patch geometries also have the primitive variable mycolor bound directly to it. In cases like this, per-master and per-instance primvars are treated as overrides, but only for the purposes of the readprimvar() shadeop: if the variable mycolor is interrogated with readprimvar(), the value returned will not be the value bound to the geometry, it will be the value bound to the master or to the outermost instance.

To illustrate this, suppose the two shaders in the object definition are defined as follows:

surface primvarcolor() {
    varying color myusercolor;
    if (readprimvar("mycolor", myusercolor) != 0) {
        Ci = myusercolor;
    } else {
        Ci = color(1, 0, 0);
    }
    Oi = 1;
}

surface anotherprimvarcolor(varying color mycolor = color(1, 0, 0)) {
    Ci = mycolor;
    Oi = 1;
}

Because per-instance primvars only override the value of object primvars for the purpose of the readprimvar() shadeop, the first shader will result in a white square for the first instance (the per-master primvar wins) and a green square for the second instance (the per-instance primvar wins). The second shader will always result in a color gradient, because the values for mycolor will always come directly from the object primvars (which are varying).

Note that since per-instance and per-master primvars are explicitly set up as override mechanisms, they also explicitly run counter to the usual RenderMan-centric notion that the innermost graphics state wins, especially when dealing with nested object definitions. This is useful because it provides a mechanism that works in an opposite way to user attributes. If it is important that a value defined in the innermost object definition should always win, then the value should be defined in a user attribute (or bound directly to the geometry). If it is important that a value defined in the outermost scope be able to override values defined in the innermost scope - even to within the object definition - then the value should be defined using a per-instance primvar, and interrogated using the readprimvar() shadeop.

Users of RIS mode (starting in PRMan 19) should note that as mentioned above, Bxdfs specified at the ObjectInstance level will override any Bxdfs in the master definition. This runs counter to the rule adopted for standard RSL shaders and facilitates shading variation for scenes set up using RIS mode.

Using RIS (PRMan 19)

The addition of RIS mode rendering in PRMan 19 brings several twists to object instancing. Chief amongst these is the fact that the raytrace hider is currently the only way of operating with RIS. Overshading becomes less of a concern with the raytrace hider; however, reducing the memory consumption of geometry that stays in memory for the duration of the render may now be of paramount importance.

As a reiteration of several points previously made:

  • The default behavior of Attribute "instance" "int singlelod" when using the raytrace hider is to create only a single level of detail for a master definition. This recognizes that favoring a reduction in memory consumption (by eliminating multiple levels of detail) over the possibility of overtessellation is generally a better strategy.
  • It is important to note the interaction between singlelod=1 and the dicing strategy in play. The aforementioned behavior of the planarprojection strategy (to choose either a screen-filling worst case representation, or a representation equivalent to the current transform of the master) may not be ideal, and it may be preferable to use a different dicing strategy for instances such as the worlddistance strategy. However, picking such a strategy will necessitate setting the シェーディングRate in a matter that one may not be accustomed to; for example, the worlddistance strategy is parameterized by units of worldspace, not units of pixel area.
  • シェーディング variation using RIS can be performed by the simple expedient of specifying a different Bxdf prior to each ObjectInstance.

Known Issues

  • Ray-traced RiCurve geometry will not render correctly in the case where no normals are supplied for the curves and Attribute "dice" "roundcurve" is set to 0. For other cases (either normals supplied, or "roundcurve" set to 1) ray-traced curves will be correct.
  • Concatenation of per-instance transformations is not supported in construct() or when declaring shader defaults.