Prev | Next



Implicit Field Plugins in PRMan

January 2006


1 Introduction

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).


2 RI Additions

RiBlobby's code array now understands an additional primitive field opcode with numeric value 1004. The new opcode takes five operands:

  1. The index in the strings array of the filename of the plugin.
  2. The number of floating point arguments to pass to the plugin.
  3. The index in the floats array of the start of the block of floating point arguments.
  4. The number of string arguments to pass to the plugin.
  5. The index in the strings array of the start of the block of string arguments.

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.


3 The Plugin Interface

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=2;
specifying that this plugin implements version 2 of the field plugin interface. 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:

    #include <ri.h>

    class ImplicitVertexValue{
    private:
        /* inhibit copying */
        ImplicitVertexValue(const ImplicitVertexValue &);
        ImplicitVertexValue &operator=(const ImplicitVertexValue &);
    public:
        ImplicitVertexValue(){}
        virtual void GetVertexValue(RtFloat *result, const RtPoint p)=0;
        virtual void GetVertexValueMultiple(int neval, RtFloat *result, 
                                           int resultstride, const RtPoint *p) {
            for (int i = 0; i &nt; neval; ++i) {
                GetVertexValue(result, *p++);
                result += resultstride;
            }
        }
    };
    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[4] are the lower bounds on x, y and z, and bbox[1], bbox[3] and bbox[5] are the upper bounds.)

The methods are:

RtFloat Eval(const RtPoint p)
returns the field value at point p, in object coordinates, at shutter open time.
void GradientEval(RtPoint result, const RtPoint p)
stores the field gradient at point p (calculated at shutter open time) into result.
void Range(RtInterval result, const RtPoint corners[8], RtVolumeHandle h)
stores into result the bounds of the field function values inside the convex frustum with the given corners, at shutter open. While implementing this method is optional, it can provide an accurate understanding of the underlying field function to prman, and can have a dramatic impact on execution speed. The base-class implementation always stores result[0]=-1e30 and result[1]=1e30 and this results in exhaustive evaluation of the region. The volume handle h identifies the volume. The same value will later be passed to a call of VolumeCompleted.
void Motion(RtPoint result, const RtPoint p)
stores into result how much the point p moves between shutter open and shutter close. The base-class implementation assumes no motion and sets result to (0,0,0).
void BoxMotion(RtBound result, const RtBound b)
stores into result a bounding box at shutter close corresponding to the argument b. The base-class implementation just copies b to result.
void VolumeCompleted(RtVolumeHandle h)
This is a courtesy callback, hinting that prman has finished processing all points inside the volume with the given handle, so that the plugin can discard data that it no longer needs. Using VolumeCompleted is a little tricky: prman calls Range with a particular RtVolumeHandle when it starts to work on a part of the level-set, and calls VolumeCompleted with the same handle when it's done. But it may in the interim have subdivided and called Range on smaller contained volumes in which it may maintain an interest after it has called VolumeCompleted on the parent volume. The handle passed to VolumeCompleted may be reused in a subsequent call to Range, but it will never ambiguously identify two volumes in which prman simultaneously maintains an interest.
ImplicitVertexValue *CreateVertexValue(const RtToken name, int nvalue)
informs the plugin of a vertex variable declaration, asking that the plugin provide prman with an entry point that evaluates the variable. Arguments are the name of a vertex variable and the number of float components it has, 1 for scalars or 3 for point types. You are requested to allocate (using C++'s new operator) and return an instance of a subclass of ImplicitVertexValue. prman will call delete on the result when it is done with it. If name is unknown to the plugin, the call should return NULL. (The base-class implementation always returns NULL.)

The ImplicitVertexValue class has two virtual methods. GetVertexValue(RtFloat *result, const RtPoint p), is required and which the plugin should define to store in result the value of the named vertex variable, evaluated at point p. On entry the result parameter has the value given to the vertex variable in the RiBlobby call. The second virtual method is optional. GetVertexValueMultiple was introduced in ImplicitFieldVersion 2 to offer an optimization opportunity to field plugin developers. The default implementation simply loops over multiple input points invoking GetVertexValue. Should it be worthwhile to share expensive computations across multiple calls to GetVertexValue plugin developers can override this default implementation to achieve potentially substantial speedups.


4 Compiling Your Plugin

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.


5 Field Plugins Supplied with Pixar's RenderMan

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.


6 An Example

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

One Implicit Cube

Just for amusement, here is another picture made using the same cube in various more complicated objects:

Many Implicit Cubes

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



Prev | Next


Pixar Animation Studios
Copyright© Pixar. All rights reserved.
Pixar® and RenderMan® are registered trademarks of Pixar.
All other trademarks are the properties of their respective holders.