Shading Languages

So far we have seen quite a variety of different techniques for modeling the appearance of 3D objects. These techniques include shading models, 2D image textures, bump mapping, reaction-diffusion textures, and solid textures. Through the use of sophisticated shading, we can render complex-looking objects even if the underlying geometry is simple. It should be clear that in most cases we are interested in combining several of these techniques in order to specify the appearance of an object. For example, we might want to mix several different textures together, apply a shading model to a surface whose reflectance properties are specified via a texture map, and whose normals are perturbed according to yet another texture map (perhaps using a solid noise function).

Many rendering systems implement a fixed number of parameterized shading models. The scene description language can choose among the types of light sources supported by the system, and specify one of the supported shading models for each surface along with the specific parameters for that surface. Clearly, such rendering systems significantly limit the range of effects that can be produced, and don't lend themselves to extensions when a new shading trick or a new type of procedural texture comes along. What we need is a systematic and modular way for specifying arbitrary complex and general shading mechanisms for 3D objects. In other words, we need a language for specifying shading. Such languages are referred to as "shading languages".

Shading languages were introduced by Cook in 1984 under the name of "Shade Trees". What is a shade tree? A shade tree can be viewed as a hierarchical data structure that describes a particular (complex) shading mechanism. Alternatively, it can be looked upon as a program for computing the shading on the objects with which the tree is associated. Note the strong analogy between shade trees and CSG trees (recall that a CSG tree is a hierarchical way of describing a solid, but it is also a program for computing the solid).

Before we explain what a shade tree is, we must define the a couple of terms. An appearance parameter is any value that can be used in a shading calculation: surface normal, the color of a light source, the specular exponent of a surface, a bump map, etc. A shading operation is any operation that can appear in a shading calculation, such as a dot product, vector normalization, etc. Each node in a shade tree corresponds to a basic shading operation. Each node produces one or more appearance parameters as output, and can use zero or more appearance parameters as input. Thus, a shade tree is really similar to the tree (or graph) that a compiler constructs when it parses an arithmetic expression in a conventional programming language, such as C. In order to evaluate the shading tree it is traversed in postorder, performing the calculation corresponding to each node after its children have been evaluated. The result of evaluating the root node is the final output of the shading calculation. Thus, one way to implement shade trees in a rendering system is to define a high-level programming language for specifying shading calculations. This language enables programmers to write shaders as routines with input parameters and output values. These shaders can be compiled or interpreted to produce the corresponding shade tree, which is then evaluated by the renderer as needed.
 

The RenderMan shading language

The basic idea of shade trees has been extended and implemented in the RenderMan shading language. The goals of this shading language were: This language is described in detail in the book "The RenderMan companion" by Upstill. The full specification is also available on the web.
 

Shader types

A shader is a procedure called to compute one or more values needed during rendering. The RenderMan interface specifies six types of shaders, distinguished by the inputs they use and the kinds of output they produce:

Light source shaders are given various parameters describing the light source and a point in the scene and return the color of the light reaching the specified point from the corresponding light source. There can be any number of such shaders in the scene, and each one can be turned on or off for any object in the scene.

Surface shaders computes the reflected light in a given outgoing direction at a point on a surface, using the normal, the light sources, and perhaps other information.

Volume shaders are associated with volumes of space both inside and outside the objects of a scene. They describe what happens to a ray of light passing through the volume from a specified origin to a specified destination. A volume shader can be associated with the interior of an object, with the exterior of an object, and with the atmosphere in the scene.

Transformation shaders lie on the blurred border between geometry and shading. They take a point in space and produce another point. They can use arbitrary defomations of space, and they are concatenated with the scene transformation.

Displacement shaders are somewhat similar to transformation shaders, but instead of deforming the entire space of the scene they perturb points on the surface of an object. Thus, they may use the properties of the surface at the point to be displaced, such as the normal, or any other property of interest.

Imager shaders transfrom an input color to another set of values, whose meaning can be arbitrary.

Data types:
float - the only scalar type in the language, no integers!
string - can't be modified
point - a vector of three floats
color - an abstract data type for representing colors, reflectances, opacities, etc.
A variety of operators and functions that operate of these data types is built into the RenderMan shading language.

A shader is defined by preceeding its declaration with one of the shading language keywords: light, displacement, surface, volume, transformation, or imager. Shaders can use functions, which are defined similarly to C. Functions can return float, color, point, or string. The default return type is float. All function parameters are by reference. Recursion is forbidden. Shaders and functions are specified similarly, except that the shader does not explicitly return values. Functions can be called by other shaders or functions, but shaders can't.

Global Variables: shaders can assume that certain global variables have been assigned valid values by the renderer before the shader is called. For example, a surface shader typically requires at least the point where the the reflected light is to be computed, and the direction of interest. This information is provided by the global point variable P and I, respectively. Other global variables serve as a means for the shader to communicate its results to the renderer. In surface shaders those variables are Ci and Oi, which represent color of light from surface and opacity of surface, respectively.
 
Instance variables: In the RenderMan interface, shaders are instanced in the course of the scene description. For example, different instances of light shaders are defined to place light sources in the scene, and surface shaders are instanced by attaching them to surfaces of objects in the scene. When the shader is instanced, it may use several instance variables as parameters. For example, a surface shader may have the instance parameters Ka and Kd defined. When attaching the surface shader to the surface, values for these parameters can be specified. These variables must have default values assigned to them when they are declared in the definition of the shader.

Shader classes and instances: RenderMan shaders resemble classes in an object-oriented language, such as C++. There is a general shader class from which the six main shader types are inherited. Whenever we write a RenderMan shader we inherit one of these six types. Writing a shader is like writing a class with a single public member function. The instance variables of the shader correspond to data members of the class (data members that are set once and for all by the class constructor, when an object of that class is defined). The global variables correspond to the parameters of the member function. When a particular shader is attached to a surface in the scene,  this corresponds to creating an instance of the class with the instance variable given as arguments to the class constructor.

Uniform and varying variables: All variables in the shading language (including instance variables) can be classified as uniform or varying. Uniform variables are variables that do not depend on position. Varying variables can change as a function of position. For example, the parametric coordinates of the position on the surface (s,t) are varying variables. The normal N is a varying variable for curved surfaces, but a uniform variable for planar surfaces. The distinction between uniform and varying variables is important, because it allows to significantly optimize the shader, once it has been bound to a particular surface.

Built-in functions: For ease of programming, many useful built-in functions are provided in RenderMan. These include many scalar math functions (sin(), radians(), sqrt(), clamp(), round(), etc), derivative functions (Du(), Dv()), a noise() function (in one, two, or three directions, returning either a point or a color), and various shading, coloring, and lighting functions: ambient(), diffuse(), phong(), specular(). There are also functions for access to texture maps, bump maps, environment maps, and shadow maps. Various geometric functions operating on points and normals are also provided.