Additional Features & Limitations

Additional Features & Limitations

This section describes the specific features and limitations of the implementation of the RenderMan Shading Language present in PRMan.


Additional Features

Shading Language DSOs

As of version 3.8, PRMan allows you to write new built-in SL functions in C or C++. Such functions overcome many of the limitations of SL-defined functions. Full documentation on the syntax, programming requirements, capabilities and limitations of this new feature are provided in the extensive on-line document RenderMan Shading Language Plugins.

Ignoring Out-of-range Texture Values

PRMan 10.0 and higher support a new ignoreabove option for all texture-related shadeops. This allows texture filtering to ignore values over a user specified threshold. The option specifies a single float as an argument. All values which exceed the specified value are not averaged into any filtering that is done. This is mostly useful for non-mipped map floating point shadow or zfile textures. Often infinite values cause strange shadow behavior along silhouette edges, this may help alleviate some of this.

String Constant Concatenation

PRMan 10.0 and higher support string constant concatenation, using C syntax:

string s = "Hello" "World";

Message Passing for Shader Execution

Sending parameters with rays to shaders on hit objects

When gather() is used to trace rays into the scene, it can send arbitrary values with each ray. The sent values are used to override shader parameters with matching names in the shaders on objects hit by the rays. The syntax is:

float val = (some override value);
gather(..., "send:surface:Ks", val, ...)

which means take the local (shooting shader) variable "val" and use its value to override parameter "Ks" of the hit surface shader. These sent overrides are far more general than the alternative string-only "label" mechanism, and the receiving shader need not use rayinfo() or be otherwise "ray-aware" to use them. Values sent in this way can be of any type. If a particular shader does not have the given parameter (same name and type), then the send is ignored. Gather retrieves values, such as the color of the hit surface, using similar variable-fetching syntax.

Sending parameters to LightSource shaders

Similarly, surface and volume shaders calling illuminance() can now send parameter overrides to lightsource shaders directly. illuminance() can also fetch back auxilliary values computed inside lightsource shaders as well. These extensions, which share the gather() messaging syntax, complement the existing lightsource and surface message passing functions: the new "forward" send requires no special knowledge in the lightsource, and variables other than predefined "output" parameters can be fetched.

Here is an example that sets the light's "intensity" parameter and retrieves the light's "temperature" value after execution, if it exists:

  float k = 0;
  float val = (some override value);
  illuminance(..., "send:light:intensity", val, "light:temperature", k) {
      ... illuminance block, using k
  }

Just as with gather, above, if the lightsource shader does not have the specified variables, then sends are ignored and fetches do not change the current local value. Note that illuminance sends and fetches are restricted to the "light:" namespace of queries.

Explicit Light Cache Control

As an optimization, PRMan has always cached the results of light shader invocations between multiple calls to illuminance. This may sometimes lead to the wrong result if rerun light shaders change their output values due to a combination of message passing and changes in the invoking surface or atmosphere shader. In prior releases, this behavior could be defeated by the use of "P = P" prior to new calls to illuminance, or by using illuminance("category", P+vector(0), ...).

Since version 12.5, PRMan allows explicit light-cache control via the illuminance parameter "lightcache". The parameter is used this way:

illuminance("category", ..., "lightcache", "reuse")

or:

illuminance("category", ..., "lightcache", "refresh")

The default setting is "reuse", which is the existing behavior: to use previously cached output values for all light shaders which match the category, if they exist from previous invocations of the light shaders (due to previous illuminance statements). The setting of "refresh" clears the output values and reruns the light shaders, again for only those light shaders which match the category.

Using "lightcache" "refresh" is more efficient than the old method of using "P=P" before the illuminance call, because only the lights matching the category are affected. It is similar to using illuminance("category", P+vector(0)), but it is more explicit, easier to understand, and possibly more efficient.


Limitations

The Shading Language compiler and interpreter in PRMan has certain limitations that must be taken into consideration when shaders are written for PRMan. If these are not understood, shaders that seem to compile correctly will produce incorrect results.

Area Functions Inside Conditionals

One shouldn't use any area or neighborhood-sensitive functions inside conditional blocks whose conditional test depends upon varying expressions. The sensitive Shading Language functions are: texture(), environment(), bump(), shadow(), Deriv(), Du(), Dv(), area(), calculatenormal(). These functions depend on comparing various values at more than one point on the surface. A conditional will partition the object being shaded into two sets, those that passed the conditional and those that did not. Values are undefined in places where the conditional failed, but they are still referenced by their neighbors who did pass the conditional. This causes the comparisons to be undefined, and leads to visible artifacts. As a result, the following:

if (u < 0.5) {
        s0 = u * 2;
        Ci = texture("foo", s0, t);
} else {
        s0 = (u - 0.5) * 2;
        s0 = s0 * s0;
        Ci = texture("foo", s0, t);
}

should be coded as:

if (u < 0.5) {
        s0 = u * 2;
} else {
        s0 = (u - 0.5) * 2;
        s0 = s0 * s0;
}
Ci = texture("foo", s0, t);

This evaluates the texture access outside the conditional, allowing it to filter correctly.

Note that this is not a problem for conditional blocks whose conditional test depends upon a uniform expression, since the object cannot be partitioned into two sets by a uniform expression, by definition.

Broad Solar Lights

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.

PRMan does support 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, PRMan 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.