Procedural RIB
As if RIB archives were not exciting enough, RenderMan also allows you to generate RIB dynamically at render time
using procedural primitives. This means instead of having to model very detailed objects (such as trees, foliage, or
hundreds of buildings), you could write a program to procedurally model these automatically and pass the program's
parameters to RenderMan to generate coherent RIB at render time.
For instance, a TD could procedurally model an entire city at render time by writing a program which takes, as inputs,
grayscale maps for city density, roads, etc. Then outputs RIB of an entire city to RenderMan, on-the-fly. This facility
enables you to generate heavy scenes without the need to model the whole scene, just the parts that fit together. The
limitation is that things that are procedurally modeled must be created by a program without human intervention.
There are two methods for creating RIB on-the-fly: a Run Program and a Dynamic Shared Object (DSO). The DSO is the
faster of the two as it sits inline with the renderer and written in C. A Run Program can be written in any language that
can communicate over stdin (python, perl, shell scripts, etc). These can be fast to write, but relatively slow to execute,
depending on how much work they have to do.
Run Program
RIB syntax: Procedural "RunProgram" [ "perl gencan.pl" "" ] [ -1 1 -1 1 0 6 ]
From the RenderMan Documentation:
The built-in procedural RiProcRunProgram will run an external helper program, capturing its output and interpreting it as
RIB input. The data parameter consists of a pointer to a string array. The first element of the array is the name of the
program to run (including any command line arguments). The second element is an argument string to be written to the
standard input stream of the helper program.
The helper program generates geometry on-the-fly in response to procedural primitive requests in the RIB stream. Each
generated procedural primitive is described by a request to the helper program, in the form of an ASCII datablock, that
describes the primitive to be generated. This datablock can be anything that is meaningful and adequate to the helper
program: such as a sequence of floating-point numbers, a filename, or snippet of code in an interpreted modeling
language. In addition, the renderer supplies the detail size of the primitives bounding box, so the generating program
can decide what to generate based on how large the object will appear on-screen.
The generation program reads the request datablocks on its standard input stream, then emits RIB commands on its
standard output stream. These RIB streams are read into the renderer as if read from a file (like ReadArchive) and may
include any standard RenderMan attributes and primitives (including procedural primitive calls to itself or other helper
programs). As long as any procedural primitives exist in the rendering database, requiring the same helper program for
processing, the socket connection to the program will remain open. This means that the program should be written with a
loop which accepts any number of requests and generates a RIB snippet" for each one.
DSOs
RIB syntax: Procedural "DynamicLoad" [ "mydso.so" "" ] [ -1 1 -1 1 0 6 ]
From the RenderMan Documentation:
A more efficient method for accessing subdivision routines is to write them as dynamic shared objects, DSOs (on
Windows dynamically linked libraries, or DLLs). Then dynamically load them into the renderer, executable at run-time. In
this case, you write your subdivision and free routines in C, exactly as if you were writing them to be linked into the
renderer using the C RiProcedural interface. DSOs are compiled with special compiler options to make them run-time
loadable and are specified in the RIB file by the name of the shared object file. The renderer will load the DSO the first
time the subdivision routine must be called. From then onwards, it is called as statically linked and executes as fast too.
DSOs are more efficient than external programs because they avoid the overhead of interprocess communication.
When writing a procedural primitive DSO, you must create three specific public subroutine entry points, named
Subdivide, Free, and !ConvertParameters. Subdivide is a standard RiProcedural() primitive subdivision routine, taking a
blind data pointer to be subdivided and a floating-point detail to estimate screen size. Free is a standard RiProcedural
primitive free routine, taking a blind data pointer to be released. ConvertParameters is a special routine that takes a
string and returns a blind data pointer. It will be called exactly once for each DynamicLoad procedural primitive in the RIB
file. Its job is to convert a printable string version of the progenitor's blind data (which must be in ASCII in the RIB file)
into something that the Subdivide routine will accept.