
During Spring Quarter of 2006, I took a class called Programming Models and Shaders I, taught by Malcolm Kesson. In this class, we learned the fundamentals, and also some advanced techniques, of using Pixar's Renderman to hard code models and scenes, and program our own custom procedural shaders. Malcolm was a great professor to study under, if not the best possible for the subject of Renderman, and made it a point to assist every single person to make sure they were progressing steadily. Our final project in this class was the Nature Mort, creating the effect of a natural object rotting or decaying by writing a custom shader with Renderman Shading Language (RSL.) The project took approximately a week of work to write and debug the shader.
This is the completed effect and breakdown video, which summarizes the methods used to create the shader. It was shown at the Spring Quarter '06 Visual Effects Show, and uses music by Thievery Corporation.
|
Click HERE to launch the video
Quicktime 7 Required (8.3 mb, 1:15) |
|
After the visual effects show, it was suggested to me to write a tutorial, or at least a more thorough explanation than the video's montage, as to how I managed to create this effect. So thats what this is.
At the beginning of the assignment it was made clear to us that in order to achieve a realistic effect, you had to provide your own reference material of a rotting object of your choice. That meant sitting fruit out to rot, taking pictures of it on a periodic basis, creating a stink, and being a bad roommate. After considering time constraints, and a hearty estimate of my ability, I decided upon a pear for my nature mort object. This is the progress my pear made over a week-long process.
Starting off, I had to build a pear object and an overall scene for the shaders to be applied to. Using simple NURBS modelling techniques, I sculpted a NURBS sphere into a pear shape, and made a stem out of a NURBS cylinder. I wanted to light the scene like a stage would be lit, so I added a stark white key light, coming in from the right of the frame, duplicated it, rotated it, and turned the intensity down mildly to provide a backlight, coming in from the left. Added a camera and a flat plane for the pear to sit on, and the scene was built.
The way I saw it, even if my rotting effect sucked, if I had a realistic pear shader to start from I would at least have a good fallback position. However, it would be hard to create a truly photorealstic shader, due to hardware limitations and time constraints.
Color was an important factor, and multiple colors would have to be used in order to simulate the turbulent surface color of a pear. As you can see from the photos, there are about three colors being used: the light lime green base color serving as the foundation, the dark green color of the dotted pattern of the skin, and a midrange green, which serves as the subtle "bloom" which extends from the dots. These colors are all user-modifiable variables in order to accomodate the animating rotted surface, and were changable within the Slim template. These were defined in the .sl file like so:
| |
surface pear(float Ka = 1,
Kd = 1,
Ks = 0.7,
roughness = 0.1,
rad = 0.2,
freq = 10,
blur = 0.15,
color_freq = 25,
color_amp = 5;
color dot_color1 = color(1,0,0),
bg_color1 = color(1,0,0),
bg_color2 = color(0,1,0),
hilitecolor = 1
) |
|
 |
This is a good base color to start from. Pears have sort of a yellow tinge to them. Now comes the first big hurdle: the polka dots.
Okay, not really polka dots. But a darker colored random dotted pattern is a major asset to the texture. I decided that a good place to start from would be using a noise function to generate randomness, but honestly, I had no clue where to start. So I consulted the professor. And he consulted a copy of Advanced Renderman. |
Inside, we found an article about a function called "cellnoise()", which would determine a random point on a per-poly basis, and return a value to "feature_distance". We implemented the sample function in the book:
float feature_distance(point p; float freq)
{
point pp = transform("object", p * freq);
point thiscell = point (floor(xcomp(pp))+0.5, floor(ycomp(pp))+0.5, floor(zcomp(pp))+0.5);
float dist_to_nearest = 1000;
float i;
float j;
float k;
for (i = -1; i <= 1; i += 1){
for (j = -1; j <= 1; j += 1){
for (k = -1; k <= 1; k += 1){
point testcell = thiscell + vector(i,j,k);
point pos = testcell + vector cellnoise (testcell) - 0.5;
float dist = distance(pos,pp);
if(dist < dist_to_nearest){
dist_to_nearest = dist;
}
}
}
}
return dist_to_nearest;
} |
Then, to create the dots themselves, we created the float rad, and used it in a procedure to draw the circles.
surface pear(float Ka = 1,
Kd = 1,
Ks = 0.7,
roughness = 0.1,
rad = 0.2,
freq = 10,
blur = 0.15,
color_freq = 25,
color_amp = 5;
color dot_color1 = color(1,0,0),
bg_color1 = color(1,0,0),
bg_color2 = color(0,1,0),
hilitecolor = 1
) |
|
float dist1 = feature_distance(P, freq);
float dist2 = feature_distance(P, freq/1.5);
// Color in the dots
if(dist1 <= rad) {
float blend = smoothstep(rad - 0.15, rad + 0.15, dist1);
surfcolor = mix(dot_color1, Cs, blend);
}
else
surfcolor = bg_color1; |
|
 |
