Shading Language Features and Limitations

Extended Features

Shading Language DSOs

Ignoring Out-of-range Texture Values

String Constant Concatenation

attribute()

option()

texture()

shadow()

environment()

textureinfo()

diffuse() and specular()

_lightingstart() and _needsurface()

while "any" ()

gridpattern()

Message Passing for Shader Execution

Explicit Light Cache Control

Limitations

Area Functions inside Conditionals

Broad Solar Lights


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

Extended 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";

attribute()

In PRMan, the pieces of attribute state that can be retrieved via the attribute() function are:

Name Type   Comment
"ShadingRate" float    
"Ri:ShadingRate" float   Same as ShadingRate. This form is preferred
"Matte" float    
"Ri:Matte" float   Same as Matte. This form is preferred
"Sides" float    
"Ri:Sides" float   Same as Sides. This form is preferred
"Ri:Color" color    
"Ri:Opacity" color    
"Ri:DetailRange" float[4]    
"Ri:TextureCoordinates" float[8]    
"Ri:Orientation" string   Either inside or outside 
"Ri:ShadingInterpolation" string   Either smooth or constant 
"Ri:Transformation" matrix   The object to world transformation 
"cull:backfacing" float    
"cull:hidden" float    
"derivatives:centered" float    
"derivatives:extrapolate" float    
"dice:binary" float    
"dice:hair" float    
"dice:rasterorient" float    
"displacementbound:sphere" float    
"displacementbound:coordinatesystem" string  
"grouping:membership" string The name of the tracegroup or an empty string if there is no group (NOTE: due to an optimization, this is not implemented for surfaces with a ray depth of zero).
"geometry:backfacing" float 1.0 if the geometry has had its orientation reversed (normals flipped), which may be due to various factors (negative determinant, orientation, or double shading). 0.0 otherwise.
"GeometricApproximation:motionfactor" float    
"identifier:name" string  
"irradiance:handle" string  
"irradiance:filemode" string  
"irradiance:maxerror" float  
"irradiance:maxpixeldist" float  
"photon:estimator" float  
"photon:globalmap" string  
"photon:causticmap" string  
"photon:shadingmodel" string  
"sides:backfacetolerance" float  
"sides:doubleshaded" float  
"shade:strategy" string  
"stitch:enable" float  
"stitch:group" float  
"stochastic:sigma" float  
"trace:bias" float  
"trace:displacements" float  
"trace:maxspeculardepth" float  
"trace:maxdiffusedepth" float  
"trace:samplemotion" float  
"user:myname" variable allows a shader to access a user defined attribute name myname, and requires a variable with type matching the RiDeclare declaration.
"visibility:camera" float  
"visibility:diffuse" float  
"visibility:specular" float  
"visibility:transmission" float  
"visibility:photon" float  

option()

In PRMan, the pieces of option state that can be retrieved via the option() function are:

Name Type Comment
"BucketSize" float[2] returns the bucket size
"limits:bucketsize" float[2] Same as BucketSize. Preferred.
"limits:gridsize" float  
"limits:texturememory" float  
"limits:brickmemory" float  
"limits:geocachememory" float  
"limits:zthreshold" color  
"limits:othreshold" color  
"limits:extremedisplacement" float  
"Clipping" float[2] see RiClipping
"Ri:Clipping" float[2] Same as Clipping. Preferred.
"CropWindow" float[4] see RiCropWindow
"Ri:CropWindow" float[4] Same as CropWindow. Preferred.
"DepthOfField" float[3] see RiDepthOfField
"Ri:DepthOfField" float[3] Same as DepthOfField. Preferred.
"DeviceResolution" float[3] returns the resolution in x and y and the pixel aspect ratio. These are usually the three numbers passed to RiFormat, but may be different when FrameAspectRatio or ScreenWindow are non-square;
"Format" float[3] returns the current resolution as specified in RiFormat
"Ri:Format" float[3] Same as Format. Preferred.
"Frame" float returns the current frame number as specified in RiFrameBegin
"Ri:FrameBegin" float Same as Frame. Preferred.
"FrameAspectRatio" float  returns the current frame aspect ratio
"Ri:FrameAspectRatio" float  Same as FrameAspectRatio. Preferred.
"Hider" string  returns the name of the current hider
"RiHider:name" string  Same as Hider. Preferred.
"RiHider:jitter" float   
"RiHider:mpcache" float   
"RiHider:mpmemory" float   
"RiHider:mpcachedir" string   
"RiHider:samplemotion" float   
"RiHider:subpixel" float   
"RiHider:maxvpdepth" float   
"RiHider:zfile" string   
"RiHider:reversesign" float   
"RiHider:depthbias" float   
"RiHider:emit" float   
"RiHider:secondarycaustics" float   
"RiHider:extrememotiondof" float   
"RiHider:sigma" float   
"RiHider:sigmablur" float   
"Shutter" float[2]  see RiShutter
"Ri:Shutter" float[2]  Same as Shutter. Preferred.
"searchpath:resource" "searchpath:shader" "searchpath:texture" "searchpath:archive" "searchpath:procedural" string returns the indicated searchpath
"shadow:bias" float  
"shutter:offset" float  
"statistics:endofframe" float Enable end-of-frame statistics.
"statistics:filename.xml" string The name of the statistics XML file
"texture:enable gaussian" float  
"texture:enable lerp" float  
"trace:maxdepth" float  
"trace:specularthreshold" float  
"user:myname" allows a shader to access a user defined option name myname, and requires a variable with type matching the RiDeclare declaration.

