Language Constructs

Expressions
Standard Control Flow Constructs  
Gather, Illuminance and Illuminate Statements


Expressions

Expressions are built from arithmetic operators, function calls and variables. The language supports the common arithmetic operators, (+, -, *, and /) plus the vector operators ^ (cross product) and . (dot product), and the C conditional expression (binary relation ? expr1 : expr2).

When operating on points, vectors, normals or colors an arithmetic operation is performed in parallel on each component. If a binary operator involves a point or color and a float, the float is promoted to the appropriate type by duplicating its value into each component. It is illegal to perform a binary operation between a point and a color. Cross products only apply to vectors; dot products apply to both vectors and colors. Two points, two colors, or two strings can be compared using == and !=. Points cannot be compared to colors.

The usual common-sense mathematical rules apply to point/vector/normal arithmetic.  For example, a vector added to a point yields a point, a vector added to a vector yields a vector, and a point subtracted from a point yields a vector.  Mixing the types of vectors, normals and points (for example, taking across product of two points, rather than two vectors) is allowed, but discouraged.  A particular implementation may choose to issue a compiler warning in such cases.  Note that vectors and normals may be used nearly interchangeably in arithmetic expressions, but care should be taken to distinguish between them when performing coordinate system transformations.

Matrix variables can be tested for equality and inequality with the == and != boolean operators.  The * operator between matrices denotes matrix multiplication, while m1/m2 denotes multiplying m1 by the inverse of matrix m2.  Thus, a matrix can be inverted by writing 1/m;

Standard Control Flow Constructs

The basic explicit control flow constructs are:

These constructs are all modeled after C. Statement grouping allows a single statement to be replaced with a sequence of statements.

{
	stmt;
	...
	stmt;
}

Any variables declared within such a brace-delimited series of statements are only visible with that block.  In other words, variables follow the same local scoping rules as in the C language.

Conditional execution is controlled by

      if ( boolean expression ) stmt else stmt

There are two loop statements,

     while ( boolean expression ) stmt

and

     for ( expr ; boolean expression ; expr ) stmt

A boolean expression is an expression involving a relational operator, one of: <, >, <=, >=, ==, and !=. It is not legal to use an arbitrary float, point or color expression directly as a boolean expression to control execution. A boolean expression can not be used as a floating point expression.

The

     break [n]

and

     continue [n]

statements cause either the for or the while loop at level n to be exited or to begin the next iteration. The default value for n is 1 and refers to the immediately enclosing loop.

Built-in and user-programmed functions are called just as in C. The

     return expr

statement is used to return a value to the caller. Any functions that do not return a value should be declared as void, just as in ANSI C; they may contain return statements with no arguments.

Return statements may now appear anywhere in a function, rather than being restricted to the end of the function. For example:

   float positive(float x) { 
       if (x > 0) 
           return 1; 
       else 
           return 0; 
   }

Uniform values returned from varying conditionals are promoted to varying. For example, the function above returns a varying value if x is varying.

Gather, Illuminance and Illuminate Statements

The Shading Language contains five block statement constructs: gather, illuminance, illuminate, solar and ambience.  gather is used to control the ray-traced sampling of incoming illumination and other forms of information. illuminance is used to control the integration of incoming light over a hemisphere centered on a surface in a surface shader. illuminate and solar are used to specify the directional properties of light sources in light shaders. If a light source does not have an illuminate or solar statement, it is a non-directional ambient light source.  ambience can be used to explicitly specify a contribution to the ambient component. 

Unlike other control statements, illuminance, illuminate, and solar statements cannot be nested. However, multiple illuminance, illuminate, or solar statements may be given sequentially within a single shader.

For more information on shading and lighting functions, see the Built-in Functions documentation.


The gather looping construct collects information via ray tracing.  It typically controls integration of a procedural reflectance model over incoming light whose source is other surfaces; that is, it collects indirect light.  Direct light from light sources is collected with illuminance loops.

  gather ( string category, point P, vector dir, float angle, float samples, ... )
       {statements} [else {statements}]