Excellent! We've effectively created a pretty random assortment of dots, much like the surface of a pear. We're not done yet, but this is a great start.
Next up is now creating a variation to the base color that also cooresponds with the locations of the dots. This is actually not as hard as it sounds. Using the noise values that were pulled from the cellnoise function above, you can generate a noise pattern and link a color spline between the two using the two background color variables. |
| |
// Do the noise for the background
for(x = 0; x < 2; x = x + 1)
{
hump = hump + 0.5 - noise(crinckle * totalFrequency * P)/totalFrequency;
totalFrequency *= 1.2;
}
|
|
 |
The algorithm above isn't a precise set-up for generating the same effect, but can be used as a foundation to create your own for whatever object you're rotting. Changing the expression in the noise function can create many different patterns which will also correspond to your dot texture. For the pear, I used a form of Worley turbulance to fade the background to the bloom color. You can read about using turbulance here.
Now we have our fruit shader situated, we can move on to the displacement. |
The surface of the pear looks a bit too glossy and smooth to be taken seriously, so I wrote a quick noise grain over the entire surface to give it a bit more detail. It isn't precisely the quality of a real pear, but it certainly helps the illusion.
pear_displace(output varying float hump = 0;
float Km = 0.1,
Ka = 1,
Kd = 0.5,
Ks = 0.7,
roughness = 0.2,
specksize = .01,
sizes = 5,
spattercolor = .2,
basecolor = 0,
blur = 0.1,
grain_freq = 30,
ampf = 5,
threshold = 0.7)
|
|
float noise_var = noise(crinkle * grain_freq * P)/grain_freq * ampf;
|
|
Considering my own reference wasn't quite exciting or interesting enough to port to a shader, I looked at other stock photos and reference for different kinds of pear rot. I noticed right away that a lot of the photos of really intense rotting was eating holes straight through the skin of the fruit. I knew this could be accomplished with a good displacement and shader-to-shader messaging (which we'll get to later.)
I didn't want the rotting to sync up with the dots, because that wouldn't make any sense, so I started from scratch. I noticed the Spatter shader, a pre-made shader for PRMan supplied by Pixar, had a similar style to what I wanted to use. This was a surface shader though, so I had to figure out a method of converting the surface to displacement.
spatter(float Ka = 1,
Kd = 0.5,
Ks = 0.7,
roughness = 0.2,
specksize = .01,
sizes = 5;
color specularcolor = 1,
basecolor = color (.1,.1,0.5),
spattercolor = color (1,1,1)) |
|
The Spatter
shader comes with a few variables which alone could be tweaked to achieve the result I was looking for in terms of the rot. The actual artistic side of this part doesn't require a lot of coding, thats all saved for the conversion to displacement. The block to the left shows all the modifiable variables in the Spatter shader.
The important variables are these:
specksize: The size of the smallest paint speck
sizes: The number of different sizes.
basecolor: The background color
spattercolor: The color of the spatter |
 |
I created the pattern for the rot I wanted to use first by showing it as the surface shader. This gave me a pretty good visual representation of the location, shape, and sizes of how the spatter will lay out. Considering the data for color is in greyscale, and a single value is all thats needed for a displacement, it would seem that everything is pretty cut out to convert. All we really need to do is change it from a surface shader to displacement shader, and add in our grain with our rot displacement with one little blurb of code under our noise:
|
Excellent. We now have a displacement. Its a little harsh on the edges, so we need to blur things inward a bit. We can do this with a smoothstep placed in the spatter code. |

|
| |
paint = noise(transform("object",P)*scalefac);
hump = smoothstep(threshold - blur, threshold + blur, paint);
|
|
 |
Voila! The finished displacement map for our rot.
Now, theres one last piece to our puzzle. We need to set up a connection between our displacement shader and our surface shader. As the pear rots and the gaps in the skin widen, I wanted it to reveal a deep ugly brown subsurface, but only where the displacement was taking place. For this, we use shader-to-shader messaging.
Setting a variable in the displacement from a float to an output varying float will allow us to use the values from one shader in another. In this instance, the hump value. |
// located in displacement shader
pear_displace(output varying float hump = 0;
float Km = 0.1,
Ka = 1,
Kd = 0.5,
Ks = 0.7,
roughness = 0.2,
specksize = .01,
sizes = 5,
spattercolor = .2,
basecolor = 0,
blur = 0.1,
grain_freq = 30,
ampf = 5,
threshold = 0.7) |
|
// located in surface shader
if(displacement("hump", height) == 1)
{
float blend = smoothstep(rot_ht-0.05,rot_ht+0.05,height);
surfcolor = mix(surfcolor,rot_color,blend);
} |
|
And there you have it! The shader is set. Using MTOR, you can import your surface and displacement into Maya and begin tweaking the values in the Slim interface, setting them to keyframe between your "clean" fruit, and the rotten counterpart. If you need to do a breakdown of your effect, simply go back and comment out each major step except for the step you're illustrating, render, repeat.
I really hope you found this write-up useful/helpful/readable. If you have any problems, feedback, questions, feel free to drop a line! I can't guarantee I'll be helpful, but I'll give it my best shot. Thanks for reading!
- Matt Burdette
matt@mattburdette.com
|