Custom Geometry

Custom Geometry

When RenderMan for Maya goes to render and encounters a plugin shape node in Maya, the shape is skipped unless you tell RfM what to do with it. This example demonstrates how you can make RfM recognize your custom shape and render it with a RenderMan procedural plugin. As our custom shape, we'll use the quadricShape from Maya's devkit, which is a plugin that draws your choice of a sphere, cylinder, disk, or partial disk. There are attributes on the node, like radius, height, and sweepangle which will be interpreted.

1 - CREATE THE quadricShape

On your platform of choice, locate the quadricShape plugin in the Maya devkit, and compile it. Check out the Maya documentation for information on compiling the plugin.

Once you have the quadricShape plugin, open the plug-in manager in Maya and load it up. Then create a shape, like this, in the Script Editor:

createNode quadricShape;
select -r quadric1;
sets -e -forceElement initialShadingGroup;

You should see a cylinder in the scene, and since it's initially created without a shading group, we've attached the default shading group. Try changing some of the attributes on the node. Notice that the quadric is missing from renders.

2 - MEL SCRIPTING

Next, some MEL scripting is necessary to tell RfM about the procedural plugin, which we'll build later on.

Set up a searchpath for procedural plugins

It'll be handy to refer to our procedural plugin just by name, rather than with a full path. This MEL procedure outputs an RiOption that sets up a search path for plugins in the RenderMan for Maya devkit. You can paste it into the Script Editor, for now, or install it among your MEL scripts.

global proc rmanOutputProceduralSearchpath()
{
   // This proc sets up a searchpath for procedural plugins.

   // On windows its necessary to load a different version of the procedural
   // plugin depending on whether it'll be running internally in RfM or
   // in the standalone prman renderer.  We can find out if this script is
   // being called during ribgen or internal rendering, and
   // refer to a different procedural path based on that.
   string $subdir = "";
   if( `about -nt` ) {
       if( `eval("rman ctxIsRib")` ) {
           $subdir = "prman_plugins";
       } else {
           $subdir = "rfm_plugins";
       }
   }

   // Change this path to correspond to the location where you put your compiled plugin.
   // Note on windows the path needs to be in UNC format.
   string $pluginpath = "//C/Program Files/Pixar/RenderMan-Studio-1.0-Maya8.5/devkit/custom_geometry/";
   RiOption "searchpath" "string procedural" ($pluginpath+$subdir);
}
  • Important

    The path referenced in the examples above is Windows-specific. OS X and Linux users should change it accordingly.

We can cause the searchpath option to be output into the RIB stream at the appropriate time, by invoking this procedure in the Default RiOptions MEL script, which is located in the Render Settings under the Advanced tab. Paste a call to the procedural above in the Default RiOptions MEL field:

rmanOutputProceduralSearchpath

Set up the RiProcedural Call

When RfM encounters the quadricShape while rendering, we'll run a script that inspects some of the attributes on the node and issues a RiProcedural call with an appropriate bounding box and argument list. The following proc does that. You can paste it into the Script Editor, for now, or install it with your MEL scripts.

