SUMMARY
In any scene with repeating geometry, instancing can be used for the benefit of a reduced memory footprint and increased scene graph cook performance. Typically, instancing works well when the geometry is the same across many copies and the only modifications required are transformations. This article describes a method of instancing using “instance array” locations, giving an example of how to use it to instance geometry to points of a point cloud.
For an overview on other available instancing approaches, please read the following section in the Katana Developer Guide: Instancing.
You can refer to the following Katana project file “instanceArray_arnold_material.katana” for an example project illustrating the sections below on point cloud instancing.
Instance Arrays
The following section is a brief overview of array instancing. For a more in-depth discussion to instancing, the Renderman Documentation: RenderMan 26 Docs - Instancing in Katana demonstrates how to set up all three instancing methods as an introduction to instancing.
In instance arrays, instance locations are mapped to one or more instance sources via attributes on a single location of type “instance array”. Instance arrays are similar to hierarchical instancing, with the added benefit of an easy way to set up multiple instance sources. One drawback is that materials cannot be varied per instance.
The instance array location needs to contain the geometry.instanceSource and at least one transformation attribute. The geometry.instanceIndex is optional if instanceSource contains a single path only, in which case that instance source is used for all instances in the instance array.
geometry.instanceSource |
String or string array attribute containing scene graph location paths of one or more instance sources |
geometry.instanceIndex |
Array attribute with the same number of elements as instances required. The values stored in this array indicate the index of the desired instance source in the geometry.instanceSource array. In other words, instanceIndex constitutes a mapping from instance index to instance source index. |
geometry.instanceMatrix geometry.instanceTranslate geometry.instanceRotateX geometry.instanceRotateY |
Each of these attributes are a flat array of per-instance transformations. The meaning of each attribute is the same as for conventional transformation attributes, and these can be multi-sampled to support animation. Note: RenderMan only supports use of the geometry.instanceMatrix attribute. |
For more information on instancing attribute conventions, please visit Katana Developer Guide: Instancing.
Array Instancing to a point cloud
Instance Arrays have the benefit of only using one location to hold the instances, which keeps the scene graph small even in scenes with thousands of instance locations. Instance arrays read the geometry.instanceSource attribute containing the locations of the instance sources to generate the instance array. Multiple instance sources can be referenced by using the geometry.indexArray attribute containing an array of indexes of the instance sources, one for each instance. The following are steps to generate an instance array to instance geometry to the points in a point cloud.
1. Create or import a point cloud object and set its location's type attribute to pointcloud using an AttributeSet node. The point cloud may be any geometry with a geometry.point.P attribute, such as that from the PrimitiveCreate node.
You will then see location Type of the point cloud has changed in the scene graph:
2. Then create or import the geometry you wish to use as an instance source. Then set it's parent location's type attribute to instance source using another AttributeSet node.
For example, if the geometry is at /root/world/geo/src/teapot then /root/world/geo/src should be the instance source location. You can later add more objects under this location to use multiple instance sources.
You will then see location Type of your instance source changed in the scene graph:
3. Merge the two networks together to get something like this:
4. Downstream of the Merge, create an OpScript node and change its applyWhere parameter to at specific location. You will now see a new location parameter at the top of the OpScript node's parameters. The value of the location parameter will determine where in the scene graph the instance array is generated. For this example we're using /root/world/geo/instances
5. In the OpScript node, create a user.pointCloudLocation string parameter and set it to the point cloud’s scene graph location path. Then create a user.instanceSourceLocations string array parameter, with a widget type of Scene Graph Locations, and add your instance source scene graph location paths as values to the array.
For steps on creating user parameters and changing their widget type, please see the Katana User Guide: Adding User Parameters.
6. The following OpScript Lua code, trimmed down from a Pixar example found at Renderman Docs: Instancing, creates an instance array based on a point cloud and instance source locations.
Copy this code into the OpScript node's script parameter to generate the instance array with instances placed at each point of the point cloud.
-- Read the op arguments
local instanceSourceLocations = Interface.GetOpArg("user.instanceSourceLocations")
local pointCloudLocation = Interface.GetOpArg("user.pointCloudLocation"):getValue()
-- Read the point cloud's points
local pointAttr = Interface.GetAttr("geometry.point.P", pointCloudLocation)
local points = pointAttr:getNearestSample(Interface.GetCurrentTime())
-- Create a single location which will generate an array of instances
-- Set type for this location to 'instance array'
Interface.SetAttr('type', StringAttribute('instance array'))
-- This instance array location must point to the instance source locations
-- through the attribute 'geometry.instanceSource'
Interface.SetAttr('geometry.instanceSource', instanceSourceLocations)
-- The indexArray attribute determines which instance source each instance location represents
local indexArray = {}
-- for each instance create an instance index
for i=0,#points/3-1 do
-- For this example, the instances are arbitrarily assigned to an
-- instance source
-- a more stable apporach would be to use an arbitrary attribute
-- on the point cloud to assign an instance source
indexArray[#indexArray+1] = i%instanceSourceLocations:getNumberOfTuples()
end
-- Set index for instance array element
Interface.SetAttr('geometry.instanceIndex', IntAttribute(indexArray, 1))
-- geometry.instanceMatrix
-- Get the transforms from the points
local numTimeSamples = pointAttr:getNumberOfTimeSamples()
local matrixArrayMap = {}
-- to get motion blur on the instances, create an instanceMatrix at each
-- time sample available from the point cloud points attribute
for idx=0,numTimeSamples-1 do
local sampleTime = pointAttr:getSampleTime(idx)
local points = pointAttr:getNearestSample(sampleTime)
-- each instance in array has its own matrix
local matrixArray = {}
local workMatrix = Imath.M44d():toTable()
-- for each instance build a matrix with a mocked up transformation
for i=0,#points/3-1 do
-- grab the points that represent this instance
x = points[3*i+1]
y = points[3*i+2]
z = points[3*i+3]
-- set the translate of the matrix to the points in the point cloud
workMatrix[13] = x
workMatrix[14] = y
workMatrix[15] = z
for j = 1,16 do
matrixArray[#matrixArray+1]=workMatrix[j]
end
end
matrixArrayMap[sampleTime] = matrixArray
end
Interface.SetAttr('geometry.instanceMatrix', DoubleAttribute(matrixArrayMap, #matrixArrayMap))
The above OpScript sets the instance array geometry.instanceSource attribute to the instance source locations set at the user.instanceSourceLocations user parameter. Additionally, the instance array geometry.instanceIndex attribute is set to alternate between the instance sources. Finally, the instance array geometry.instanceMatrix attribute is populated with the translation information from each point in the point cloud.
7. Now the instances have been created, the point cloud is no longer needed and we can use a Prune node to remove it from the scene.
Now you can render to see your instance array:
You can download the completed scene here: instanceArray.katana
Instance Array Workaround to Vary the Assigned Material Per Instance
Unlike other instancing techniques, instance arrays do not have a mechanism to vary the material per instance directly. However, different materials can be used for instances by using instance source locations with varying materials. A benefit of using instance arrays is that all instances are contained in one scene graph location. However, this also means that locating a specific instance is almost impossible, as the individual instances do not appear in the Viewer or the Scene Graph and are only visible in a render of the scene
Please see the Instance Array to Pointcloud section above to generate an instance array. Once the instance array has been created, the material can be varied per batches of instances by varying the materials for each instance source. Use a MaterialAssign node to change the material for an instance source, and the material assign carries to each instance location that utilizes that specific instance source. Simply create MaterialAssign nodes and assign materials to the scene graph locations selected as instance sources.
FURTHER READING
For an instancing overview, see the first resource link below. The other resources below go in-depth on other advanced instancing topics on each instancing method.
- Katana Developer Guide: Instancing
- Q100508: Increasing scene graph cook performance with Hierarchical (Instance Source) Instancing
- Q100517: Increasing scene graph cook performance with Leaf-level (Instance ID) Instancing
- Renderman Documentation: RenderMan 26 Docs - Instancing in Katana
ATTACHMENTS
We're sorry to hear that
Please tell us why