RenderMan Artist Tools

PhotoRealistic RenderMan
Application Note #17


Converting Shaders to use new Shading Language Features

March, 1997

Contents:

  1. Introduction
  2. Using Shader Functions
  3. Living with Shader Lint -- part 1: Point Types
  4. Living with Shader Lint -- part 2: Coordinate Spaces
  5. Remember Uniforms!
  6. Using vtransform and ntransform


1. Introduction

The shader compiler has been completely rewritten for PhotoRealistic RenderMan in an attempt to greatly extend the Shading Language. New features and improvements in the compiler include:

For the most part, old shaders should continue to run without modification. However, some shaders may need to be altered, either to conform to the newer language syntax, or to eliminate warnings generated by the new "shader lint" (and thus, take advantage of the hints the new compiler will give).

The remainder of this app-note details the ways in which you may wish to modify your existing shaders to take advantage of new functionality.


2. Using Shader Functions

No more separate compilation

The old compiler generated separate .slo files for each function. When your shader finally compiled, the pre-compiled slo for each function which your shader called was reread and inlined into your shader code. This was the source of many problems.

The new compiler does not support separate compilation of functions. Instead, you should declare your functions either in the same source file as your shader, or put the function definition in a header (.h) file and use #include to tell the compiler to include the source for the function. In some sense, this makes functions more like the "Pascal model", where you define your functions in the same source module before using them, whereas the old way was more like the "C model" of separate compilation of modules.

Declare function parameters as output if you modify them

It used to be that function parameters were passed by reference, and the function could overwrite any of its parameters, thus changing its value. Now it is illegal to write to a function parameter, unless it is declared using the output keyword. For example, you can now no longer do this:
	float foo (float bar)
	{
	    bar = 1;    /* Illegal! Cannot write to parameter. */
	    ...
	}

But by using the output keyword, you may change the value of the parameter:

	float foo (output float bar)
	{
	    bar = 1;    /* OK! */
	    ...
	}

Remember that parameters are still passed by reference. Be careful of the "aliasing" that will occur if you pass the same variable as more than one of the function's parameters.

More Info

Please refer to the Shading Language Extensions document for more details about additional functionality of this feature.


3. Living with Shader Lint -- part 1: Point Types

The new compiler has much better error detection and recovery than the old one. In general, errors should have more clear error messages, and should try to report several errors (rather than terminating after the first one).

In addition to reporting lexical, syntactic, and semantic errors, nshadecom has a feature that we call "shader lint". Like the old "lint" for C, shader lint attempts to find shader code which, while technically valid, is likely to be an error of intent on the part of the shader programmer.

Unlike C lint, shader lint is not a separate program. It is part of the compiler. By default, the compiler operates in lint mode, producing warning messages when it sees questionable code. These messages are just warnings, and your shader will still compile into valid .slo files. But you are encouraged to pay attention to the warnings; while it's possible that you're just using a trick that confuses the lint system, it's very likely that a lint message indicates that you may be making a big mistake.

One aspect of shader lint is strong typing of point types. Previously, there was only one point type which was used to represent three different geometric concepts: (1) spatial positions, (2) directions, and (3) surface normals. However, these three geometric entities represent different concepts and have subtly different usage properties. Now there are three separate types corresponding to these concepts: point, vector, and normal, respectively.

The compiler tries to warn you about misuses of these types. Examples include:

If you try compiling old shaders written before these types were available, you will notice a series of warning messages. For example, below is the old plastic.sl shader, and the output of the new compiler if you were to compile it:

    surface
    plastic (float Ka = 1;
             float Kd = .5;
             float Ks = .5;
             float roughness = .1;
             color specularcolor = 1;)
    {
      point Nf;
      point V;

      Nf = faceforward (normalize(N),I);
      V = - normalize(I);
      Oi = Os;
      Ci = Os * ( Cs * (Ka*ambient() + Kd*diffuse(Nf)) +
                  specularcolor * Ks*specular(Nf,V,roughness));
    }


    -------

    "plastic.sl", line 11: Warning: Type mismatch.
            Assigned a normal to a point

    "plastic.sl", line 12: Warning: Type mismatch.
            Assigned a vector to a point

    "plastic.sl", line 14: Warning: diffuse, arg 1: type mismatch
            vector or normal expected instead of point

    "plastic.sl", line 15: Warning: specular, arg 1: type mismatch
            vector or normal expected instead of point

    "plastic.sl", line 15: Warning: specular, arg 2: type mismatch
        vector expected instead of point

    plastic: compiled.