global proc rmanOutputQuadricProcedural()
{
   // Find out which object this script is associated with
   string $object = `rman ctxGetObject`;
   if( `nodeType $object` != "quadricShape" ) {
       warning("rmanOutputQuadricProcedural can only operate on nodes of type quadricShape\n");
       return;
       }

       // These are the attributes from the quadricShape node that our
       // quadric procedural plugin will interpret.
       int $shapetype = `getAttr ($object+".shapeType")`;
       float $radius1 = `getAttr ($object+".radius1")`;
       float $radius2 = `getAttr ($object+".radius2")`;
       float $height = `getAttr ($object+".height")`;
       float $startAngle = `getAttr ($object+".startAngle")`;
       float $sweepAngle = `getAttr ($object+".sweepAngle")`;

       // Assemble the values of the attributes into an arg list,
       // to pass to the procedural
       string $args = ($shapetype + " " + $radius1 + " " + $radius2 + " " + $height + " " + $startAngle + " " + $sweepAngle);


       // The RiProcedural needs to be supplied a bounding box, and since
       // the quadricShape node takes care of updating its bounding box,
       // we'll use that.  Note the RiProcedural needs its bounding box
       // to be expressed in centimeters.
       string $curUnit = `currentUnit -q -linear`;
       currentUnit -linear "cm";  // temporarily change to centimeters
       float $bbSize[3] = `getAttr ($object + ".boundingBoxSize")`;
       float $bbMin[3], $bboxMax[3];
       $bbMin[0] = -$bbSize[0]/2;
       $bbMin[1] = -$bbSize[1]/2;
       $bbMin[2] = -$bbSize[2]/2;
       $bbMax[0] = $bbSize[0]/2;
       $bbMax[1] = $bbSize[1]/2;
       $bbMax[2] = $bbSize[2]/2;
       currentUnit -linear $curUnit;

       // Issue the RiProcedural call, passing the bounding box and arg list
       RiProcedural "DynamicLoad" "quadricProcedural.dll" $bbMin[0] $bbMax[0] $bbMin[1] $bbMax[1] $bbMin[2] $bbMax[2] $args;
}
  • Important

    The .dll file referenced in the example above is Windows-specific. OS X and Linux would use .so.

RfM needs to be told to invoke this procedure when it encounters the quadricShape node. That's accomplished with a preShape MEL script. Select the quadricShape node, and in the Attribute Editor make sure the quadricShape tab is selected. Open the RenderMan Attributes manager via Attributes->RenderMan->Manage Attributes. From the list choose the Pre Shape MEL attribute and add it. The attribute shows up at the bottom of the Attribute Editor. Paste a call to the above procedural as its value:

rmanOutputQuadricProcedural

3 - THE QUADRIC PROCEDURAL

Now the Maya scene is set up. Next, we'll build the procedural quadric plugin, which will be dynamically loaded when its bounding box is encountered during rendering.

The code and makefile for the quadric procedural is located in the RenderMan for Maya devkit, which is included as part of your RenderMan for Maya installation, in the devkit directory.

Set up your build environment

Proper compilation of the quadric procedural assumes you have two variables set in your environment: RMANTREE and RMSTREE. RMSTREE should be set to the path of your RMS installation, and RMANTREE can be set to either the location of your RenderMan Pro Server installation or Path/to/RMS/rmantree.

Build the plugin

Open a shell and change to the directory of the RMS devkit. There's an example makefile for Windows, which you can execute by typing:

make -f Makefile.windows

Or you can compile using the standard RenderMan compilation method. Use the standard C++ compiler to generate an object (.o/.obj) file, then generate a shared object (.so/.dll) file from the object file. Remember that, though using C++, you must use C style linkage. You also must ensure that your C++ compiler and libraries are compatible with the compiler and runtime libraries used by PRMan (gcc for Linux and OS-X and Microsoft Visual C for Windows). Here are example commands for building the plugin on several architectures:

Linux:
g++ -fPIC -I$RMANTREE/include -c quadricProcedural.cpp
g++ -shared quadricProcedural.o -o quadricProcedural.so
Mac OS-X:
g++ -I$RMANTREE/include -c quadricProcedural.cpp
setenv MACOSX_DEPLOYMENT_TARGET 10.3
g++ -bundle -undefined dynamic_lookup quadricProcedural.o -o quadricProcedural.so
Windows:
cl -nologo -MT -I%RMANTREE%\include -c quadricProcedural.cpp
link -nologo -DLL -out:quadricProcedural.dll quadricProcedural.obj %RMANTREE%\lib\prman.lib

quadricProcedural.cpp

The code for the quadric is in quadricProcedural.cpp, located in the devkit directory. Procedural plugins must implement three entrypoints: ConvertParameters, Subdivide, and Free.

You'll see prototypes for these exported at the beginning of the file:

extern "C" {
       export RtPointer ConvertParameters(RtString paramstr);
       export RtVoid Subdivide(RtPointer data, RtFloat detail);
       export RtVoid Free(RtPointer data);
}

The ConvertParameters method takes the arg string that we passed in the RiProcedural call and stashes the values in a data structure, which it returns a blind pointer to. Our data goes into a struct with a value for each attr:

