Hello Sphere of Operators
Let's start by creating a simple Operator that creates one or more spheres of a given radius. Click on create_spheres below to expand and view the code block.
See Creating a Simple Plugin for further information about compiling the operators
The transformation of the spheres depends on how the Operator is cooked. If it's cooked as part of a graph that's connected to the options node the spheres will be at the world origin. If however the Operator is cooked as part of a graph that's connected to a procedural the spheres will inherit the procedural's transform chain.
In the next example, we'll expand on this to create an Operator that also creates spheres, but it also creates a child instance of itself to dynamically keep on making spheres. We'll also explain the benefits of doing this.
Dynamic Creation of Spheres Using Child Operators
Let's build on the sphere Operator above and show a powerful way Operators can be used. An Operator can create another Operator, including itself. When doing so, the Operator can send private information to its children. A practical example would be an Operator authoring a cache such as Alembic, where the Operator can pass the Alembic iterator to child Operators in order to create sub-trees of the hierarchy. The benefit of doing this is twofold. Firstly, the work is split across more threads as each node, including Operators, is processed by a single thread so this effectively chunks the work up across Operators, and consequently threads. Secondly, any hierarchical information is expressed in the Operator graph which helps with maintaining and manipulating the data.
To keep things simple, let's create an Operator that:
- Creates a single sphere and a single instance of itself
- Each child instance keeps doing this until a certain criterion is met, namely:
- Each op creates a random radius and passes it to its child
- Stop if the radius received is below than some threshold or we have exceeded a given maximum number of instances, otherwise, keep going
Check the implementation by expanding the source code for private_ryan below. The Operator doesn't set a transform on the spheres so they are subject to the same rules as described above.
The RyanData structure contains the information that's passed to the child Operators using AiOpSetChildData. As well as the data we must provide a cleanup function using AtOpCleanupChildData.
Scattering and Shading Spheres
Before we describe the operators we're going to use to scatter and shade the spheres generated by private_ryan, we'll look at a simple example that summarises the chain of events. The diagram below shows 4 chained Operators and images, where each image represents what the scene would look like if it was rendered with the respective Operator set as the target Operator.
The first Operator is the built-in Collection Operator which creates a reusable selection expression. Nothing is visible if we render from the collect_spheres Operator. The second Operator is private_ryan which we described above, and as expected we get one or more overlapping spheres at the origin if we render with it as a target. We attach the scatterer Operator (see below) which randomly scatters the spheres if we render from this point. Lastly, we attach a shade Operator (see below) which shades the spheres if rendered from there.
Click on the Arnold test scene below to look at how this is set up using a .ass file.
You can render the scene with different targets using kick, e.g.
Scatterer Operator Example
The scatterer has a selection expression which tells it which objects to scatter (translation only). It randomly places each object that's selected within a region which the user can specify. Remember that from the Operator's perspective it only sees a single object at a time in the cook function.
Shade Operator Example
The shade Operator creates a shader for each selected shape (using the selection expression) and attaches it to the shape. The base color of the shader is a random value which is generated with the shape's hash as a seed.