next up previous contents
Next: Bibliography Up: Blue Moon Rendering Tools Previous: Miscellaneous Tools   Contents


Using BMRT as a ``Ray Server'' for PRMan

This chapter explains how to render scenes using PRMan with ray traced shadows and reflections, using BMRT as an "oracle" to provide answers to computations that PRMan cannot solve. We describe a method of actually stitching the two renderers together using a Unix pipe, allowing each renderer to perform the tasks that it is best at.


PhotoRealistic RenderMan has a Shading Language function called trace(), but since there is no ability in PRMan to compute global visibility, the trace() function always returns 0.0 (black). This is no way to ask for any other global visibility information in PRMan. Though PRMan often can fake reflections and shadows with texture mapping, there are limitations:

  • Environment mapped reflections are only ``correct'' from a single point. Environment mapping a large reflective object has errors (which, to be fair, are often very hard to spot). Mutually reflective objects are a big pain in PRMan.

  • Environment and shadow maps require multiple rendering passes, and require TD time to set up properly.

  • Dealing with shadow maps - selecting resolution, bias, blur, etc. - can be time consuming and still show artifacts in the shadows. Also, shadows cannot motion blur in PRMan, and cannot correctly handle opacity (or color) changes in the object casting a shadow.

  • Refraction is nearly impossible to do correctly, since even when environment mapping is acceptable, PRMan cannot tell the direction that a ray exits a refractive object, since the ``backside'' is not available for ray tracing.

  • The Blue Moon Rendering Tools (BMRT) contains a renderer, rendrib, which is largely compatible with the RenderMan 3.2 specification and supports ray tracing, radiosity, area lights, volumes, etc. It can compute ray traced reflections, shadows, and so on, but is much slower than PRMan for geometry which doesn't require these special features.

Both renderers share much of their input, and to a very large extent can read the same geometry description and shader source code files. (Note: The two renderers each have different formats for stored texture maps and compiled shaders, and support different feature sets.) It's tempting to want to combine the effects of the two renderers, using each for those effects that it achieves well. Several strategies come to mind:

  1. Choosing one renderer or the other based on the project, sequence, or shot. Perhaps a strategy might be to use PRMan most of the time, BMRT if you need radiosity or ray tracing.

  2. Rendering different objects (or layered elements) with different renderers, then compositing them together to form final frames.

  3. Rendering different lighting layers with different renderers, then adding them together. For example, one might render base color with PRMan, but do an ``area light pass'' (or radiosity, or whatever) in BMRT.

All of these approaches have difficulties (though all have been done). Strategy #1 may force you to choose a slow renderer for everything, just because you need a little ray tracing. There may also be problems matching the exact look from shot to shot, if you are liberally switching between the two renderers. Strategies #2 and #3 have potential problems with "registration," or alignment, of the images computed by the renderers. Also, #3 can be very costly, as it involves renders with each renderer.

The attraction of using the two renderers together, exploiting the respective strengths of both programs while avoiding undue expense, is alluring. Larry Gritz has developed a method of literally stitching the two programs together.

Background: DSO Shadeops in PRMan

RenderMan Shading Language has always had a rich library of built-in functions (sometimes called ``shadeops''), already known to the SL compiler and implemented as part of the runtime shader interpreter in the renderer. This built-in function library included math operations (sin, sqrt, etc.), vector and matrix operations, coordinate transformations, etc. It has also been possible to write SL functions in Shading Language itself, however, native SL functions have several limitations.

PRMan 3.8 (and later) allows you to write new built-in SL functions in `C' or `C++'. Writing new shadeops in C and linking them as DSO's has many advantages over writing functions in SL, including:

  • The resulting object code from a DSO shadeop is shared among all its uses in a renderer. In contrast, compiled shader function code is inlined every time the function is called, and thus is not shared among its uses, let along among separate shaders that call the same function.

  • DSO shadeops are compiled to optimized machine code, whereas shader functions are interpreted at runtime. While PRMan has a very efficient interpreter, it is definitely slower than native machine code.

  • DSO shadeops can call library functions from the standard C library or from other third party libraries.

  • Whereas functions implemented in SL are restricted to operations and data structures available in the Shading Language, DSO shadeops can do anything you might normally do in a C program. Examples include creating complex data structures or reading external files (other than textures and shadows). For example, implementing an alternative noise() function, which needs a stored table to be efficient, would be exceptionally difficult in SL, but very easy as a DSO shadeop.