texture()

The additional arguments that may be used in PRMan with the texture() function:

Attribute Value Comment
"filter" "box" this is the default filter if not specified
"filter" "disk" optimized for large blur sizes, and (unlike the other filters) is free of mipmap artifacts because it uses all mipmap levels in its computations; it is about twice as expensive as the other filters. 
"filter" "gaussian" "gaussian" should produce higher quality results.
"filter" "lagrangian" The Lagrangian filter is designed from the ground up for speed and sharpness. Texture files with MIP map pyramids are required, and full pyramids are recommended. For each sample, the system selects the next highest resolution image plane and performs a 4x4 bicubic interpolation combined with a 4x4 decimation filter in one pass. The result is equivalent to a point sample of an exactly rescaled and subpixel-shifted image.
"filter" "radial-bspline" specifically designed for optimally filtering displacement textures.
"filter" "ewa" The elliptical weighted average filter uses an elliptical region in S-T space to filter textures. The ellipse may be specified explicitly using the four-point invocation of the texture() call, where the four points define the convex hull of the ellipse. The order of the four S-T points does not matter.
"lerp" 0,0 selects whether to interpolate between adjacent resolutions of a multi-resolution texture in order to smooth the transition between resolutions."lerp" takes a uniform float value. Possible values are 0.0 to disable the interpolation and 1.0 to enable it. The default is 0.0 to not interpolate.

diffuse() and specular()

In PRMan, the specular and diffuse shadeops 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 the Specification.

shadow()

Soft Shadows

The shadow() function in PRMan has been enhanced to support soft shadows with true penumbral fadeout, simulating shadows of area light sources. The method uses multiple rendered shadow maps to infer visibility information from a light source whose extended geometry is also specified in the shadeop.

The enhanced shadow call is fully backwards-compatible with the Specification, but supports a new form to allow specification of multiple shadow maps in a single call:

float shadow(string maplist, texture coordinates[,parameterlist])

where maplist is a comma-separated list of shadowmap filenames.

In the past, the location of the point light source (as far as shadows were concerned) was implicit in the shadow map matrices. When using multiple maps to simulate an area light source, the user must also specify the size and shape of the intended light source in the shadow shadeop. The following new parameters to shadow support this new soft shadow method:

The old parameters "bias" and "samples" still apply when using the new soft-shadow form:

This new method of generating soft shadows requires multiple shadow maps to infer geometry between the surface being shaded and the light source. For best results, the views should be placed on or near the light source, pointing in the direction of the objects that will cast shadows. All views should fully contain the shadowing objects, although they will contribute slightly different information (due to their differing viewpoints). Specifying more views gives the renderer more information to work with, and thus can improve the quality of the shadows rendered. However, too many views can be unnecessarily expensive, so caution is advised. For most scenes, three to five shadow maps will be sufficient to capture the geometry sufficiently.

The shadow maps that are used for soft shadow information contain additional mip-map information that makes the soft shadow evaluation more efficient. When creating the .shad files, instead of txmake -shadow file.z file.shad you should use txmake -minmaxshadow file.z file.shad which will generate an extended shadow map file. These files are backwards-compatible with the normal shadow call (the new files can be used in the standard calls), but are required when using the soft shadow form.

An example of the soft-shadow form of shadow, using four shadow maps and a triangular light source would be:

attenuation = shadow("view1.shad,view2.shad,view3.shad,view4.shad", Ps, "source",
			Pl1, Pl2, Pl3, "gapbias", 0.0015, "bias", 0.001);

where Pl1, Pl2, and Pl3 are uniform points (the vertices of the triangle), and view1.shad, view2.shad, view3.shad and view4.shad are new-format shadow map files.

Deep Shadows