Rays are cast within the cone specified by P, dir and angle.  The angle, in radians, is measured from dir, the central axis of the cone; an angle of zero implies that rays should only be cast in direction dir, whereas PI/2 produces sampling over a hemispherical area.  Within this region, samples rays are stochastically distributed, so increasing the cone angle increases the blurriness; increasing the number of samples increases the quality.  When a ray intersection occurs, the optional output variables are updated with values obtained from the hit surface.  The loop statements are executed repeatedly, once for each sample pass, similar to while, for, or illuminance loops.  In each pass, results from the current ray cast are available to the statement code, which typically accumulates values from all sample passes; a weighted average is then often computed outside the gather loop.  Code in the first statement block will be executed when a sample ray hit occurs; code in the else block will be executed when no ray intersection is found.  For example:
  float samples = 16;      /* often from a shader parameter */
  float blur = radians(5); /* often from a shader parameter */
  Ci = 0;
  if (N.I < 0) { /* only trace from points facing the eye */
     vector R = reflect(normalize(I),normalize(N));
     color hitcolor = 0;
     gather("illuminance", P, R, blur, samples, "volume:Ci", hitcolor) {
        Ci += hitcolor;
     } else {
        Ci += environment("skymap.env", R);
     }
     Ci = Ci / samples;
  }
  Ci *= Os;
  Oi = Os;

Arbitrary data associated with ray hits can be collected, see msg_spec below.  Since many gathered values will be unrelated to indirect illumination, the required category string (first argument to gather) allows the renderer to distinguish certain important types of gathering.  There are, currently, two special categories: illuminance and samplepattern. "illuminance", is recognized as distinguishing illuminance-gathering blocks from other information-gathering uses.  In particular, during photon map creation, shader analysis will select only those gather calls categorized as "illuminance" to participate in photon scattering.  "samplepattern" causes gather() to generate correctly stratified and distributed rays within the sample cone described by the subsequent arguments, but gather itself will not actually shoot any rays. Instead, the gather() "else" block is executed once per sample, after filling in any requests for "ray:direction", which can be useful for shaders that want to do other types of direction-based lookups but don't want to generate their own sample distributions.

Unlike illuminance blocks, in which L and Cl are preset to each light's direction and color, there are no additional predefined variables provided by gather to its code blocks.  Instead, shader writers supply their own local variables to gather which are filled in with values from the hit surface as specified with an output msg_spec.  These local variables can then be referenced within the gather code block. This general interface without reserved names prevents conflicts when gather loops are nested or called within lightsource shaders (where illuminance is illegal). 

Note that there can be two code blocks associated with a gather call, the first block is execute when a ray hits something; the optional else block executes when a ray misses everything.  Output msg_spec variables have undefined values in the "miss" case, and therefore should not be referenced in the 'else' block.  Output variables from the "ray:" namespace are an exception, they are valid in both hit and miss cases.  Even when a ray-hit occurs it is possible that the value requested for a particular msg_spec is not available from the hit surface; this is especially true for user-defined "message passing" shader output variables, which might not be supplied by every hit surface shader.  In this case the local output variable will not be updated by gather, so it is up to the shader writer to re-initialize loop variables whose spec doesn't guarantee success. 

Automatic continuation of rays occurs when the hit surface is not opaque, as determined by the output Oi from the shader(s) on the surface. Rays will continue in their original direction, collecting values from hit surfaces, until the opacity threshold is reached (see othreshold below).

Optional parameters to gather take the form of token-value pairs and include:

Gather Input Parameters
"bias", 0.01 a bias value for ray origin, to prevent self-intersection
"maxdist", RI_INFINITY limits the distance from P in which ray hits can occur
"samplebase", 1.0 scales the ray origin jitter area, which is the size of the origin micropolygon by default
"distribution",
  • "uniform"
  • "cosine"
  • array of ray directions