The shader did compile, but not without a long list of warnings. Careful inspection of the warning messages indicate that they are all related to point/vector/normal types. If you assign a vector-like quantity to a point, for example, you will get a warning. The problem here is that we've declared Nf and V as points, even though they are really used to hold normals and vectors, respectively.

The following change to plastic will compile without any warnings (note the slightly changed declarations):

    surface
    plastic (float Ka = 1;
             float Kd = .5;
             float Ks = .5;
             float roughness = .1;
             color specularcolor = 1;)
    {
      normal Nf;
      vector V;

      Nf = faceforward (normalize(N),I);
      V = - normalize(I);
      Oi = Os;
      Ci = Os * ( Cs * (Ka*ambient() + Kd*diffuse(Nf)) +
                  specularcolor * Ks*specular(Nf,V,roughness));
    }

You can probably eliminate the majority of lint warnings from your old shaders simply by correcting the declarations of Nf and V.


4. Living with Shader Lint -- part 2: Coordinate Spaces

At the start of shader execution, all point, vector, normal, and matrix variables are expressed in the "current" coordinate system. Exactly which coordinate system is "current" is implementation-dependent. It just so happens to be that "current" is "camera" for PhotoRealistic RenderMan, but you should never count on this behavior -- it is entirely possible that other RenderMan compliant renderers (including future renderers from Pixar) may use some other space (like "world") as "current" space.

Some calculations must be done in "current" space. For example, the direction vector passed to diffuse() must be a normal in current space.

But other calculations are not advisable to do in current space. For example, you probably don't want to take noise values of P, which is in current space, because if the object moves, the texture will slide and stretch over the object. The solution to this problem generally involves transforming to "shader" space, like this:

	point Psh;
	Psh = transform ("shader", P);
	n = noise (Psh);

The problem is, it is not geometrically meaningful to combine points which are represented in different spaces. This is a common SL programming error. The new compiler will warn you when it thinks that you are combining points in different spaces.

Examples of common space mistakes that the compiler will warn about:


5. Remember Uniforms!

Variables in Shading Language may either be uniform -- meaning that its value is the same for all points on the surface, or varying -- meaning that its value may be different for every point on the surface. Parameters passed to the shader are assumed to be uniform, unless they are explicitly declared as varying. Local variables in the shader are assumed to be varying, unless they are explicitly declared as uniform.

Computations involving uniform expressions can be performed significantly faster than the same computations involving varying quantities. It is important to declare as uniform any local variables which you know will not need to vary from point to point on the surface. One example of such a variable is a loop control variable. For example, consider the following loop which computes several octaves of fractional Brownian motion:

	float i;
	float n = 0;
	float a = 1;
	float Q = transform ("shader", P);
	for (i = 0;  i < 5;  i += 1) {
	    n += a * noise (Q);
	    a *= 0.5;  Q *= 2;
	}

This loop could run considerably faster if recoded as follows:

	uniform float i;
	float n = 0;
	uniform float a = 1;
	float Q = transform ("shader", P);
	for (i = 0;  i < 5;  i += 1) {
	    n += a * noise (Q);
	    a *= 0.5;  Q *= 2;
	}

Notice that we've recoded the loop control variable i, and the amplitude variable a, as uniforms. Since all manipulations of i and a are done identically for all points on the surface, this is safe. In contrast, n is necessarily varying, since it gets the result of a noise field which has a different value at every point.

It has always been the case that faster execution would result by recoding variables like a as uniform. But the new compiler can now take particular advantage of uniform expressions in the conditional expressions of if, while, and for statements. This optimization can speed up shader execution by large amounts for shaders with many conditionals or loops. We have experienced speedups of nearly 20% in overall rendering speed as a result of carefully applying this optimization to our complex shaders.


6. Using vtransform and ntransform

Quite a while back, before the vtransform and ntransform functions were added to Shading Language, it was common to see constructs like this in shaders:

	point Nsh;
	Nsh = transform ("shader", N + point "shader" (0,0,0));

The addition of point "shader" (0,0,0) accounts for the fact that N is really a vector (or normal), and thus should not transform in the same way as a point.

This is the old way of transforming vectors and normals. These days, we encourage you to use the following equivalent (but less confusing and cheaper!) construct:

	normal Nsh;
	Nsh = ntransform ("shader", N);
For vectors (as opposed to normals), one should use the vtransform function.
 

Pixar Animation Studios
(510) 752-3000 (voice)   (510) 752-3151 (fax)
Copyright © 1996- Pixar. All rights reserved.
RenderMan® is a registered trademark of Pixar.