DSO shadeops also have several limitations that you should be aware of:

  • DSO shadeops only have access to information passed to them as parameters. They have no knowledge of ``global'' shader variables such as P, parameters to the shader, or any other renderer state. If you need to access global variables or shader parameters or locals, you must pass them as parameters.

  • DSO shadeops act as strictly point processes. They possess no knowledge of the topology of the surface, derivatives, or the nature of surface grids (in the case of a REYES renderer like PRMan). If you want to take derivatives, for example, you need to take them in the shader and pass them as parameters to your DSO shadeop.

  • DSO shadeops cannot call other builtin shadeops or any other internal entry points to the renderer itself.

Further details about DSO shadeops, including exactly how to write them, are well beyond the scope of these course notes. For more information, please see the RenderMan Toolkit 3.8 User Manual.

How Much Can We Get Away With?

So PRMan 3.8 has a magic backdoor to the shading system. One thing it's good for is to make certain common operations much faster, by compiling them to machine code. But it also has the ability to allow us to write functions which would not be expressible in SL at all -- for example, file I/O, process control or system calls, constructing complex data structures, etc.

How far can we push this idea? Is there some implementation of trace() that we can write as a DSO which will work? Yes! The central idea is to render using PRMan, but implement trace as a call to BMRT. In this sense, we would be using BMRT as an oracle, or a ray server, that could answer the questions that PRMan needs help with, but let PRMan do the rest of the hard work.

BMRT (release 2.3.6 and later) has a ray server mode, triggered by the command line option -rayserver. When in this mode, instead of rendering the frame and writing an image file, BMRT reads the scene file but it just waits for ``ray queries'' to come over stdin. When such queries (specified by a ray server protocol) are received, BMRT computes the results of the query, and returns the value by sending data over stdout.

The PRMan side is a DSO which, when called, runs rendrib and opens a pipe to its process. Thereafter, calls to the new functions make ray queries over the pipe, then wait for the results.

New Functionality

This hybrid scheme effectively adds six new functions that you can call from your shaders:

color trace (point from, vector dir)

Traces a ray from position from in the direction of vector dir. The return value is the incoming light from that direction.

color visibility (point p1, p2)

Forces a visibility (shadow) check between two arbitrary points, retuning the spectral visibility between them. If there is no geometry between the two points, the return value will be (1,1,1). If fully opaque geometry is between the two points, the return value will be (0,0,0). Partially opaque occluders will result in the return of a partial transmission value.

