Using RenderMan Shading Language Pipeline Methods: What, Why, and When

Using RenderMan Shading Language Pipeline Methods: What, Why, and When

September 2012


RenderMan Shading Language (RSL) introduced the pipeline methods diffuselighting() and specularlighting() in PRMan 16. These methods, invoked by the renderer in much the same way as lighting() provide a mechanism by which PRMan can obtain partial shading results. By breaking apart the traditional pipeline or atomizing it, PRMan is able to leverage the richer understanding of the computation being performed, potentially caching results, thus leading to more efficient render times.

Recall that the outline flow of the methods is as follows:

     [lighting methods]

Not all of the various pipeline methods (old and new) are required to be present, which further signals to the renderer the intent of the shader. This document discusses the conditions under which the various pipeline methods may be invoked, the meaning of the presence or absence of the methods, and best practices for writing them.

Adhering to the guidelines presented here enables PRMan to efficiently work with your shaders - leveraging optimizations in current releases and making future optimizations possible.

In some circumstances, PRMan only requires information provided by an early pipeline stage, and does not require the potentially more expensive results from later stages. In such cases, although preserving the ordering of the pipeline, PRMan may terminate the pipeline early - having obtained the results it needs. Subsequently, in certain cases, PRMan may require results from later pipeline stages. If earlier pipeline stages have already been executed there are cases where the renderer may simply provide the shader with values cached from the previous invocation and will skip that earlier pipeline stage, proceeding to the stage from which results are now needed. Skipping pipeline stages has some implications for the shader's ability to later rely on the state of member variables and other side effects from running the potentially skipped stage. These implications are discussed below.

RSL and opacity()

Before we further discuss the various combinations of lighting pipeline methods, an earlier stage - opacity() - bears some discussion.

  • Tip

    PRman's treatment of opacity is discussed in detail in the Opacity Revealed application note.

The opacity() method serves three purposes: providing opacity values for camera rays, controlling compositing for surfaces and volumes; controlling opacity seen by transmission rays -- modulating shadows cast by thin, partially transparent surfaces; and, controlling the continuation of diffuse and specular rays -- providing the hold-out functionality of occupancy maps. Understanding when the method is called, and what it may contain, is key to ensuring artifact-free pictures.

No opacity() Method

A shader without an opacity() method is assumed to not set opacity - that is Oi==Os. The renderer does not have to invoke any shaders to determine the opacity. This is the fastest mode of operation. In more complex shader systems, it may not be practical to leave out the opacity method to signal that Oi is not modified. The same effect may be had by setting a shader specially named shader parameter uniform float __computesOpacity = 0.

Simple Opacity

Unless special settings are employed, the results of opacity() must be view-independent and raytype independent. For example, setting a value of Oi = 0.75 in your opacity() method will cause the following treatments:

  • camera rays/primary rendering will composite surfaces behind the currently shaded one using the opacity of 0.75
  • specular rays will undergo continuation and composite subsequent hits with an opacity value of 0.75
  • transmission rays will see the object as 25% transparent
  • diffuse rays will continue through the surface and composite subsequent hits with an opacity value of 0.75

This simplest usage of the opacity() method is suitable for presence modeling or thin-film transparency. The renderer is free to cache the opacity results from your shader and will invoke the method only when required, rather than per ray.

The shader is assumed to compute opacity in a view-independent manner, which is also possible to denote using a specially named shader parameter uniform float __computesOpacity = 1.


If your intent is to model a glass-like object that handles its own refractions, camera ray compositing is undesirable. If camera rays continue past the primary intersection point, objects behind the glass will be seen as though the glass was a thin-film object. Insetead, objects behind the glass should only be seen by refracted specular rays. So, compositing should be disabled or direct-composited results will be mixed with refractions.

Recall that for specular rays and camera rays, the full shaded result is required (all appropriate lighting methods executed). The opacity used for compositing is the opacity value in Oi at the end of the pipeline. Thus, setting Oi=1 in both specularlighting() and lighting() will ensure that no continuation / compositing occurs for specular and camera rays.

For the example above with Oi = 0.75, diffuse rays will be composited with the opacity value 0.75.

As above, the shader is assumed to compute opacity in a view-independent manner, which is possible to denote using a specially named shader parameter uniform float __computesOpacity = 1.

