Consider the following problem: You have a scene with a lot of staircases or for example a huge pyramid. Most of the time the camera shows the whole scene from distance which means that you can't see the individual steps very well. Instead of modelling each individual step you consider to create a simplified model without steps and faking the effect of having stairs within the shader:
/* stairs */
surface
stairs(float Ka = 1, Kd = 0.5, Ks = 0.5, roughness = 0.1;
color specularcolor = 1;
float steps = 5;)
{
normal Nf;
float sv = mod(v * steps, 1);
if (sv < 0.5)
Nf = ntransform("object", "current", normal(0, -1, 0)); /* front */
else
Nf = ntransform("object", "current", normal(0, 0, 1)); /* up */
Oi = Os;
Ci = Os * (Cs * (Ka * ambient() + Kd * diffuse(Nf)) +
specularcolor * Ks * specular(Nf, normalize(-I), roughness));
}
As you can see in figure 12 I rendered a simple
staircase with 10, 50, and 100 steps. I used a Python program to
generate the RIB file automatically based on a parameter for the
number of steps. On the web page where you downloaded this document
or where you read it right now you will find a copy of the Python
script I used to generate the staircases. It's called
stairs.py. You should generate the real geometry and render
it with your favourite RenderMan compatible renderer to compare the
results with the shader based version using the following RIB file.
Change the parameter steps for the shader before you render
the RIB file again:
# stairs.shader.rib
Exposure 1.0 2.2
Display "stairs.shader.tiff" "file" "rgba"
Format 640 480 1.0
Projection "perspective" "fov" [ 30 ]
Translate 0 0.25 7
Rotate -30 1 0 0
Rotate 30 0 1 0
Rotate 90 1 0 0
Rotate 180 0 1 0
WorldBegin
Surface "plastic"
LightSource "spotlight" 1
"color lightcolor" [ 1.0 1.0 1.0 ]
"point from" [ 0.0 -1.5 5.0 ]
"point to" [ 0.0 0.0 0.0 ]
"float intensity" [ 10 ]
"float coneangle" [ 0.35 ]
# ribPatchMesh
AttributeBegin
Surface "stairs" "float steps" [ 100 ]
PatchMesh "bilinear" 2 "nonperiodic" 2 "nonperiodic"
"P" [ -1.0 -1.0 -1.0
1.0 -1.0 -1.0
-1.0 1.0 1.0
1.0 1.0 1.0 ]
AttributeEnd
WorldEnd
You can improve the quality by introducing a fuzz area to get
better anti-aliasing:
/* stairs */
surface
stairs(float Ka = 1, Kd = 0.5, Ks = 0.5, roughness = 0.1;
color specularcolor = 1;
float steps = 4, fuzz = 0.05;)
{
float p;
normal Nf;
normal front = ntransform("object", "current", normal(0, -1, 0));
normal up = ntransform("object", "current", normal(0, 0, 1));
float sv = mod(v * steps, 1);
if (sv < 0.5 - fuzz)
{
if (sv > fuzz || (v * steps) <= fuzz)
{
Nf = front;
}
else
{
p = 0.5 + sv / (2 * fuzz);
Nf = ((1.0 - p) * up + p * front);
}
}
else if (sv > 0.5 + fuzz)
{
if (sv < (1.0 - fuzz) || v >= (1.0 - fuzz / steps))
{
Nf = up;
}
else
{
p = (sv - 1.0 + fuzz) / (2 * fuzz);
Nf = ((1.0 - p) * up + p * front);
}
}
else
{
p = (sv - 0.5 + fuzz) / (2 * fuzz);
Nf = ((1.0 - p) * front + p * up);
}
Oi = Os;
Ci = Os * (Cs * (Ka * ambient() + Kd * diffuse(Nf)) +
specularcolor * Ks * specular(Nf, normalize(-I), roughness));
}
Exercise:
Try to think about other improvements you could introduce to make this solution available to a wider range of staircases. At the moment I assume that the staircase was modelled a certain way. How can you generalise it? For the case with 100 steps the difference between the real geometry and the shader version is hard to see but this is only true for this perspective from the camera. If you would look more or less straight from the front you should basically see only the front of the steps and nearly nothing from the top. This effect can be seen as well if you look at the top step of the staircase with only 10 steps in figure 12. How can you compensate for that in the shader?