
VSFX 319 - Programming Models and Shaders
Maya and Renderman RSL Shader Pattern Animation
Project Summary:
The animation and notes on this page explain how custom shaders written in the RenderMan Shading Language can be animated using SLIM parameter expressions driven in Maya.
Results:
Click on the image to the right to see the final animation.
The Tale of the Happy Little Radio
Our story begins with a simple geometric pattern, programmed by hand in the RSL language. Below is the code that set up the .sl shader, including the variables I used. Default code was used for setting up specularity and light acceptance. The custom variables I declared act as radii values for the circles in the pattern. This came in handy later when I wrote the expression to drive their animation in Maya. The pattern began simply as a blue field. What follows is the process of building the pattern by adding the shapes progressively, thru code
surface
st_coloration(float Kd = 0.8,
Ka = 1,
Ks = 1,
roughness = 0.1,
Large_inner = 0.04, /*[0 0.5 .001] Large inner circle radius*/
Large_outer = 0.075, /*[0 0.5 .001] Large outer circle radius*/
Medium_inner = .005, /*[0 0.5 .001] Medium inner circle radius*/
Medium_outer = .011, /*[0 0.5 .001] Medium outer circle radius*/
Small_circle = .001; /*[0 0.5 .001] Small circle radius*/
color beige = color(0.929,0.929,0.835);
color yellow = color(1,.95,0);
color blue = color(0.352,0.450,0.521);
color hlcolor = 1
color surfcolor = blue;
normal n = normalize(N);
normal nf =faceforward(n, I);
st_coloration(float Kd = 0.8,
Ka = 1,
Ks = 1,
roughness = 0.1,
Large_inner = 0.04, /*[0 0.5 .001] Large inner circle radius*/
Large_outer = 0.075, /*[0 0.5 .001] Large outer circle radius*/
Medium_inner = .005, /*[0 0.5 .001] Medium inner circle radius*/
Medium_outer = .011, /*[0 0.5 .001] Medium outer circle radius*/
Small_circle = .001; /*[0 0.5 .001] Small circle radius*/
color beige = color(0.929,0.929,0.835);
color yellow = color(1,.95,0);
color blue = color(0.352,0.450,0.521);
color hlcolor = 1
color surfcolor = blue;
normal n = normalize(N);
normal nf =faceforward(n, I);
Building the Pattern

if ( ((t-.5)*(t-.5)) + ((s-.5)*(s-.5)) < Large_outer)
surfcolor = yellow;

//as the background serves to knockout to a crescent.
if ( ((t-.554)*(t-.554)) + ((s-.554)*(s-.554)) < Large_inner)
surfcolor = blue;

//values mirrored them across the large circle's center of symmetry.
if ( ((t-.82)*(t-.82)) + ((s-.3)*(s-.3)) < Medium_outer)
surfcolor = yellow;
if ( ((t-.3)*(t-.3)) + ((s-.82)*(s-.82)) < Medium_outer)
surfcolor = yellow;

if ( ((t-.85)*(t-.85)) + ((s-.318)*(s-.318)) < Medium_inner)
surfcolor = blue;
if ( ((t-.318)*(t-.318)) + ((s-.85)*(s-.85)) < Medium_inner)
surfcolor = blue;

surfcolor = yellow;
if ( ((t-.21)*(t-.21)) + ((s-.92)*(s-.92)) < Small_circle)
surfcolor = yellow;

if ((s*s)+(t*t) > .95)
surfcolor = beige;
This last bit finishes off the lighting calculations for the surface and closes up the shader.
color difcolor = diffuse(nf) * Kd;
color ambcolor = ambient() * Ka;
vector i = normalize(-I);
color speccolor = specular(nf, i, roughness) * Ks * hlcolor;
Oi = Os;
Ci = Oi * Cs * surfcolor * (difcolor + ambcolor + speccolor);
}
color ambcolor = ambient() * Ka;
vector i = normalize(-I);
color speccolor = specular(nf, i, roughness) * Ks * hlcolor;
Oi = Os;
Ci = Oi * Cs * surfcolor * (difcolor + ambcolor + speccolor);
}
Expression-driven Animation

