Converting Shaders to use new シェーディング Language Features

Converting Shaders to use new シェーディング Language Features

March 1997

1   Introduction

The shader compiler has been completely rewritten for PhotoRealistic RenderMan in an attempt to greatly extend the シェーディング Language. New features and improvements in the compiler include:

  • The new compiler is able to compile large shaders as much as 10 times faster than the old compiler.
  • The new compiler generates shaders that can execute as much as 20% faster than the shaders generated by the old compiler.
  • Several new types are available in the シェーディング Language, including vector, normal, and matrix.
  • シェーディング Language now supports arrays.
  • Many new built-in functions are available, especially giving new capabilities for the matrix and string types.
  • New user function and scoping rules greatly increase the flexibility of modular programming in RSL.

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 application note details the ways in which you may wish to modify your existing shaders to take advantage of new functionality.


2   Using Shader Functions

2.1   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 that your shader called was reread and inlined into your shader code. This was a 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.

2.2   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.

2.3   More Info

Please refer to the About Shaders and Functions documentation for more details about this feature.


3   Living with Shader Lint

3.1   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 that, 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. サンプル include:

  • If you have typed point-like variables and parameters specifically as vector, normal, or point, the compiler will warn you if you are assigning mixed types (for example, assigning a vector quantity to a point).
  • Passing the wrong kind of point-like data to a function, for example, passing a point to diffuse() (a normal is expected).
  • Combining point-like data in a way that is not geometrically meaningful, for example, point ^ point (whereas the cross product of two vectors would be okay). Other examples include: point + vector yields a point, but point + point gives a warning; point - point is legal, yielding a vector; point . point is not legal, but vector . vector is fine. Use common sense and a little knowledge of geometric concepts, and you'll be okay.
  • Using the transform(), vtransform(), or ntransform() with the wrong point/vector/normal types.

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`.

3.2   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 PRMan*, 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 that 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.

サンプル of common space mistakes that the compiler will warn about:

  • Mixing points or colors which are suspected to be in different spaces, for example, adding a point in "current" space to a vector in "shader" space, or adding a color in "rgb" space to a color in "hsv" space.
  • Passing points in spaces other than "current" to functions that expect their arguments in "current" space (for example, shadow()).
  • Taking the noise value of a point that is not in "object" or "shader" space (since this will almost always product "crawlies" when animated).
  • Using the transform(), vtransform(), or ntransform() without matching the spaces correctly.

4   Remember Uniforms!

Variables in シェーディング 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 that 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 that 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.


5   Using vtransform and ntransform

Quite a while back, before the vtransform and ntransform functions were added to RSL, 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.