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=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:

	#include 
	class 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:

RtFloat Eval(const RtPoint p)
Eval returns the field value at point p, in object coordinates, at shutter open time.
void GradientEval(RtPoint result, const RtPoint p)
GradientEval stores the field gradient at point p (calculated at shutter open time) into result.
void Range(RtInterval result, const RtPoint corners[8], RtVolumeHandle h)
Range stores into result an interval that bounds the values of the field function inside the convex frustum with the given corners, at shutter open. The base version always stores result[0]=-1e30 and result[1]=1e30. 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)
Motion stores into result how much the point p moves between shutter open and shutter close. The base version, assuming no motion, sets result to (0,0,0).
void BoxMotion(RtBound result, const RtBound b)
BoxMotion stores into result a bounding box at shutter closecorresponding to the argument b. The base version 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 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.


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


 

Pixar Animation Studios
(510) 752-3000 (voice)  (510) 752-3151 (fax)
Copyright © 1996- Pixar. All rights reserved.
RenderMan® is a registered trademark of Pixar.