enum QuadricsType {
       kCylinder,
       kDisk,
       kPartialDisk,
       kSphere
};

struct quadricsData {
   QuadricsType shapeType;
   RtFloat radius1;
   RtFloat radius2;
   RtFloat height;
   RtFloat startAngle;
   RtFloat sweepAngle;
};

export RtPointer ConvertParameters(RtString paramstr) {
   struct quadricsData* data;
   char* parameters;
   char* token, *tokenstate;
   int i, j;

   parameters = strdup(paramstr);

data = reinterpret_cast<struct quadricsData*>(malloc(sizeof(struct quadricsData)));

token = strtok_r(parameters, " ", &tokenstate);
   data->shapeType = static_cast<QuadricsType>(atoi(token));

   token = strtok_r(0, " ", &tokenstate);
   data->radius1 = atof(token);

   token = strtok_r(0, " ", &tokenstate);
   data->radius2 = atof(token);

   token = strtok_r(0, " ", &tokenstate);
   data->height = atof(token);

   token = strtok_r(0, " ", &tokenstate);
   data->startAngle = atof(token);

   token = strtok_r(0, " ", &tokenstate);
   data->sweepAngle = atof(token);

   free(parameters);
   return (RtPointer)(data);
}

The Subdivide method is where the quadric shape gets rendered. The cylinder, disk, and partial disk can each be represented by an RiHyperboloid primitive, and the sphere by an RiSphere primitive. These look somewhat different than the quadricShape in the Maya render view because it uses OpenGL primitive calls. RenderMan's primitives aren't polygonal, so they'll appear smooth.

export RtVoid Subdivide(RtPointer datap, RtFloat detail) {
       struct quadricsData* data = reinterpret_cast<struct quadricsData*>(datap);
       int i, j;

       RiTransformBegin();

       RtPoint p1;
       RtPoint p2;
       p1[0] = data->radius1;
       p2[0] = data->radius2;
       p1[1] = 0;
       p1[2] = 0;
       p2[1] = 0;
       p2[2] = 0;

       switch (data->shapeType) {
       case kCylinder:
       p2[2] = data->height;
       RiHyperboloid(p1, p2, 360, RI_NULL);
       break;
       case kDisk:
       RiHyperboloid(p1, p2, 360, RI_NULL);
       break;
       case kPartialDisk:
       RiRotate(90, 0, 0, 1);
       RiRotate(-data->startAngle, 0, 0, 1);
       RiHyperboloid(p1, p2, -data->sweepAngle, RI_NULL);
       break;
       case kSphere:
       RiSphere(data->radius1, -data->radius1, data->radius1, 360, RI_NULL);
       break;
       }
       RiTransformEnd();
}

And finally, the Free method frees up the struct allocated in the ConvertParameters method.

export RtVoid Free(RtPointer datap) {
       struct quadricsData* data = reinterpret_cast<struct quadricsData*>(datap);
       free(data);
}

4 - RENDER!

At this point, you should be able to render with RenderMan for Maya. If you don't see the quadric in the render, the most likely problem is that the searchpath isn't set correctly. You should see the quadric shape in both internal RfM renders and spooled RIB renders, assuming that the location of the quadricProcedural is accessible from the machine where the spooled render is happening.

5 - BUT CAN WE MOTION BLUR...

So you want to motion blur your custom shape, eh? When the transform for the custom shape node is animated, motion blur happens automatically. Your procedural doesn't need to do anything special. However, if your shape node deforms and you want to motion blur that, you'll need to do some extra work.

Cache Shape MEL Script

Normally, when RfM encounters deforming shapes, and motion blur is enabled, it will cache the state of the shape at Shutter Open and Shutter Close times. Then at render time both samples are output, surrounded by a motion block. RfM allows you to cache your own shape during those caching passes. You can specify a caching MEL script by adding an attribute called Cache Shape MEL to your shape. You may also need to add the Evaluation Frequency attribute, and change it to Frame. That lets RfM know your shape is deforming, even if its usual method of looking for upstream animated nodes isn't adequate for detecting deformation of the custom shape.