Glass with View-Dependent Transmission

It may be desirable for the transmission value on a glass object to be view-dependent. This can be done in RSL by creating a specially named shader parameter - uniform float __computesOpacity = 2. For transmission rays there is no caching of opacity under this setting, the opacity method is invoked for each transmission ray. For all other ray types, however, the opacity method must return a view-independent result which the renderer is free to cache. Therefore the shader must check rayinfo("type",...) and only return view-dependent results when raytype == "transmission".

Diffuse rays will use the view-independent opacity (the raytype != "transmission" clause in your shader) for continuation and compositing purposes. The same methods for making the glass opaque for camera and specular rays still apply. If you do not write to Oi inside the specularlighting() and lighting() methods, that view-independent result is also used for specular and camera rays.


Note that in all cases, a shader's subsequent pipeline stages may rely on the state of values set in the opacity() method. It will not be skipped when PRMan runs the pipeline to access results of a later stage, even if the opacity() result has already been cached.

Lighting Methods in RSL

RSL 2.0 introduced the lighting() method - designed to return all interaction between a shading element and the lighting in the scene. Subsequently, diffuselighting() and specularlighting() were introduced in order to allow the renderer to distinguish between view-independent and view-dependent shading. By separating these computations, the renderer is able to make optimizations resulting in more performant renders. The intended purpose of each method is given in the following.

method purpose
diffuselighting() compute view-independent shading which may be cached.
specularlighting() compute view-dependent shading which is not cached, but builds upon an input value which may have resulted from a cache lookup of view-independent results
lighting() produce a full computation of all lighting interaction when the renderer knows it will require a full result.

The diffuselighting() method may be skipped if a value for its result is available in the cache. The pipeline will resume at specularlighting() with the Ci that was read from the cache. It should be noted that when caching occurs, the pipeline is run up to diffuselighting() on a different shading manifold or grid than that which will be shaded upon for later specular contributions. The shading invocations are separate and results other that Ci and Oi are not preserved. This means that values of member variables and output variables set in diffuselighing() should not be relied upon during later stages of the pipeline.

Pipeline methods may be invoked under a number of different situations with differing shading requirements:

  • camera shading for REYES hider
  • camera ray in raytrace hider
  • specular ray
  • diffuse ray

No Lighting Methods

This is unsupported.

lighting() Only

All methods will call lighting(). Its behavior is assumed to have been specialized for diffuse rays in order to disable code that is pertinent to specular shading only.

If only a lighting method exists, the following pattern should be employed

