Imager Shaders

Imager Shaders

September, 2009


With the release of RenderMan Pro Server 15 imager shaders are supported, as envisioned by the original RenderMan Interface Specification. PRMan supports two kinds of imager shaders: the Imager shader, which operates on output pixels after pixel filtering has occurred, and the PixelSampleImager shader, which is run on the subsamples specified by RiPixelSamples. Imager shaders allow manipulation of the final image prior to it being dispatched for display.

Shading Language support for Imager shaders

An Imager shader may be declared as an old-style shader, like so:

imager myImager(...) {

Or as a new-style class-based shader. The class should have an imager method that has output varying color Ci and output varying color Oi arguments:

class myImager(...) {
   public void imager(output varying color Ci; output varying color Oi) {

Note that the imager method is only run on shaders that are defined using the RiImager or RiPixelImager calls; it is not run on shaders assigned via RiSurface or other calls. Member variables therefore cannot be shared between surface shaders and imager shaders, as the former run on each surface separately, while the latter run on all final pixels or pixel samples. If you need to communicate between the surface and the imager shader, Arbitrary Output Variables provide a mechanism to do so (see below).

An imager shader is much like an option in that it is specified before WorldBegin and applies for the whole render (Imager shaders cannot be set per gprim). Attributes and Shaders which are current at WorldBegin may be accessed by an Imager shader.

Attribute "user" "string attributeReferredToByImager" "somevalue"
Shader "imagerCoshader" "coshader1"
Imager "background"

Standard outputs Oi and Ci

The variables Oi and Ci are the composited color and opacity, respectively. In the case of an Imager shader these have also been filtered by the pixel filter. In the case of a RiPixelSampleImager, the pixel filter has not yet been applied. During compositing, Ci is assumed to be premultiplied and is therefore delivered to the imager shader as a premultiplied entity. If an AOV has an associated alpha then the AOV is assumed to be premultiplied by it.

Surface parameters u and v

The variables u and v run from 0->1 over the final output image resolution. Thus they are 0->1 over the ScreenWindow.

Geometric variables P, dPdu, dPdv, I

The variables P, dPdu, dPdv and I are defined to be in current space, as they are for other shader types. Note that current space is camera space in Pixar's RenderMan (for all shader types, not just Imager shaders). P is the camera space position of the pixel center on the image plane. We define the image plane to exist at the near clipping plane (z=znear). No geometry in front of that plane will be rendered and therefore it represents a good choice for the position of the image plane in camera space. Note that z=znear maps to z=0 after projection. It may help to think of the image plane (in camera space) as being centered on the position defined by transforming the raster space point (xres/2,yres/2.0) into camera space.

Because P is defined in current space it is perfectly valid to use its value as an argument to shadeops such as trace(). For example, the following shader fragment:

class incidentLightImager() {
  public void imager(output varying color Ci; output varying color Oi) {
    illuminance (P, I, PI/2) {



would allow you to evaluate light falling directly on the image plane (perhaps for flare effects).

For a PixelSampleImager the values of P are jittered appropriately to match the sub-sample positions. The number of subsamples for a pixel is specified with RiPixelSamples. For an RiPixelSampleImager the range of P and the other variables may be expanded slightly, due to samples that lie outside the frame but are required for filtering (as specified by RiPixelFilter).

Note that P is not writable in an imager shader.

Texture mapping variables s,t

The variables s and t represent the texture mapping coordinates required to map a texture over the ScreenWindow. They represent pixel centers over a range of 0.5/res -> 1-(0.5/res) in an Imager shader. Should you require raster coordinates (in the resolution of the final image), you may either use transform(), or make the following calculation:

uniform float res[2];
varying float s_raster = s*res[0]-0.5;
varying float t_raster = t*res[1]-0.5;

Note however that the values for s and t are chosen such that if you have an image of the same resolution as the final image, then texture(texturename,s,t) will be mapped appropriately. For example, suppose you had a background plate from a shoot, and want images to be displayed over it, then the following shader will allow you to place that image underneath the output rendered image:

class backgroundPlate(uniform string plateTexture=""; string imgColorSpace="";) {
   private constant float A = 1.0/12.92;
   private constant float B = 1.0/1.055;

   public void imager(output varying color Ci; output varying color Oi) {
       if (plateTexture != "") {
           color tval = texture(plateTexture);
           if (imgColorSpace != "") {
               if (imgColorSpace == "sRGB") {
                   uniform float i;
                   for (i=0; i<ncomps; i++) {
                       tval[i] = tval[i] < 0.04045 ?
                           tval[i] * A : pow((tval[i]+0.055) * B , 2.4);
               } else {
                   tval = ctransform(imgColorSpace,"rgb",tval);
           Ci = Ci /*already multiplied by Oi*/ + (1-Oi)*tval;

Note that in the case of a PixelSampleImager the values of s and t may lie outside the range specified above, because additional pixel subsamples are used to provide sufficient samples for the pixel filter. The larger the pixel filter (as specified by RiPixelFilter), the larger the region that lies outside this range. You might therefore wish to prepare your background textures with wrap mode clamp, depending on what you are trying to achieve.

CoShader support

Coshaders present at WorldBegin may be queried using getshaders(). This allows modular imagers to be written if so desired:

shader coImagers[] = getshaders("category","myimagercat");
uniform float i, n = arraylength(coImagers);
for(i=0; i < n; i+=1) {

If desired, lights may also be queried if they are declared before WorldBegin.

Arbitrary Output Variables

Arbitrary output variables (AOVs) may be read and modified in an imager shader. This allows compositing of 'layers' or outputs just as you might do in a compositing package after render. For example, suppose your render generates a series of color AOVs such as CoutDiff (the diffuse color response of the surface), CoutSpec (the specular color response of the surface), CoutAmbi (ambient color), and CoutRefl (reflected color); you may choose to produce a beauty render by compositing these in your imager shader, like so:

class compositeAovs( output varying color CoutDiff;
                     output varying color CoutSpec;
                     output varying color CoutAmbi;
                     output varying color CoutDiffAmbi;
                     output varying color CoutRefl;
                     uniform float diffuseMix = 1;
                     uniform float specularMix = 1;
                     uniform float reflMix = 1;
                     uniform float ambientMix = 1;
                     ) {
   public void imager(output varying color Ci; output varying color Oi) {
       CoutDiffAmbi = CoutDiffColor * CoutDiffDirect;
       Ci =        diffuseMix * CoutDiffuseResponse +
                   specularMix * CoutSpecResponse +
                   ambientMix * CoutAmbi +
                   reflMix * CoutRefl;

Note the lack of default values for the outputs that represent the AOVs - this prevents overwriting of the AOV with the default value. Note also their declaration as varying color.

Note also that although we are regenerating Ci, we do not need to premultiply it by Oi because it is assumed each of the AOVs is already premultiplied. Should they have their own associated alpha, then you would need to deassociate them and multiply the result with Oi.

Furthermore, additional AOVs generated by the imager shader may be output. In the example above, CoutDiffAmbi is such an additional output. Once again, when multiplicatively combining two inputs, be aware that each is assumed to be premultiplied by Oi, so take care not to have Oi's effect count more than it should.

Such an approach may represent a saving compared to compositing in each surface shader. It may therefore be worth simply setting Ci=Cs in the surface shader if a complex calculation will reconstitute it later.

This example uses a modified version of, which outputs each contribution separately:

shinymetal (
       float Ka=1, Ks=1, Kr = 1, Kd=0.1, roughness=.1;
       string texturename = ""; string envspace = "";
       output varying color CoutSpec=0;
       output varying color CoutDiff=0;
       output varying color CoutRefl=0;
       output varying color CoutAmbi=0;
   normal Nf;
   vector V;
   vector D;
   color  Cr;

   Nf = faceforward(normalize(N), I) ;
   V = normalize(-I);

   string espace = "world";
   if (envspace != "") espace = envspace;
   D = reflect(I, Nf) ;
   D = vtransform (espace, D);

   if (texturename != "") {
      Cr = Kr * color environment(texturename, D);
   } else {
      Cr = 0.;

   Oi = Os;
   Ci = Os * Cs;

   CoutRefl = Oi * Cs * Cr;
   CoutSpec = Oi * Cs * Ks*specular(Nf,V,roughness);
   CoutAmbi = Oi * Cs * Ka*ambient();
   CoutDiff = Oi * Cs * diffuse(Nf);
images/figures.imager_shaders/teapot_CoutAmbi.png images/figures.imager_shaders/teapot_CoutDiff.png
Ambient (CoutAmbi) Diffuse (CoutDiff)
images/figures.imager_shaders/teapot_CoutRefl.png images/figures.imager_shaders/teapot_CoutSpec.png
Reflection (CoutRelection) Specular (CoutSpec)

Combined image

Accessing depth

Should you need to, you may access the z value in your imager shader by adding a parameter with the following declaration:

output varying float z;

Note that you must currently have z in your primary display for this to work. If neccessary, z may be modified by an imager shader.


Imager shaders might be used to composite of AOVs in order to produce a 'beauty' render. Of course you might want to produce an imager over a background plate. Imager shaders might also be used to color correct or burn in a slate image, or perhaps even to add additional content to an image. Here's a rather stylized render with a lens flare applied via an imager shader:


Lens flare!

The shader is a fractionally modified version of the lens flare shader from Advanced RenderMan [*] (converted to an imager shader).


[*]Anthony A. Apodaca & Larry Gritz, Advanced RenderMan: Creating CGI for Motion Pictures, Morgan Kaufman, 2000, page 325.