In order to get a handle on the depths of prman, we decided to experiment with DSOs, or Dynamic Shared Objects. We did this by adding our own custom function as a shadeop. This special C program will run within the shader and place points at every micropolygon on the shaded surface. Here is a look at the shadeop that Professor Kesson wrote:

#include <stdio.h>
#include <stdlib.h>
#include <shadeop.h>
  
// Because these variables are declared outside of the shadeop functions
// they have "file scope". They are, in effect, shared by the functions
// implemented within this file.
FILE     *f = NULL;
int         dataOnLine = 0; 
float    width;
  
// The startup function. Its been added to the table even though
// it does not do anything useful!
SHADEOP_INIT(writePoints_init) {
    return NULL;
    }
  
// The shutdown function. Here we conclude the points statement
// and ensure the output rib file is properly closed.
SHADEOP_CLEANUP(writePoints_cleanup) {
    fprintf(f, "\n] \"constantwidth\" [%1.3f] ", width);
    fflush(f);
    fclose(f);
    }
  
// Here we define the "signature" of the writePoints() custom
// function we are adding to the RenderMan Shading Language.
SHADEOP_TABLE(writePoints) =
{ 
    { "float writePoints (point, float, string)",     
      "writePoints_init", "writePoints_cleanup" },                                        
    // The table is always concluded with an empty string.
    { "" }
};
 
// Here we implement the core functionality of our custom 
// RSL function
SHADEOP (writePoints)
{
float     *pnt = (float*)argv[1];                // an array of floats
width = *(float*)argv[2];                    // a single float
STRING_DESC *desc = (STRING_DESC *)argv[3];
  
// Open the output rib file and begin our points statement
if(f == NULL) {
    f = fopen( desc->s, "w");
    fprintf(f, "Points \"P\" [\n");
    }
// For formating purposes ensure short lines of data
if(dataOnLine < 12)
    fprintf(f, "%1.3f %1.3f %1.3f ", pnt[0],pnt[1],pnt[2]);
else
    {
    fprintf(f, "\n%1.3f %1.3f %1.3f ", pnt[0],pnt[1],pnt[2]);
    dataOnLine = 0;
    }
dataOnLine += 3;
return 0;
}

This custom function defined as writePoints runs within our surface shader:

surface
pointPlacer(float    Kfb = 1,
                    jitter = 0.05,
                    radius = 0.08,
                    freq = 8;
            string  bakename = "")
{
color    surfcolor = 1;
  
if(bakename != "")
    {
    point pp = transform("object", P);
    point ppp = pp + (noise(P * freq) - 0.5) * jitter;
    
    float x = ppp[0] + (random() - 0.5) * jitter/2;
    float y = ppp[1] + (random() - 0.5) * jitter/2;
    float z = ppp[2] + (random() - 0.5) * jitter/2;
    
    ppp = point ( x,y,z );
    
    writePoints(ppp,radius,bakename);
    }
  
Oi = Os;
Ci = Oi * Cs * surfcolor * Kfb;
}
So now that the architecture for placing dust-like geometry on a surface was in place, I decided to explore its functionality on Boris.
Above is the default operation of the shader (before using the noise function documented above). The shader jitters the position of point P and then passes it into the shadeop. The shadeop then places the point at the correct jtitered location. Next we had some fun. Using the ridiculously simple particle shader, we accumulated color values as the camera rays traveled through the points:
surface particle()
{
Ci = Cs;
Oi = 0;
}
 

Then, using the noise function, we organized the jitter into interesting patterns:

This exercise was designed to attain a better grasp of how the C programming language can interact with prman to achieve more functionality than is already available. Although this was mostly a class exercise, it would be interesting to experiment further with creating interesting patterns with the points on rendered surfaces.