Implicit Field Plugins in PRManJanuary 2006 |
|
The purpose of this application note is to introduce a new interface that lets users describe implicit fields ("level set" surfaces) in a plugin. The interface comprises two parts: changes to the RiBlobby RenderMan Interface call (with corresponding RIB changes), and a C++ class interface for the plugins. This description assumes familiarity with Section 5.6 of the RenderMan Interface specification (version 3.2).
RiBlobby's code array now understands an additional primitive field opcode with numeric value 1004. The new opcode takes five operands:
Plugin filenames are looked up using the same path used for RiProcDynamicLoad (i.e. use RiOption("searchpath", "procedural", ...); to set the path.)
Because Implicit Field plugins act as primitive fields under RiBlobby they share all of the flexibility of existing primitive field opcodes. They can all blend with each other and have vertex values in exactly the same ways.
For efficiency, PRMan's blobby implementation depends on getting quite a lot of information from the primitives, so this interface is fairly elaborate. It is possible to get away with just writing a constructor and methods that compute the field value and its gradient; the price you pay is efficiency and accuracy.
Every Implicit Field plugin must have an entry point declared:
extern "C" ImplicitField *ImplicitFieldNew( int nfloat, const RtFloat *float0, const float *float1, int nstring, const RtString *string);
The arguments are just the floating point and string parameters specified when the plugin was mentioned in the code of the RiBlobby call. For motion blur purposes, float0 and float1 give the floating point values at shutter open and shutter close. If RiBlobby was not called in a motion block, they are identical. It is guaranteed that no argument data will be freed during the life of the plugin. In addition, the plugin must define a variable:
extern "C" const int ImplicitFieldVersion=1;specifying that this plugin implements version 1 of the field plugin interface. (There are no other versions, but there may be in the future.) The FIELDCREATE manifest, in ImplicitField.h defines ImplicitFieldVersion appropriately and emits the function header for ImplicitFieldNew. (The example below indicates usage.)
The return value of ImplicitFieldNew is an instance of a subclass of class ImplicitField, whose definition is in ImplicitField.h in the PRMan include directory:
#includeclass ImplicitVertexValue{ private: /* inhibit copying */ ImplicitVertexValue(const ImplicitVertexValue &); ImplicitVertexValue &operator=(const ImplicitVertexValue &); public: ImplicitVertexValue(){} virtual void GetVertexValue(RtFloat *result, const RtPoint p)=0; }; class ImplicitField{ public: /* * bounding box at t=0, * ImplicitField::ImplicitField fills this in */ RtBound bbox; private: /* inhibit copying */ ImplicitField(const ImplicitField &); ImplicitField &operator=(const ImplicitField &); public: ImplicitField(){} virtual ~ImplicitField(){} virtual RtFloat Eval(const RtPoint p) = 0; virtual void GradientEval(RtPoint result, const RtPoint p)=0; virtual void Range(RtInterval result, const RtPoint corners[8], const RtVolumeHandle h){ result[0]=-1e30; result[1]=1e30; } virtual void Motion(RtPoint result, const RtPoint p){ result[0]=0.; result[1]=0.; result[2]=0.; } virtual void BoxMotion(RtBound result, const RtBound b){ for(int i=0;i!=6;i++) result[i]=b[i]; } virtual void VolumeCompleted(const RtVolumeHandle h){} virtual ImplicitVertexValue *CreateVertexValue( const RtToken name, int nvalue){ return 0; } };
The bbox field must be filled in during initialization with a bounding box (in the object coordinate system active at the call to RiBlobby) outside which the field value is guaranteed to be identically zero. (Type RtBound is defined in ri.h to be an array of 6 floats. bbox[0], bbox[2] and bbox[3] are the lower bounds on x, y and z, and bbox[1], bbox[3] and bbox[5] are the upper bounds.)
The methods are:
The ImplicitVertexValue class has a single virtual method, GetVertexValue(RtFloat *result, const RtPoint p), which the plugin should define to store in result the value of the named vertex variable, evaluted at point p. On entry the result parameter has the value given to the vertex variable in the RiBlobby call.
Using g++ on Linux, if your plugin is in a file called field.cpp, you can compile it by typing the following (augmented, of course by whatever other flags and filenames your code needs to compile):
g++ -I$RMANTREE/include -fPIC -shared -o field.so field.cpp
Other compilers and other systems will doubtless require other procedures.
Version 13 of Pixar's RenderMan ships with two implicit field plugins, one to extract level sets from Maya fluid cache files and the other to produce segment blobs with variable-radius cross sections.
Here is a plugin for a field function whose level sets are cubes centered at the origin. The field cross-section is the same as that of RiBlobby's sphere primitives, to make it blend nicely in compound objects.
#include <ImplicitField.h> class Cube: public ImplicitField{ public: Cube(); virtual ~Cube(); virtual RtFloat Eval(const RtPoint p); virtual void GradientEval(RtPoint grad, const RtPoint p); virtual void Range(RtInterval r, const RtPoint corners[8], RtVolumeHandle h); }; Cube::Cube(){ bbox[0]=-1.; bbox[1]=1.; bbox[2]=-1.; bbox[3]=1.; bbox[4]=-1.; bbox[5]=1.; } /* * This is the same field falloff (as a function of the * square of distance from the center) that RiBlobby uses * for its primitive blobs. * It has * geoff(-1)=0 geoff'(-1)=0 geoff"(-1)=0 * geoff( 0)=1 geoff'(0)=0 * geoff( 1)=0 geoff'( 1)=0 geoff"( 1)=0 */ static float geoff(float r2){ if(r2>=1.f) return 0.f; return ((3.f-r2)*r2-3.f)*r2+1.f; } /* * d geoff(r2) * ----------- * d r2 */ static float dgeoff(float r2){ if(r2>=1.f) return 0.f; return (6.f-3.f*r2)*r2-3.f; } /* * geoff(max(x^2, y^2, z^2)) */ float Cube::Eval(const RtPoint p){ RtPoint sq; float r2; sq[0]=p[0]*p[0]; sq[1]=p[1]*p[1]; sq[2]=p[2]*p[2]; if(sq[0]>sq[1]) r2=sq[0]>sq[2]?sq[0]:sq[2]; else r2=sq[1]>sq[2]?sq[1]:sq[2]; return geoff(r2); } void Cube::GradientEval(RtPoint grad, const RtPoint p){ RtPoint sq; grad[0]=0.; grad[1]=0.; grad[2]=0.; sq[0]=p[0]*p[0]; sq[1]=p[1]*p[1]; sq[2]=p[2]*p[2]; if(sq[0]>sq[1]){ if(sq[0]>sq[2]) grad[0]=2.*p[0]*dgeoff(sq[0]); else grad[2]=2.*p[2]*dgeoff(sq[2]); } else if(sq[1]>sq[2]) grad[1]=2.*p[1]*dgeoff(sq[1]); else grad[2]=2.*p[2]*dgeoff(sq[2]); } void isq(RtInterval sq, RtInterval x){ if(x[0]>=0){ sq[0]=x[0]*x[0]; sq[1]=x[1]*x[1]; } else if(x[1]<=0){ sq[0]=x[1]*x[1]; sq[1]=x[0]*x[0]; } else{ sq[0]=0; sq[1]=-x[0]>x[1]?x[0]*x[0]:x[1]+x[1]; } } void imax(RtInterval max, RtInterval a, RtInterval b){ max[0]=b[0]>a[0]?b[0]:a[0]; max[1]=b[1]>a[1]?b[1]:a[1]; } void igeoff(RtInterval g, RtInterval r2){ g[0]=geoff(r2[1]); g[1]=geoff(r2[0]); } void Cube::Range(RtInterval val, const RtPoint corners[8], RtVolumeHandle h){ RtInterval r, x, y, z, xsq, ysq, zsq, maxxy, maxxyz; int i; x[0]=x[1]=corners[0][0]; y[0]=y[1]=corners[0][0]; z[0]=z[1]=corners[0][0]; for(i=0;i!=8;i++){ if(corners[i][0]<x[0]) x[0]=corners[i][0]; if(corners[i][0]>x[1]) x[1]=corners[i][0]; if(corners[i][1]<y[0]) y[0]=corners[i][1]; if(corners[i][1]>y[1]) y[1]=corners[i][1]; if(corners[i][2]<z[0]) z[0]=corners[i][2]; if(corners[i][2]>z[1]) z[1]=corners[i][2]; } isq(xsq, x); isq(ysq, y); isq(zsq, z); imax(maxxy, xsq, ysq); imax(maxxyz, maxxy, zsq); igeoff(val, maxxyz); } Cube::~Cube(){} FIELDCREATE{ return new Cube(); }
Here is a RIB file that uses the plugin, and the resulting image:
FrameBegin 0 Display "cube.tif" "tiff" "rgba" Quantize "rgba" 0 0 0 0 Format 200 200 1 Clipping 1e-3 1e5 Projection "perspective" "fov" 3 LightSource "distantlight" 1 "from" [-7 12 -14] "intensity" [0.7] LightSource "distantlight" 2 "from" [7 10 -14] "intensity" [0.7] LightSource "ambientlight" 3 "intensity" [0.1] Translate 0 0 27 WorldBegin Imager "background" "color" [1 1 1] Color [.9 .3 .2] Surface "plastic" ShadingInterpolation "smooth" Sides 2 Rotate 10 0 1 0 Rotate 10 1 1 1 Blobby 1 [ 1004 0 0 0 0 0 ] [0] ["cube.so"] WorldEnd FrameEnd
![]() |
Just for amusement, here is another picture made using the same cube in various more complicated objects:
![]() |
And the RIB file that made them:
FrameBegin 0 Display "manycubes.tif" "tiff" "rgba" Quantize "rgba" 0 0 0 0 Format 600 600 1 Clipping 1e-3 1e5 Projection "perspective" "fov" 13 LightSource "distantlight" 1 "from" [-7 12 -14] "intensity" [0.7] LightSource "distantlight" 2 "from" [7 10 -14] "intensity" [0.7] LightSource "ambientlight" 3 "intensity" [0.1] Translate 0 0 27 WorldBegin Imager "background" "color" [1 1 1] Color [.9 .3 .2] Surface "plastic" ShadingInterpolation "smooth" Sides 2 TransformBegin Translate -1.3 -1.3 0 Rotate 10 0 1 0 Rotate 10 1 1 1 Blobby 2 [ 1001 0 1004 0 0 0 0 0 4 0 1 ] [ 2.4 0 0 0 0 2.4 0 0 0 0 2.4 0 0 0 0 1 ] ["cube.so"] TransformEnd TransformBegin Translate 1.3 -1.3 0 Rotate 10 0 1 0 Rotate 10 1 1 1 Blobby 5 [ 1004 0 0 0 0 0 1001 0 1001 16 1001 32 1001 48 0 5 0 1 2 3 4 ] [ 1 0 0 0 0 1 0 0 0 0 1 0 0.6 -0.6 0.6 1 1 0 0 0 0 1 0 0 0 0 1 0 -0.6 0.6 0.6 1 1 0 0 0 0 1 0 0 0 0 1 0 0.6 0.6 -0.6 1 1 0 0 0 0 1 0 0 0 0 1 0 -0.6 -0.6 -0.6 1 ] ["cube.so"] TransformEnd TransformBegin Translate -1.3 1.3 0 Rotate 10 0 1 0 Rotate 10 1 1 1 Blobby 6 [ 1004 0 0 0 0 0 1001 0 1001 16 1001 32 1001 48 1001 64 0 5 0 1 2 3 4 4 6 5 ] [ 1 0 0 0 0 1 0 0 0 0 1 0 0.6 -0.6 0.6 1 1 0 0 0 0 1 0 0 0 0 1 0 -0.6 0.6 0.6 1 1 0 0 0 0 1 0 0 0 0 1 0 0.6 0.6 -0.6 1 1 0 0 0 0 1 0 0 0 0 1 0 -0.6 -0.6 -0.6 1 .75 0 0 0 0 .75 0 0 0 0 .75 0 0 0 0 1 ] ["cube.so"] TransformEnd TransformBegin Translate 1.3 1.3 0 Rotate 10 0 1 0 Rotate 10 1 1 1 Blobby 2 [ 1004 0 0 0 0 0 1001 0 4 0 1 ] [ .75 0 0 0 0 .75 0 0 0 0 .75 0 0 0 0 1 ] ["cube.so"] TransformEnd WorldEnd FrameEnd
Pixar
Animation Studios
|