a hint to the stochastic sample generator, controls the ray density pattern within the sample cone; when using non-uniform sampling, directional weighting factors may be unnecessary in the gather user code blocks. If an array of ray directions is given, those will be used directly (no ray directions are generated).
"hitsides" hitsides (uniform string) Specifies which side(s) of one-sided surfaces can be hit by the rays. The possible values are "front", "back", and "both". The default is "both".
"subset", "string" A trace set to consider for ray intersections. Only objects which are members of the named groups will be visible to the traced rays. Sets are defined with Attribute "grouping" "membership"
"othreshold", color an opacity threshold which determines termination criteria for automatic continuation of rays through semi-transparent surfaces. Using color(0) effectively disables automatic continuation for the given gather call. The implementation sets the default opacity threshold, typically color(1-epsilon).
"ohitthreshold", color an opacity threshold which determines when a ray is considered to have "hit" anything or not, for the purposes of choosing which of the "hit" or "miss" code blocks to execute. When it is desirable to use shading (or texture maps) to cause parts of objects to be "invisible" to rays, the shaders on those objects should set Ci and Oi to zero, for those parts. Gather rays which probe those objects will ignore them when the returned Ci and Oi are less than the "ohitthreshold" value. Using color(0) for this parameter causes shading at intersection points to be ignored for hit-testing purposes. The implementation sets the default threshold, typically color(epsilon).
"label", "string" labels can be queried in shaders executed as the result of a ray hit
Note: some implementations may provide an additional message passing mechanism for sending arbitrary variable values with the ray, to be used as parameters in shaders on hit objects.
 
Gather Output Parameters
gather(..., "s", v, ...)
   "s" -  descriptor (listed below)
    v  -  local_variable
