RSL
Point Clouds as Accumulation Buffers


return to main index



Introduction

I am grateful to Michael Kessler for showing me how point clouds can be used to accumulate shading values. A point cloud is normally written once and read many times. Alternatively, it is converted to a brickmap that is, likewise, read many times. This tutorial presents Michael's technique of accumulating shading data by reading and writing a point cloud each frame of an animation.

An accumulative shader does the following,

  1. calculate a (current) shading value,
  2. read a (previous) shading value from a point cloud,
  3. compare the shading values and write one of them back into the point cloud,
  4. use the shading value that was chosen in step 3.

As an introduction to this topic the surface shaders in listings 1 and 2 show how spot light values can be accumulated into a point cloud. As shown by figures 1 and 2 such accumulation results in a "spot light trail". Using spot lights as a source of data is somewhat limiting so the second section demonstrates the use of so-called magic lights.


Trailing a Spot Light

Example 1 - solid trail

In this example a spotlight, with a narrow cone angle, moves across a polygon. Figure 1 shows the effect when the polygon is shaded with a plastic surface shader. Figure 2 shows the effect when the polygon is shaded with the accumulative surface shader given in listing 1.



Figure 1 - plastic surface shader



Figure 2 - "accumulative" surface shader


Listing 1


surface
trail_1(float    Kd = 0.5,
                 frame_num = 1;
        string   bakename = "";
output varying color _spot = 0)
{
normal    n = normalize(N);
normal    nf = faceforward(n, I);
  
// STEP 1. Get spotlight value(s) and the non-spotlight value(s)
color currSpot = 0;
color diffuseColor = 0;
float notUsed;
illuminance(P, nf, PI/2) {
    if(lightsource("coneangle", notUsed))
        currSpot += Cl * normalize(L).nf;
    else
        diffuseColor += Cl * normalize(L).nf;
    }
diffuseColor *= Kd;
  
// STEP 2. Read the point cloud - unless its frame one in which
//         case we ignore the previous spot light value
color    prevSpot = 0;
if(bakename != "" && frame_num > 1) {
    if(texture3d(bakename, P, n, "_spot", prevSpot) == 0)
        prevSpot = 0; /* no data - default to black */
    }
  
// STEP 3. Decide which value to store in the point cloud.
float currValue = comp(ctransform("hsv", currSpot), 2);
float prevValue = comp(ctransform("hsv", prevSpot), 2);
_spot = (currValue > prevValue) ? currSpot:prevSpot;
if(bakename != "")    
    bake3d(bakename, "_spot", P, n, "interpolate", 0, "_spot", _spot);
Oi = Cs;
Ci = Oi * Cs * (diffuseColor + _spot);
}

For the calculation of direct lighting, surface shaders generally add the values returned by the RSL functions ambient(), diffuse() and specular(). The shaders in listings 1 and 2 take a different approach. They use the illuminance() function to loop over the lights in a scene. If a light source has a parameter called "coneangle", and it successfully returns that value, the shaders assume they are getting lighting values from a spot light. The shaders assign the spot light value to a separate variable so that past and current values can be compared and a spot light value can be written (accumulated) into a point cloud. It should be noted that the actual value of "coneangle" is ignored - it is merely used to distinguish spot lights from other kinds of light sources.


Example 2 - faded trail

Although the first shader is somewhat rudimentary it can, with minor modifications, produce some additional effects. Figure 3, for example, demonstrates how a shader can cause the trail of light to fade. Listing 3 shows how this can be done by maintaining a count of the number of times a non-zero spot light value is baked into the point cloud.



Figure 3 - faded light trail

Listing 2


surface
trail_2(float     Kd = 0.5,
                  frame_num = 1;
        string    bakename = "";
output varying color _spot = 0;
output varying float _writes = 0)
{
normal    n = normalize(N);
normal    nf = faceforward(n, I);
  
// STEP 1. Get spotlight value(s) and the non-spotlight value(s)
color currSpot = 0;
color diffuseColor = 0;
float notUsed;
illuminance(P, nf, PI/2) {
    if(lightsource("coneangle", notUsed))
        currSpot += Cl * normalize(L).nf;
    else
        diffuseColor += Cl * normalize(L).nf;
    }
diffuseColor *= Kd;
 
// STEP 2. Read the point cloud - unless its frame one in which
//         case we ignore the previous spot light value
color    prevSpot = 0;
float    writes = 0;
if(bakename != "" && frame_num > 1) {
    if(texture3d(bakename, P, n, 
                "_spot", prevSpot, "_writes", writes) == 0) {
        prevSpot = 0; /* no data - default to black */
        writes = 0;
        }
    }
  
// STEP 3. Decide which value to store in the point cloud.
float currValue = comp(ctransform("hsv", currSpot), 2);
float prevValue = comp(ctransform("hsv", prevSpot), 2);
  
_spot = (currValue > prevValue) ? currSpot : prevSpot;
if(bakename != "") {
    if(currValue > 0 || prevValue > 0)
        writes += 1;
    bake3d(bakename, "_spot,_writes", P, n, "interpolate", 0, 
                     "_spot", _spot, "_writes", writes);
    }
// STEP 4. Apply a simple "fade" 
_spot = _spot * (1 - smoothstep(0, 10, writes));
  
Oi = Cs;
Ci = Oi * Cs * (diffuseColor + _spot);
}


Example 3 - noisey trail

The rsl noise() function can help to mask the transitions from one patch of light to another - figure 5.

    // STEP 4. Apply noise to the "fade" 
    _spot = _spot * (1 - smoothstep(0, 10, writes * 
                     noise(transform("shader", P) * 8)));