In PRMan 11.0 support for "deep shadowmaps" was added. This output format stores more information per pixel than a normal shadowmap, and has three main advantages over normal shadowmaps:

Creation of these new shadow files is achieved with a new display driver, deepshad.

Starting in 11.0, deep shadow files are supported by the shadow() shadeop, which means that existing shaders can use these shadow files.  In addition, the shadow() shadeop can now return a color (like texture()), which is necessary to take advantage of any color information in a deep shadow file.

Currently, the only way to access deep texture maps is through the shadow() shadeop.

Ray Traced Shadows

The shadow call can also trace rays to compute whether a surface point is in shadow or not, as an alternative to using shadow maps.  This functionality was added in release 11.0.  Ray tracing is used when the special keyword "raytrace" is supplied as the "shadow map" name.  For example, a light source shader might contain a line like this:

    attenuation = shadow("raytrace", Ps, "samples", samples);
Since the shadow map name is usually passed in as a shader parameter from the RIB file, many light source shaders can be used for ray tracing just by supplying "raytrace" as the map name, without even needing to recompile the shader.  However, because ray traced shadow blur and other parameters can add new effects, most people will probably want to write new shaders.

Note: the transmission function provides a direct interface to ray-traced shadow determination, with additional capabilities and options.

Note that shadow rays will only intersect objects that have their transmission visibility enabled.  This attribute controls whether shadow and transmission rays can see particular primitives, which is similar to including or excluding particular objects from the shadow map generation pass when using the traditional map-based shadow call.

Ray traced shadows can be used to obtain colored shadows from semi-transparent objects.  These colored results are obtained by assigning the results of the shadow call to a color rather than the typical float; this is similar to the deep shadow behavior described above.

The ray-tracing version of shadow() accepts the following optional parameters:

environment()

Ray Traced Environments

The environment call usually looks up reflection colors in a previously rendered environment map.  Starting with release 11.0, it can alternatively trace rays to reflections.  This functionality overlaps signficantly with the trace and gather calls, but is provided so that existing shaders that make environment calls can continue to be used.  Ray tracing will be used when the special keyword "raytrace" is provided as the environment map name.  For example, a surface source shader might contain a line like this:

    color e = environment("raytrace", dir);
Since the environment map name is usually passed in as a shader parameter from the RIB file, many existing surface source shaders can be used for ray tracing just by supplying "raytrace" as the map name from the RIB file, without even needing to recompile the shader.

Note that environment rays will only intersect objects that have their trace visibility enabled.  This attribute controls whether environment, gather, and trace rays can see particular primitives, which is similar to including or excluding particular objects from the environment map generation pass when using the traditional map-based environment call.

When the function return value is assigned to a float, rather than a color, then environment returns occlusion, which is the fraction of rays that hit something; when samples=1 then the result will be either 0 or 1, when samples > 1 then the result will be fractional.

When ray tracing environments, the following optional parameters are supported:

textureinfo()

In addition to the standard data names listed in Table 15.3 in the Specification, the textureinfo() shadeop supports additional query modes.

The exists option can be used to query whether a texture map is actually present, with the shadeop itself returning 1 if the texture exists or 0 otherwise. The variable passed in as the third argument will be ignored. This allows for the following usage:

if (textureinfo(texturename, "exists", 0)) {
    Ci = texture(texturename);
} else {
    Ci = color(1, 0, 0); /* Mark color as red if texture map is missing */
}

The pixelaspectratio returns a float, which is the original aspect ratio of the source image used to generate the texture map (useful because a resize operation is often involved when creating the texture map). This lookup does not work on environment maps.

_lightingstart() and _needsurface()

PRMan 10 and higher support two special no-op shadeops which serve as optimization hints to the Irma re-renderer.

The _lightingstart() shadeop marks the end of light-independent calculations (texturing and noise for example), and the beginning of light dependent calculations (such as calls to diffuse(), illuminance(), etc); this allows Irma to cache all shading results which occur before lighting.

The _needsurface() shadeop works in the same way for atmosphere shaders, except that it should be inserted just prior to any call which sets the value of the output color Ci.

The shader compiler attempts to place both shadeops in reasonable parts of the shaders. For complicated shaders, the result may not always be optimal and can be overridden by explicitly adding the statements to the shader sourcecode. For example:

surface
matte( float Ka=1, Kd=1 )
{
    normal Nf;

    Nf = faceforward(normalize(N),I);

    Oi = Os;
    _lightingstart();
    Ci = Os * Cs * ( Ka*ambient() + Kd*diffuse(Nf) ) ;
}

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

Irradiance maps

