Pixar
January, 1996
Several proprietary extensions have been made to the Shading Language in PhotoRealistic RenderMan 3.6, in order to facilitate writing more powerful systems of shaders. There are several new or enhanced built-in functions, two new variable types for vectors, better control of specular and diffuse lighting, a generalization of distant lights for environment mapping, and a new persistent data type which provides the basis of a "blackboard"-style message passing system.
The following new built-in functions have been added to the Shading Language:
float inversesqrt( float x )
float random( )
point random( )
point vtransform( string fromspace, tospace; point V )
point vtransform( string tospace; point V )
point ntransform( string fromspace, tospace; point N )
point ntransform( string tospace; point N )
color ctransform( string fromspace, tospace; color C )
color ctransform( string tospace; color C )
point rotate( point Q; float angle; point P0, P1 )
float filterstep( float edge, s1 [, s2]; [parameterlist] )
float cellnoise( float x )
float cellnoise( float s, float t )
float cellnoise( point pt )
float cellnoise( point pt, float t )
point cellnoise( float x )
point cellnoise( float s, float t )
point cellnoise( point pt )
point cellnoise( point pt, float t )
color cellnoise( float x )
color cellnoise( float s, float t )
color cellnoise( point pt )
color cellnoise( point pt, float t )
The following built-in Shading Language functions have been extended to add greater flexibility and to increase the overall efficiency of shaders which use them.
float min( float a, b, ... )
point min( point a, b, ... )
color min( color a, b, ... )
float max( float a, b, ... )
point max( point a, b, ... )
color max( color a, b, ... )
float clamp( float a, min, max )
point clamp( point a, min, max )
color clamp( color a, min, max )
float mix( float fg, bg; float value )
point mix( point fg, bg; float value )
color mix( color fg, bg; float value )
float spline( [string basis;] float value; float f1,f2,...,fn,fn1 ) color spline( [string basis;] float value; color f1,f2,...,fn,fn1 ) point spline( [string basis;] float value; point f1,f2,...,fn,fn1 )
float noise (point pt, float t) color noise (point pt, float t) point noise (point pt, float t)
float shadow ( ..., parameterlist )
float texture ( ..., parameterlist ) color texture ( ..., parameterlist ) color environment ( ..., parameterlist )
The RenderMan Interface Specification Version 3.1 specifies that the Shading Language has exactly four data types: float, point, color, and string. This set has been extended to include two variants on point types: vector and normal.
Variables declared as vector and normal behave identically to variables declared as point in most situations. point, vector and normal variables can be arbitrarily assigned from one to another and can be used identically in expressions and function arguments. There is no implicit type checking or type coersion in any of these standard uses. There are exactly two differences in behavior between the three types: transformation of shader parameters provided through the RI, and casting values to these types in the Shading Language.
When a shader parameter is declared (with Declare in a RIB file or with RiDeclare in an RI program) to be a vector, any value provided for that variable in any RI call will be transformed into current space using the vtransform function described above. When a variable is declared to be a normal, any value provided for that variable in any RI call will be transformed into current space using the ntransform function. Note that the declaration of the variable in the Shading Language code does not invoke this functionality (in the current implementation). It is the declaration in the RI stream that determines this behavior.
When a triple of floating point values is assigned to a point, vector or normal variable using the type cast operator, an optional coordinate system name can be specified, such as
foo = point "object" (0, 0, 0); bar = vector "world" (s, t, u); baz = normalize( normal "world" (xcomp(N), 0.5, zcomp(N)) );These operators now transform the triples of floats from the named coordinate system to the current coordinate system using the appropriate transformation routine. Variables declared as vectors and normals do not automatically call vtransform or ntransform when used as arguments or destinations of the point transform routine. This is left to the SL programmer.
Note: Prior to PRMan 3.6,
the keyword vector was reserved in the
Shading Language, but it was an alias for point,
and did not have the new behavior described above.
3. Lighting Control
The specular and diffuse shadeops now respond to two special parameters of the light shader. Any light shader which contains the following parameter
float __nondiffuse = 1
will be ignored by diffuse (note that there are two underbars). Similarly, any light which contains the following parameter
float __nonspecular = 1
will be ignored by both specular and phong. Please note that if the value of the parameter is 0.0, it is not ignored, so this behavior can be controlled both from the RIB file, or procedurally from inside the light shader, if desired. These variables may be either uniform or varying. These special parameters can also be accessed within illuminance statements, as described in the section on Message Passing.
The illuminance statement, as well as the specular and diffuse shadeops, now correctly ignore all ambient lights (lights where L = (0,0,0)).
The solar block is used by distantlight to specify a light which is emitted from infinity along a particular axis direction. In this use, the solar block takes two arguments, the axis vector A, and 0.0, which is the angular width of the emission cone. The RenderMan specification refers to the fact that this cone can have non-zero width, and specifically mentions the use of solar with no arguments to write an environment mapping light source using a 360 degree cone, but PhotoRealistic RenderMan has never supported this feature.
PhotoRealistic RenderMan now supports solar cones of any angular width, including the zero-argument omnidirectional case. The meaning of a non-zero width solar cone is extremely confusing to explain. Nonetheless, imagine a distant light source which is willing to emit light in any of a range of directions (not just down its axis vector), and merely needs the renderer to tell it which direction is pointed most favorably toward the specular highlight of surface. Another way of thinking of it is a large area light source, located at infinity, where each point on the area light is emitting a little spotlight.
The most obvious example is an environment-mapping light. The light wants to cast the environment texture map like a slide projector from infinity, and just needs to know which pixel on the map will reflect back into the camera. The pixel to choose is based entirely on the reflection direction R of the surface; the light itself is willing to send light in any direction as needed.
The second example is a diffuse skylight, where light is arriving on the surface from everywhere above it, and would like to be colored by all of it (radiosity-style). In this case, the solar light cone would be a hemisphere pointed down.
At this time, PhotoRealistic RenderMan will not point-sample the area light source, but it will attempt to find the most favorable direction by considering the size of the solar cone and the surface's reflection direction. The light direction vector L inside the solar block will be this direction. In the specific case of an omnidirectional light, this L will be exactly equal to -R. For narrower solar cones, it will be some vector between -R and A, such that it falls within both cones and is somewhat central to the intersection of the cones.
Here is a light source which casts an environment map onto a surface. Notice the use of the __nondiffuse flag to put the environment only in the specular component (since environment maps are really specular reflections of the worldsphere).
light envir ( string mapname = ""; float __nondiffuse = 1; ) {
solar() {
Cl = environment(mapname, vtransform("world", -L));
}
}
Note: Some surface shaders use non-standard reflection directions for their specular highlights. For example, some anisotropic shaders (such as brushedmetal) have multiple specular highlights, none of them in the "normal" place. solar's guess about which direction is the most favorable direction will be wrong in these cases.
There are four new Shading Language functions which permit shaders to peek at the parameters of the other shaders that are being executed on the same piece of geometry. This permits shaders to customize their behavior based on their knowledge of what the other shaders will do with those parameter settings.
float atmosphere( string param; {float|point|color|string} var )
float displacement( string param; {float|point|color|string} var )
float lightsource( string param; {float|point|color|string} var )
float surface( string param; {float|point|color|string} var )
For example, the new diffuse shadeop, which notices the special __nondiffuse light parameter, can now be written as follows:
color diffuse( point N ) {
color C = 0;
point Nn, Ln;
uniform float nondif;
Nn = normalize( N );
illuminance( P, Nn, PI/2 ) {
#if EITHER_WAY
nondif = 0;
lightsource("__nondiffuse", nondif);
#else
if (lightsource("__nondiffuse", nondif)==0)
nondif = 0;
#endif
if (nondif == 0) {
Ln = normalize( L );
C += Cl * Ln.Nn;
}
}
return C;
}
Parameters to a shader typically cannot be modified by the shader. For example, the following shader
surface foo ( float bar = 1 ) {
bar = 2;
}
will generate the following error if compiled
"foo.sl", line 2: shader "foo" - warning: "bar" readonly but modified
This can now be eliminated by the use of the new keyword output:
surface foo ( output float bar = 1 ) {
bar = 2;
}
Shaders can set flags and send them as "messages" to other shaders, by placing them in this globally-visible "blackboard" of output parameters, where they can be received (using the parameter-reading functions above) by any other shader that is savvy to their existence.
A more powerful variation of this theme is to use output varying parameters. This way, shaders can compute arbitrary values that vary across the surface, and pass them to other shaders. For example, consider the following pair of shaders, which shade a displaced surface based on the original shape (if that information is available).
displacement wavy (float height = 1; output varying point oldP = 0;) {
point Po = transform("object", P);
point No = ntransform("object", N);
oldP = Po;
Po += height * normalize(No) * sin(2*PI*s);
P = transform("object", "current", Po);
}
surface prepainted (float Kd = .5) {
point Po;
if (displacement("oldP", Po)==0)
Po = transform("object", P);
Ci = Kd * texture("monasmile.tx", xcomp(Po), ycomp(Po));
}
Notice that prepainted will work correctly if there is no displacement shader, and do the best it can if the displacement does not set oldP, but will have the special effect if oldP does exist.
Unlike standard varying parameters, output varying parameters do not need to be bound to the geometry in the RIB file. However, there is currently an implementation limitation (aka bug) whose effect is to make the default initialization values and the RIB overrides of those defaults not always be correct for output varying parameters. The programmer should not rely on those values, even within the shader itself. In other words, it is best for a shader to consider its output varying parameters to be write-only.
The wily programmer may ask: At what point in the shading pipeline do output parameters get set so that I can read them? (Since there is no restriction on the use of the reading functions, one might imagine that the displacement shader might try read an output of the atmosphere shader before the atmosphere has ever been run.) The answer is complicated, but the basic rules are:
There are functions which have always been available in the Shading Language that are not documented in the RenderMan specification, and have therefore faded into undeserved obscurity. These functions are alive and well, and are available for use.
float ptlined( point P0, point P1, point Q )
float pnoise( float v, float period )
float pnoise( float u, v, float uperiod, vperiod )
float pnoise( point pt, point period )
float pnoise( point pt, float t, point pperiod, float tperiod )
point pnoise( float v, float period )
point pnoise( float u, v, float uperiod, vperiod )
point pnoise( point pt, point period )
point pnoise( point pt, float t, point pperiod, float tperiod )
color pnoise( float v, float period )
color pnoise( float u, v, float uperiod, vperiod )
color pnoise( point pt, point period )
color pnoise( point pt, float t, point pperiod, float tperiod )