In this code fading begins after a spot light value has been written 10 into the point cloud. The noise value (frequency of 8) in effect "jitters" the true number of writes.



Figure 5 - noisey trail


Testing the Shaders

The rib file given in listing 3 can be used to test the first two shaders. Use the Cutter text editor to convert the rib to a keyframe animation file - figure 6. The second keyframe of the animation file can should be edited so spot light moves across the polygon ie.

    Keyframe 1
    LightSource "spotlight" 1 "intensity" 25 "from" [-0.5 4 0] "to" [-0.5 0 0] 
                             "coneangle" 0.05 
    Keyframe 2
    LightSource "spotlight" 1 "intensity" 25 "from" [0.5 4 0] "to" [0.5 0 0] 
                             "coneangle" 0.05

In addition, the "frame_num" parameter in the second keyframe should be changed to 30.



Figure 6 - converting to a keyframe animation file


When rendering the first frame of an animation the renderer (prman) will issue a warning that can be ignored ie.

    T43030 Point cloud file "PATH_TO/data.ptc" doesn't exist.

If you are using prman version 13.5.1 you will also get this warning that can also be ignored ie.

    S44019 {WARNING} Brick map "PATH_TO/data.ptc" does not contain enough data.

Listing 3


DisplayChannel "color _spot"
DisplayChannel "color _magic"
DisplayChannel "float _writes"
 
Display "untitled" "framebuffer" "rgb"
Format 427 240 1
Projection "perspective" "fov" 40
ShadingRate 1
 
Translate  0 0 2
Rotate -50 1 0 0
Rotate 0   0 1 0
Scale 1 1 -1
WorldBegin
   # TransformBegin
   #    Translate 0.5 0.1 0
   #    LightSource "magicPointLight" 1 "intensity" 1.5 "radius" 0.2
   # TransformEnd
   LightSource "pointlight" 1 "intensity" 10  "from" [0 5 0]
   LightSource "spotlight" 1 "intensity" 25 "from" [-0.5 4 0] "to" [-0.5 0 0] 
                             "coneangle" 0.025 
   TransformBegin
       Surface "PATH_TO_SHADER" "bakename" ["PATH_TO/data.ptc"] "frame_num" [1]
       Polygon "P" [-0.5 0 -0.5  -0.5 0  0.5  
                     0.5 0  0.5   0.5 0 -0.5] 
               "st" [0 0  0 1  1 1  1 0]
   TransformEnd
WorldEnd


Trailing a Magic Light

Example 4

Querrying a light source for a parameter named "coneangle" in order to identify a spot light is not as flexible as using a custom light source shader that has been designed to work in conjunction with an accumulation surface shader. So-called magic light source shaders offer an attractive alternative to using regular light source shaders.

Generally, a magic light does not contribute illumination to a scene. Instead, their output value(s) are used by a surface shader to create a very specific visual effect. A simple example of a magic light source shader, called "magicPointLight" is given in listing 4. It calculates a value, stored in a varaible named "_output", that is proportional to the distance (L) of the light source to the micro-polygon it is "illuminating". Unlike a regular point light, magicPointLight has a dropoff distance (radius). For simplicity it uses the smoothstep() function to calculate the value of dropoff.


Listing 4


light magicPointLight(
    float intensity = 1;
    float radius = 1;
    string __category = "magicPointLight";
    output varying color _output = 0)
{
illuminate(point "shader"(0,0,0))
    {
    _output = intensity * (1 - smoothstep(0, radius, length(L)));
    Cl = 0;
    }
}

Listing 5 is a modified version of listing 2. The variable named "__category" enables the illuminance function to "loop over" only those lights in a scene that have been tagged as "magicPointLight". Because of the improved use of the illuminance function the shader can now conviently use the standard lighting functions to obtain the ambient, diffuse and specular lighting components. The "trail_3" shader is, to all intents and purposes, a customized version of the classic Pixar "plastic" shader.


Listing 5


surface
trail_3(float Ka = 1,
              Kd = 0.5,
              Ks = 0.8,
              Kmagic = 1,
              roughness = 0.1,
              frame_num = 1;
        color hilitecolor = 1;
       string bakename = "";
output varying color _magic = 0)
{
normal n = normalize(N);
normal nf = faceforward(n, I);
  
// STEP 1. Get the magic light color
color currMagic = 0;
color magicOutput;
illuminance("magicPointLight", P) {
    if(lightsource("_output", magicOutput)) {
        currMagic += magicOutput;
        }
    }
  
// STEP 2. Read the previous magic light color
color    prevMagic = 0;
if(bakename != "" && frame_num > 1) {
    if(texture3d(bakename, P, n, "_magic", prevMagic) == 0)
        prevMagic = 0; /* no data - default to black */
    }
  
// STEP 3. Decide which value to store in the point cloud.
float currValue = comp(ctransform("hsv", currMagic),2);
float prevValue = comp(ctransform("hsv", prevMagic),2);
  
_magic = (currValue > prevValue) ? currMagic : prevMagic;
if(bakename != "")    
    bake3d(bakename, "_magic", P, n, "interpolate", 0, "_magic", _magic);
  
// STEP 4. Get the standard lighting components
color ambColor = Ka * ambient();
color diffuseColor = Kd * diffuse(nf);
color specColor = Ks * hilitecolor * specular(nf, normalize(-I), roughness);
Oi = Os;
Ci = Oi * Cs * (ambColor + diffuseColor + specColor + (_magic *  Kmagic));
}




© 2002- Malcolm Kesson. All rights reserved.