Irradiance maps are sparse volumetric files containing samples representing an irradiance (incoming illumination) function. Irradiance maps are superseded by the new and more general point cloud files, and is no longer supported.

float irradiancecache( string operation, string handle, string filemode, point P, normal N, ... )

This function is no longer supported.

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 behaviour could be defeated by the use of "P = P" prior to new calls to illuminance, or by using illuminance("category", P+vector(0), ...). PRMan 12.5 now allows explicit light-cache control via the illuminance parameter "lightcache". The new 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.

gridpattern()

float gridpattern( string pattern, float context, ... )

Gridpattern returns a one or a zero for each shading point, according to the specified pattern and context. Like other varying functions, gridpattern returns a result which may be different at each shading point on the underlying grid; however, unlike most other functions, the output is intentionally a function of the shading grid's structure. It is typically used to produce a “mask” using one of the built-in pattern generators, the mask is then used in a varying conditional to apply specific operations to the “on” subset of points. Furthermore, some patterns can be applied iteratively, in conjunction with a preserved context, and will become successively more “refined” in terms of the sets of points selected, until all of the points on the grid have been visited.

The available patterns are:
"gridpoint" one shading point on at a time convergent
"binaryrefinement" sparse-to-dense sampling convergent
"concentric" “rings” around the periphery of the grid ¹ convergent
"checkerboard" alternating on and off points ¹ non-convergent
"random" a random distribution of on and off points non-convergent
¹ some patterns have undefined results on non-rectangular internal grid types

Note: The internal structure and content of shading grids are inherently implementation dependent, and they are strongly affected by the input geometry types, shading rate, camera settings, and numerous other tuning parameters. There are no guarantees about the specific behavior or reproducibility of patterns.

The context variable is used to carry state between iterative calls to gridpattern; the meaning and usage of context depends on pattern. Usually context should be initialized to zero prior to first use. Each pattern will modify context differently and it should generally be assumed to be private data owned by gridpattern. Some patterns guarantee convergence in the sense that after a series of calls, all grid points will have been "visited" and further calls will return zero for all points.

The shading language supports several familiar conditional constructs which cause operations to occur on grid subsets; consider:

   	if (s < 0.5) k += 1;
The varying variable k will only be incremented at the points which satisfy the condition. Similarly, gridpattern produces a varying float “mask” which is 1 for some points, 0 for others. This mask might be used directly (as a float) or used in further conditional expressions.

For example, for a shader executing on a tiny 5x5 grid:

   	varying float patctx = 0;  /* initialize the context */
   	varying float f = gridpattern("concentric", patctx);
the variable f will now contain the following pattern:
   1 1 1 1 1
   1 0 0 0 1
   1 0 0 0 1
   1 0 0 0 1
   1 1 1 1 1
the patctx variable allows gridpattern to track a particular context through repeated calls, so a subsequent call to:
   	varying float g = gridpattern("concentric", patctx);
would produce, in g,   and f+g would be
   0 0 0 0 0
   0 1 1 1 0
   0 1 0 1 0
   0 1 1 1 0
   0 0 0 0 0
 
   1 1 1 1 1
   1 1 1 1 1
   1 1 0 1 1
   1 1 1 1 1
   1 1 1 1 1
After another iteration (in which only the center spot would be one), the pattern will have “converged,” meaning that all of the gridpoints will have been visited and subsequent calls to gridpattern (with the same context variable) will return all zeros.

Another pattern type, named "binaryrefinement", tries to select a few widely spaced points for the first pass, and then more points which are less widely spaced for the second pass, etc., until all points have been visited.

Note:   while "any" (relation)

The while loop usually has the desirable behavior that only shading points which satisfy the specified condition continue to be evaluated within the loop. When all points finally fail the condition, or have had break applied to them, then the loop is complete.

There are a few rare cases, such as with gridpattern, where it is instead desirable for every point which was active when the loop started to remain potentially active if any shading point passes the conditional. In these situations, the addition of the "any" modifier string after while causes the success/fail state at every shading point to be reset (rather than preserved) each time through the loop.

The while "any" construct allows looping on gridpattern calls until a convergence occurs. In this mode a different set of gridpoints will be active during each iteration. So a typical convergence loop might look like this:

   	while "any" (0 != gridpattern("binaryrefinement", patctx)) {
    	    /* do things on this subset */
   	}

See the gridpattern example for an illustration.

 

Limitations

The Shading Language compiler and interpreter in PhotoRealistic RenderMan has certain limitations which must be taken into consideration when shaders are written for PhotoRealistic RenderMan. If these are not understood, shaders which 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.

PhotoRealistic RenderMan 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, 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.

 

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