A single gather call can collect multiple output values, these are values fetched from the surfaces hit by gather rays.  Each output is specified with a descriptor-variable pair of parameters. The descriptor specifies the value to be fetched, and the local variable name is where that value should be stored.  The local variable type and detail must match the requested value!
[Note: currently, the requested value's type cannot be string, since strings are uniform, and gather must return varying results since rays from each grid point may hit a different object.]
"shadertype:varname" retrieves the value of a graphics state variable (Ci, N, P, etc), or user-specified shader output "message passing" parameter, from the shader context on the hit primitive.  The typical reflection or refraction rays should fetch "volume:Ci".  The value of the named remote variable is taken from the shader context after the specified type of shader has executed.  Supported shader types are volume, surface, and displacement.  So in the volume:Ci case, the shader output color, Ci, is retrieved after any displacement, surface, and volume shaders associated with the hit surface have executed. Note that Atmosphere shaders are defined for "camera rays" only and therefore are not executed in the gather "secondary ray" context. However, Interior and Exterior shaders are executed in ray traced situations.
"primitive:varname" retrieves the value of varname on the hit surface prior to shader execution, for example "primitive:P" would be the point of ray-surface intersection.  Outputs such as Ci which require shader execution can not be retrieved using this mode since they are only valid after shading.
"attribute:name:value" retrieves an attribute from the state bound to the hit primitive, for example:
    float hitproperty = 0;
    gather(..., "attribute:user:myproperty", hitproperty, ...) {
          ... use hitproperty ...
    }
"ray:element"
  • "ray:origin" (point)
  • "ray:direction" (vector)
  • "ray:length" (float)
retrieves details of the particular ray associated with the current sample pass.  Note that the actual ray origin and direction returned here will be different from the nominal (P, dir) due to jittering and stochastic sampling of the cone region. Ray length will be large (maxdist) when no hits occur, or when hits do not reach the opacity threshold before the ray leaves the scene.
In situations where values are fetched using only   primitive:var, attribute:var,   or   ray:var   descriptors, some implementations may opt to skip execution of the shaders on the hit surface entirely, as an optimization.  In these cases, automatic ray continuation may not occur, since shader execution is required to determine the surface opacity, Oi.

 


The illuminance statement controls integration of a procedural reflectance model over the incoming light. Inside the illuminance block two additional variables are defined: Cl or light color, and L or light direction. The vector L points towards the light source, but may not be normalized (see Figure 12.2). The arguments to the illuminance statement specify a three-dimensional solid cone and, optionally, the effective number of samples or step size of the integral. The two forms of an illuminance statement are:

     illuminance([string category], point position )
        statement
     illuminance([string category], point position, vector axis, float angle ) 
        statement

The first form specifies that the integral extends over the entire sphere centered at position. The second form integrates over a cone whose apex is on the surface at position. This cone is specified by giving its centerline, and the angle between the side and the axis in radians. If angle is PI, the cone extends to cover the entire sphere and these forms are the same as the first form. If angle is PI/2, the cone subtends a hemisphere with the north pole in the direction axis. Finally, if angle is 0, the cone reduces to an infinitesimally thin ray.

The optional category string parameter selects which lights should execute during a particular illuminance loop. The illuminance category string is a simple pattern expression which is matched against each light's declared list of category "memberships." Light shaders can specify categories to which they belong by declaring a string parameter named __category (this name has two underscores), whose value is a comma-separated list of names.  These categories are simply arbitrary names, chosen by the programmer to be useful among a set of cooperating shaders.  The illuminance category parameter expressions are very limited. They consist of category "terms" separated by "&" or "|" (for AND and OR). The "terms" can be:

 
"name"   matches lights which include "name" in their category list
"-name"   matches lights which do not have "name" in their category list (including lights which have no category list)
"*"   matches every light with non-null category
"-*"   matches every light with no categories
""   matches everything
"-"   matches nothing
omitted if the category parameter is omitted, the illuminance loop will execute its body for every nonambient light source.

A Lambertian shading model is expressed simply using the illuminance statement:

     Nn = normalize(N);
     illuminance( P, Nn, PI/2 )
     {
         Ln = normalize(L);
         Ci += Cs * Cl * Ln.Nn;
     }

This example integrates over a hemisphere centered at the point on the surface with its north pole in the direction of the normal. Since the integral extends only over the upper hemisphere, it is not necessary to use the customary max(0,Ln.Nn) to exclude lights that are locally occluded by the surface element.


The illuminate and solar statements are inverses of the illuminance statement. They control the casting of light in different directions. The vector variable L corresponding to a particular light direction is available inside this block. This vector points outward from the light source. The color variable Cl corresponds to the color in this direction and should be set. Like the illuminance statements, illuminate and solar statements cannot be nested.

The illuminate statement is used to specify light cast by local light sources. The arguments to the illuminate statement specify a three-dimensional solid cone. The general forms are:

     illuminate( point position ) stmt
     illuminate( point position, vector axis, float angle ) stmt

The first form specifies that light is cast in all directions. The second form specifies that light is cast only inside the given cone. The length of L inside an illuminate statement is equal to the distance between the light source and the surface currently being shaded.

The solar statement is used to specify light cast by distant light sources. The arguments to the solar statement specify a three-dimensional cone. Light is cast from distant directions inside this cone. Since this cone specifies only directions, its apex need not be given. The general forms of the solar statement are:

     solar( ) stmt
     solar( vector axis, float angle ) stmt

The first form specifies that light is being cast from all points at infinity (e.g., an illumination map). The second form specifies that light is being cast from only directions inside a cone.

An example of the solar statement is the specification of a distant light source:

     solar( D, 0 )
         Cl = intensity * lightcolor;

This defines a light source at infinity that sends light in the direction D. Since the angle of the cone is 0, all rays from this light are parallel.

An example of the illuminate statement is the specification of a standard point light source:

     illuminate( P )
         Cl = (intensity * lightcolor) / (L.L)

This defines a light source at position P that casts light in all directions. The 1/L.L term indicates an inverse square law fall off.


ambience()  statement

Lights are classified at compile-time whether they will contain ambient contributions or not, thus permitting the renderer to refrain from executing certain lights when only the ambient contribution is being computed. The criteria is simple. If the light contains "solar" or "illuminate" blocks, it is considered non-ambient, and will not be forced to execute by the ambient() surface shader shadeop. If the light contains neither op, it is considered ambient and is not run by diffuse(), specular(), or illuminance().

This behavior can be overridden by using an ambience() block. Any light shader which contains an ambience() block specifies a "run-it-and-check" behavior. ambience() is a simple block structure like solar(), but has no parameters. It automatically sets L to 0, so it is not necessary (nor advisable) to tweak L inside the block. For example:

if (goofytest != 0) { 
    illuminate(from, axis, coneangle) { 
        Cl = lightcolor * atten; 
    } 
} else { 
    ambience() { 
        Cl = 0.5; 
    } 
}

However, it continues to be useful to set L = 0 in the case where you wish to dynamically "turn off" a non-ambient light.


No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without the prior written permission of Pixar. The information in this publication is furnished for informational use only, is subject to change without notice and should not be construed as a commitment by Pixar. Pixar assumes no responsibility or liability for any errors or inaccuracies that may appear in this publication.

 

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