public void lighting(output color Ci,Oi) {
        string raytype = "";

        //... compute diffuse

        if (raytype != "diffuse") {
                //... compute specular

specularlighting() Only

The surface is assumed to have no diffuse component and diffuse rays trivially return black. specularlighting() is invoked in all other circumstances. Conductors are one class of material that are efficiently expressed with only a specularlighting() method.

No results are inserted into the radiosity cache; none are looked up.

diffuselighting() Only

The surface is assumed to be view-independent. diffuselighting() is run for diffuse rays and is cached. It is also run for raytrace hider camera rays and is cached. Specular rays obtain this cached result - if it exists - and invoke the pipeline while skipping over the diffuselighting() method (whose results are known). If no cached result exists, the pipeline is run up to the diffuselighting() method, and a result inserted into the cache. Shading then proceeds as if the result already existed (see above). In other words, shading does occur on specular rays, but is seeded with the diffuselighting() cached result.

The diffuselighting() must always return a view-independent result.

The diffuselighting() method may rely on diffusedepth but must not rely upon speculardepth, depth (which includes speculardepth) or other ray-path-dependent entities such as ray importance, or other message-passed values.

lighting() + diffuselighting()

The surface is assumed to be view-independent for the purposes of diffuse rays. However, this combination signals the intent that specular and camera rays contain a view-dependent diffuse term. The lighting() method is utilized for REYES hider shading and ray-traced shading (specular and camera rays). The diffuselighting() method is invoked for diffuse rays. Its result is cached. For diffuse rays if a value exists in the cache, no further shading evaluation occurs.

The diffuselighting() must always return a view-independent result.

The diffuselighting() method may rely on diffusedepth but must not rely upon speculardepth, depth (which includes speculardepth), or other ray-path-dependent entities such as ray importance, or other message-passed values.

The lighting() method is free to be view-dependent in both its specular term (which is always view-dependent) and in its diffuse term.

lighting() + specularlighting()

The lighting() method is evaluated for REYES shading, raytraced hider rays and specular rays. No results are looked up from the cache nor contributed to it. Diffuse rays are assumed to return a black result without shader evaluation.

This combination is more verbose than a single specularlighting() method, specularlighting() is never called. This is not recommended.

lighting() + diffuselighting() + specularlighting()

When the fully shaded result with both specular and diffuse contributions is required, it is typically more efficient to evaluate both at once. This is because the light samples - those dictated by the light rather than the BRDF for Multiple Importance Sampling purposes - may be shared between both the specular and diffuse computations. By evaluating both at once, a single set of transmission rays is required rather than two - one for the diffuse and one for the specular. When combined, lights are typically asked for their samples once; these samples are then evaluated against both the specular and diffuse contributions and shadowing is performed. When only the diffuselighting() and specularlighting() methods exist, each is responsible for its own integration. Each would typically ask the lights for their samples and evaluate and shadow them.

This is the preferred implementation for a surface that has diffuse and specular contributions.

For camera rays, the diffuse result is found in the cache (or evaluated and cached if it does not exist). The pipeline is run and diffuselighting() is skipped - its result is known from the cache. The input Ci to specularlighting() is the value resulting from the cache.

For REYES shading, the lighting() method is run.

The standard view-independent restrictions apply to diffuselighting(), which is run for diffuse rays as well as to fill the diffuse portion of the result for camera rays.

The diffuselighting() and lighting() methods should generally agree with each other with respect to the view-independent nature of the diffuse term. However, in some cases it may be desirable to treat primary visibility for REYES specially. For example, if a view-dependent diffuse is absolutely required, the REYES shading may have a view-dependent diffuse term via the lighting() method. Be aware that this will create look differences between the raytrace and REYES hiders.

If specular reflections absolutely must see a view-dependent diffuse term, there exists a workaround, in which diffusedepth is checked inside diffuselighting(). If diffusedepth is 0, then black is returned for Ci and the view-dependent diffuse term is calculated in specularlighting().

specularlighting() + diffuselighting()

This is not recommended, see above.

Pipeline Summary

stage cacheable notes
opacity() cached not skipped if later stages required
diffuselighting() cached skipped if later stages required; do not rely on member variable writes during later stages
lighting() not cached  
specularlighting( not cached  

Grubby Details and Expert Settings

Presented so far has been the default out-of-box behavior for PRMan. Further to the caching scenarios presented above, there exist additional opportunities for caching, each with their own compromises. The details of these modes are subject to change and are documented here for expert use only. Due to the scene-dependent nature of the benefits of these modes, it is not clear that they should be employed by default. The setting are available via an .ini setting. We would welcome feedback on your experiences with these modes should you choose to experiment with them.

PRMan 17 now supports caching of results in the REYES hider, which can be shared between diffuse-depth 0 (specular rays). This is permitted by extending the interface for the lighting() method:

public void lighting(output color Ci, Oi, radiosity) {


public void lighting(output color Ci, Oi, radiosity, irradiance) {

In both cases, the radiosity must be a view-independent diffuse entity. Irradiance must also be view-independent. An appropriate cache mode must also be used. When enabled, REYES caching may also use results cached from running diffuselighting() during a specular ray. In that case, the REYES grid is shaded with specularlighting() and the results from the cache lookup are available in Ci. This may break expectations in some shaders that assume specularlighting() is only ever invoked at the end of a ray. Note, if these optional parameters exist they must be filled with valid value. Otherwise, the respective caches will be filled with garbage values.

PRMan 17 also changed the semantics for caching on the end of specular rays. Previously, specular ray shading was divided up into a cacheable invocation of diffuselighting() and an uncached, per-ray invocation of specularlighting(). It is not clear that this is always beneficial, and in some cases the reuse rate of the cached diffuse entity was not high enough to warrant splitting the computation. This is in part due to the tradeoff between reuse via the cache and its implication that transmission rays will occur separately for the diffuse and specular lobes (see above).

The caching opportunities are outlined below:

caching mechanism meaning
REYES Caching of results from an extended lighting() method may be shared with diffuse-depth 0 rays.
specular ray

Specular rays will invoke diffuselighting(), which is cached. In combination with REYES caching, this cached result may be used in REYES shading, where available. In such cases the diffuselighting() method may be invoked for REYES shading as well as for ray-traced shading.

Specular rays may reuse the cached diffuselighting() results, and may thereby reduce the shading workload. However, the benefits of doing so are scene-dependent as there is also a cost to the separate computation (and shadowing of) the diffuse and specular results.

raytrace hider camera ray Camera rays in the raytrace hider will invoke separate diffuselighting() and specularlighting() methods. The diffuselighting() results may be cached. Note that the coherency of rays generated by the raytrace hider lead to a cost-benefit tradeoff which differs from what is typical for specular rays. In particular, with pixel samples > 1x1, caching of the radiosity via the diffuselighting() method is generally very beneficial.
diffuse ray Diffuse rays cache the radiosity via diffuselighting(). Note: modes that disable this will lead to a significant increase in the cost of computing indirectdiffuse()
subsurface ray Irradiance results provided by diffuselighting() will be cached and reused by the subsurface() shadeop. Note that there is currently no way to turn off this caching independently of the other caching opportunities.
transmission ray Opacity may be cached and reused by the transmission() and areashadow(). Note that under __computesOpacity == 2, the opacity is not cached. Opacity is also cached to determine ray continuation for all other ray types. This is not disabled under __computesOpacity == 2.

The cache mode may be controlled by the rendermn.ini switch /prman/shade/__cachemode, or Option "shade" "int __cachemode" [i]. Possible values are:

0 - Caching disabled. No opacity, irradiance, or diffuse results are cached.

1 - Cache opacity, irradiance, and diffuse for transmission, diffuse, and subsurface rays. Transmission rays are generated when ray tracing shadows,for instance the areashadow() and transmission() shadeops. Typically, the heaviest generator of diffuse rays is the indirectdiffuse() shadeop. Subsurface is not formally a type of ray, it is a diffuse ray with the subsurface label, generated during execution of the ray-traced version of the subsurface() shadeop.

All further modes build upon mode 1. In other words, if caching is on, the renderer always caches for diffuse rays. The remaining modes are divided into two groups, caching diffuse on the end of specular rays (or not). Within each group are the four combinations of caching shading for primary grids -- those shaded for either the REYES hider or the ray traced hider.

2 - In addition to case 1, cache diffuse for REYES grids. REYES grids can't share with one another, they can only share with specular rays (e.g. indirectspecular()) and subsurface rays (cast by subsurface()). Since this mode does not cache on specular rays, it would be chosen only for a scene that makes heavy use of raytraced subsurface scattering.

3 - (default) In addition to case 1, cache diffuse for camera rays (the ray traced hider, not REYES grids). Cached shading precludes using a different time value for each ray. In the event that temporal effects are needed on camera rays, caching of diffuse on camera rays should be not be used. Because of the spatial coherence of rays generated by the raytrace hider, caching diffuse on camera rays provides a substantial speed-up.

4 - In addition to case 1, cache diffuse for REYES grids and ray-traced camera rays. Choosing this mode allows one to switch between hiders and maintain the same caching behavior.

Modes 5 through 8 are the same as modes 1 through 4 with the addition of caching for specular rays.

5 - Cache on diffuse and specular rays.

6 - Cache on diffuse rays, specular rays, and REYES grids.

7 - Cache on diffuse, specular, and camera rays. (PRMan 16 behavior).

8 - Cache on diffuse rays, specular rays, camera rays, and REYES grids. For the REYES hider, standard REYES shading will attempt to use diffuse results previously cached as the result of running diffuselighting() on the end of a specular ray. If no cache is found, one will be made and potentially used by future specular rays.

Best Practices

  • Do not use depth, speculardepth or rayinfo("type",...) queries in diffuselighting().
  • Do not use depth, speculardepth or rayinfo("type",...) queries in opacity(), with the exception of checking raytype == "transmission" when __computesOpacity == 2.
  • Return view independent results from diffuselighting().
  • If using extended REYES caching (extra outputs to lighting()), ensure their results are view-independent and match the computation in diffuselighting().