Animating the eyes was an easy trick for me. I had previously written a script for my VSFX 160 class for a random blink animation. I simply modified the code from its original application to suit this one. After some minor stumbles, I finally switched its syntax from nested If-Else loops to a Switch/Case statement. This worked perfectly and, if I do say so myself, more elegantly.
It was the eyes' expression that finally gave me my solution for the mouth. I couldn't use sound to drive it directly but I could fudge it a bit and make it look like it was. I set to work on writing a second expression that would randomly pick a number to incrementally increase or decrease the mouth size while keeping it within certain proportions. If it was wider, the code would choose to close it down a bit. If it was smaller, the code would open it up. But always randomly and never in a predictable pattern, this randomness gave the results their realism in that it didn't pulse or repeat. It fluttered and fluctuated and gave the illusion of moving by sound, much like a speaker cone.
Click on the pattern image to the right to see a demo of the eyes blinking and mouth singing. Below are the blocks of code that drive the facial animation.
//Eyes expression:
int $blink = rand(101);
int $count;
int $caseType;
switch ($caseType) {
case 0: if ($blink <= 3) {
RadioShader.Medium_inner = 0.000;
$caseType = 1;
$count = 0;
};
break;
case 1: if ($count <= 4) {
$count++;
} else {
RadioShader.Medium_inner = 0.005;
$caseType = 0;
};
break;
default: RadioShader.Medium_inner = 0.005;
break;
}
//Mouth expression:
int $case;
int $count;
if (RadioShader.Large_inner >= .04) {
$case = 1;
} else {
$case = 2;
}
switch ($case) {
case 1: $count = `rand 1 5`;
$incScale = `rand 0 .006`;
for ($i = 1; $i <= $count; $i++){
RadioShader.Large_inner = RadioShader.Large_inner - $incScale;
if (RadioShader.Large_inner < 0) {
RadioShader.Large_inner = 0;
}
};
break;
case 2: $count = `rand 1 5`;
$incScale = `rand 0 .006`;
for ($i = 1; $i <= $count; $i++){
RadioShader.Large_inner = RadioShader.Large_inner + $incScale;
if (RadioShader.Large_inner > .04) {
RadioShader.Large_inner = .04;
}
};
break;
default: RadioShader.Large_inner = .04;
}
int $blink = rand(101);
int $count;
int $caseType;
switch ($caseType) {
case 0: if ($blink <= 3) {
RadioShader.Medium_inner = 0.000;
$caseType = 1;
$count = 0;
};
break;
case 1: if ($count <= 4) {
$count++;
} else {
RadioShader.Medium_inner = 0.005;
$caseType = 0;
};
break;
default: RadioShader.Medium_inner = 0.005;
break;
}
//Mouth expression:
int $case;
int $count;
if (RadioShader.Large_inner >= .04) {
$case = 1;
} else {
$case = 2;
}
switch ($case) {
case 1: $count = `rand 1 5`;
$incScale = `rand 0 .006`;
for ($i = 1; $i <= $count; $i++){
RadioShader.Large_inner = RadioShader.Large_inner - $incScale;
if (RadioShader.Large_inner < 0) {
RadioShader.Large_inner = 0;
}
};
break;
case 2: $count = `rand 1 5`;
$incScale = `rand 0 .006`;
for ($i = 1; $i <= $count; $i++){
RadioShader.Large_inner = RadioShader.Large_inner + $incScale;
if (RadioShader.Large_inner > .04) {
RadioShader.Large_inner = .04;
}
};
break;
default: RadioShader.Large_inner = .04;
}

Context
Well great, I had my pattern animation.....now what? I needed some kind of context for this; I needed it to tell a little story. To me the "face" in the pattern looked rather childish, rather cartoony; like the graphics I remember seeing on toy electronics when I was a child. That's it: a radio! A little toy radio with a screen and a little face singing along with the music. And so I built my happy little radio, keeping the geometry, and concept, simple. A shiny blue blinn and a photoshopped dial face spruced it up. After getting the shader on the surface and rendering a test, there was still something missing. It wasn't emotive enough. It needed to enjoy this more. It needed to dance!


Final Product
I used Deep Shadows d-map shadows on two spotlights; one over the camera's left shoulder and the other low and to the right of the screen as a fill light. I originally used a 1024 map but it proved to be overkill and bloated render times unnecessarily. The point light you see in the playblasts was used only for testing purposes and was removed for this final pass. The dial face is now in place and the edge normals on the radio have been hardened up to correct them after smoothing the screen.Click on the image at left to see the final movie.