An example use of this function would be to make an explicit shadow check in a light source shader, rather than to mark lights as casting shadows in the RIB stream (as described in the previous section on nonstandard attributes). For example:

    shadowpointlight (float intensity = 1;
    	              color lightcolor = 1;
	              point from = point "shader" (0,0,0);
                      float raytraceshadow = 1;)
        illuminate (from) {
            Cl = intensity * lightcolor / (L . L);
            if (raytraceshadow != 0)
                Cl *= visibility (Ps, from);

float rayhittest (point from, vector dir,
output point Ph, output vector Nh)

Probes geometry from point from looking in direction dir. If no geometry is hit by the ray probe, the return value will be very large (1e38). If geometry is encountered, the position and normal of the geometry hit will be stored in Ph and Nh, respectively, and the return value will be the distance to the geometry.

float fulltrace (point pos, vector dir,
output color hitcolor, output float hitdist,
output point Phit, output vector Nhit,
output point Pmiss, output point Rmiss)

Traces a ray from pos in the direction dir.

If any object is hit by the ray, then hitdist will be set to the distance of the nearest object hit by the ray, Phit and Nhit will be set to the position and surface normal of that nearest object at the intersection point, and hitcolor will be set to the light color arriving from the ray (just like the return value of trace).

If no object is hit by the ray, then hitdist will be set to 1.0e30, hitcolor will bet set to (0,0,0).

In either case, in the course of tracing, if any ray (including subsequent rays traced through glass, for example) ever misses all objects entirely, then Pmiss and Rmiss will be set to the position and direction of the deepest ray that failed to hit any objects, and the return value of this function will be the depth of the ray which missed. If no ray misses (i.e. some ray eventually hits a nonreflective, nonrefractive object), then the return value of this function will be zero. An example use of this functionality would be to combine ray tracing of near objects with an environment map of far objects.

The code fragment below traces a ray (for example, through glass). If the ray emerging from the far side of the glass misses all objects, it adds in a contribution from an environment map, scaled such that the more layers of glass it went through, the dimmer it will be.
    missdepth = fulltrace (P, R, C, d, Ph, Nh, Pm, Rm);
    if (missdepth > 0)
        C += environment ("foo.env", Rm) / missdepth;

float isshadowray ()

Returns 1 if this shader is being executed in order to evaluate the transparency of a surface for the purpose of a shadow ray. If the shader is instead being evaluated for visible appearance, this function will return 0. This function can be used to alter the behavior of a shader so that it does one thing in the case of visibility rays, something else in the case of shadow rays.

float raylevel ()

Returns the level of the ray which caused this shader to be executed. A return value of 0 indicates that this shader is being executed on a camera (eye) ray, 1 that it is the result of a single reflection or refraction, etc. This allows one to customize the behavior of a shader based on how ``deep'' in the reflection/refraction tree.

How to use it

Using PRMan as a ray tracer is straightforward:

  1. Use these functions in your shaders. In any shader that uses the functions, you should:

         #include "rayserver.h"

    If you inspect rayserver.h (in the examples directory), you'll see that most the functions described above are really macros. When compiling with BMRT's compiler, the functions are unchanged (all three are actually implemented in BMRT). But when compiling with PRMan's compiler, the macros transform their arguments to world space and call a function called rayserver().

  2. Compile the shaders with both BMRT and PRMan's shader compilers. When compiling for PRMan, make sure that the DSO (in the BMRT lib directory) is in your include path (-I).

  3. Render the file using the frankenrender script that comes with BMRT. This is a Perl script that sets up the environment that controls the ray server, and passes the correct arguments to both PRMan and BMRT. Just look at the script for more details on how it works and what arguments are valid.

    If you are rendering the same geometry with both renderers, just use frankenrender in the same way as you would use prman or rendrib:

        frankenrender teapots.rib

    If you want to give separate RIB files to each renderer, use the -prman and -bmrt flags:

      frankenrender common.rib -bmrt bmrt.rib -prman prman.rib

That's it!

Pros and Cons

The big advantage here is that you can render most of your scene with PRMan, using BMRT for tracing individual rays on selected objects or calculating shadows for selected lights. This is much faster than rendering in BMRT, particularly if you only tell the ray tracer about a subset of the scene that you want in the shadows or reflections. The following effects are utterly trivial to produce with this scheme:

  • Ray cast shadows, including shadows that correctly respond to color and opacity of occluding objects. Moving objects can cast correct motion-blurred shadows.

  • Correct reflections, including motion blur.

  • Real refraction for glass, water, etc.

  • No setup time or multi-pass rendering for these effects.

The big disadvantage is that it requires two renderers to both have the scene loaded at the same time. This can be alleviated somewhat by reducing the scene that the ray tracer sees, or by telling the ray tracer to use a significantly reduced tessellation rate, etc. But still, it's a significant memory hit compared to running PRMan alone.

All of the usual considerations about compatibility between the two renderers apply. Be particularly aware of new PRMan primitives and SL features not currently supported by BMRT, texture file format differences, results of noise() functions, etc.

All of the usual considerations about compatibility between the two renderers apply. Be particularly aware of new PRMan primitives and SL features not currently supported by BMRT, texture file format differences, results of noise() functions, etc.

Note that by default, all rays will be traced from the positions at the shutter open time.

Efficiency Tips

Here are several tips to help you speed up the ray server.

  • Use the -prman and -bmrt flags to give separate RIB files to each renderer, eliminating the objects which do not need to be visible in reflections or refractions from the file for rendrib. Where this is not possible, at least use Attribute "render" "visibility" to make objects invisible in reflections if they are not needed to be seen in reflections (and similarly for shadows).

  • Be sure that your max ray recursion level (Option "render" "max_raylevel") is set as low as possible (the default is 4, but you may be able to get away with as little as 1 or 2 if you don't have much glass or mutual reflection.

  • It's possible that objects which are only visible in reflections or refractions can be tessellated even more coarsely than usual. Try:

    Attribute "render" "patch_multiplier" [n]}

    The -rayserver mode automatically sets n to 0.5, indicating that patches should be diced only half as finely when serving rays as when rendering whole frames. Try reducing n to 0.25 or even lower, to increase speed and decrease memory use. Make n as low as you can get it without seeing visible artifacts.

next up previous contents
Next: Bibliography Up: Blue Moon Rendering Tools Previous: Miscellaneous Tools   Contents
Exluna, Inc.