Since the publication of the RenderMan Interface Specification 3.1, many features have been added to the shading language by the various compliant implementations.  This document describes the shading language extensions made by RenderDotC, organized into the following chapters:

### New Variable Types: vector, normal, and matrix

Triples

The 3.1 specification of the RenderMan Interface defined only one type of geometric triple: point.  In affine geometry, points and vectors behave differently.  For example, points may be translated but vectors are immune to translation.

In addition to point, RenderDotC defines vector and normal types.  All three have x, y, and z components.  They only behave differently when being automatically transformed from one coordinate system to another. Vectors transform the same was as points except translation is ignored. Normals are transformed such that they remain perpendicular to a plane undergoing the same transformation.  More specifically, normals are mutiplied by the inverse transpose of the transformation matrix.

Shader parameters may be declared point, vector or normal using RiDeclare. When triples are passed to the shader, they are automatically transformed to the current space.  The details of this transformation depend upon the type.

In addition, triples may be implicitly transformed from within the shader. For example:

vector up = vector "shader" (0, 1, 0);
This projects the vector (0, 1, 0) from "shader" space to "current" space.

To explicitly transform a point, vector, or normal from one space to another, use the transform, vtransform, or ntransform function, respectively.  The vtransform and ntransform functions are shading language extensions documented below.

Matrices

The matrix data type represents a 4x4 array of floating point numbers.  Such matrices are often called "homogeneous" or "affine" transformations because multiplying a 3D point or vector by a 4x4 matrix performs an affine transformation (the fourth row is necessary for translating points).  This is the same type of matrix that RenderDotC uses internally to implement coordinate systems, transform(), etc.

In RIB, a matrix is declared like this:

Varying and vertex matrices are also allowed.  Matrices may be passed from RIB to shaders in the token-value lists of shaders or primitives:
Surface "printall" "mshader" [1 0 0 0 0 1 0 0 0 0 1 0 10 20 30 1]
Points "P" [0 0 0] "mobject" [1 0 0 0 0 1 0 0 0 0 1 0 10 20 30 1]
Matrices passed to shaders are automatically projected from "shader" to "current" space, while matrices passed to primitives undergo the "object" to "current" transformation.

Within the shading language, matrices may be explicitly initialized with a list of 16 values:

uniform matrix mscalexy = (2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
This syntax is similar to the way points are initialized.  Like points, matrix initializers take an optional coordinate system:
uniform matrix mscalexy = matrix "shader" (2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
The latter syntax prepends the "current" to "space" transformation (where "space" is "shader" in the example).

Casting from a float to a matrix results in a matrix with all zeros except on the diagonal, where all four values equal the original float.  This is often used to create the identity matrix.  The following two lines are equivalent:

uniform matrix identity = 1;
uniform matrix identity = (1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1);
The equality and inequality operators are defined on matrices:
if (m1 == m2) ...
if (m1 != m2) ...
These operators compare the matrices component-by-component.

Matrices may be multiplied or divided:

m3 = m1 * m2;
m3 = m1 / m2;
Dividing by a matrix is equivalent to multiplying the left hand matrix by the inverse of the right hand matrix.  If the right hand matrix is uninvertible, and error message will be printed and the result of the division expression will be the left hand matrix.

The following builtin functions take matrix arguments:

float determinant(matrix m)
matrix inverse(matrix m)
point transform(matrix m, point p)
vector vtransform(matrix m, vector v)
normal ntransform(matrix m, normal n)
point transform(string space, matrix m, point p)
vector vtransform(string space, matrix m, vector n)
normal ntransform(string space, matrix m, normal n)
matrix translate(matrix m, point t)
matrix rotate(matrix m, float angle, vector axis)
matrix scale(matrix m, point s)
float comp(matrix m; float row, column)
void setcomp(matrix m; float row, column; float newvalue)
printf("%m", m1)
They are described in detail below.

### Arrays

RenderDotC's shader compiler now allows arrays of all the basic data types.  Arrays must be one dimensional and of fixed, compile-time lengths greater than 0.  No array overrun checking is performed at run-time.  Array subscripts are floating point numbers (rounded down if necessary) specified in square brackets as in C.  Array initializers are constants in curly braces:

uniform string months[12] = {"january", "february", "march", ...};

An array element may be used in any exression where a single variable of the same base type is allowed.  If an element of an array is used as an actual parameter to a function which returns a value in that parameter, this sets that element to the return value.  In other words, array elements are call-by-reference, just like other function arguments in RenderMan shading language.

If an array of strings is used as the first argument to texture(), then it's ambiguous whether square brackets indicate an array subscript or a channel number.  If two sets of square brackets are present, the first is the array subscript and the second is the channel number.  If only one set of square brackets appear, then they are interpreted as an array subscript.  The channel number will default to 0 in this case.

The spline function has been extended to allow control vertices to be passed as arrays.

### Texture, Environment and Shadow Tokens

The texture(), environment() and shadow() functions take an arbitrary list of token-value pairs.  This provides a backdoor to control implementation-specific behavior.  The following table summarizes the implementation-specific tokens recognized by RenderDotC's texture(), environment() and shadow() functions:

 SL: Defaults texture(mapname, "width", x) 1.0 texture(mapname, "fill", x) 0.0 texture(mapname, "lerp", b) 1.0 texture(mapname, "filter", "s") "box" texture(mapname, "blur", x) 0.0 texture(mapname, "sblur", x) 0.0 texture(mapname, "tblur", x) 0.0 shadow(mapname, "bias", x) (bias0 + bias1) / 2 shadow(map1,map2,map3) - none - shadow(mapname, "source", p1, p2, p3, p4) P shadow(mapname, "gapbias", x) (bias0 + bias1) * 5

#### Width

This token simply sets "swidth" and "twidth" (documented in the spec) to the same value.  The actual filterwidths are then (swidth * ds + sblur) and (twidth * dt + tblur) where ds and dt are the actual dimensions of the area being textured in texture coordinates.

#### Fill

When a texture access is made to a channel number that isn't present in the map file, the fill value is used as a placeholder.  A good choice is usually 0 but in the case of an alpha channel, 1 is more appropriate.  For example:

C = texture("map.tex"[0], "fill", 0);
A = texture("map.tex"[3], "fill", 1);

If the texture map has an alpha channel it will be used.  Otherwise, it will default to opaque (1).

The "fill" token is not recognized by the shadow() function.

#### Lerp

Trilinear mip-mapping involves sampling 8 texels for each texture access to arrive at a filtered, interpolated value.  This results in high quality texture mapping and is especially important in animations where the textured object changes in scale.  A slightly faster algorithm is bilinear mip-mapping, which only samples 4 texels per access.  The "lerp" flag determines which algorithm is used.  The value 0.0 turns off trilinear mip-mapping, and any non-zero value enables it.

Note that this flag may be overridden by the enable lerp option.

The "lerp" token is not recognized by the shadow() function.

#### Filter

Each set of 4 texels sampled by the mip-mapping algorithm is further filtered by interpolating to the center of the quad being textured.  The "filter" token chooses the interpolator.  The possibilities are "box", "triangle", "gaussian" and "catmull-rom".  The "box" interpolator is simply linear interpolation.  The others are more sophisticated and may produce higher quality images.

Note that this flag may be overridden by the enable gaussian option.

The shadow() function honors only the "box" and "gaussian" filters.

#### Blur

The standard "swidth" and "twidth" tokens cause the natural filterwidth to be multiplied by a constant amount.  For finer control, "sblur" and "tblur" can be used to add an amount to the filter width.  The final filterwidths are:

totalds = swidth * ds + sblur
totaldt = twidth * dt + tblur

where ds and dt are the actual dimensions of the area being textured in texture coordinates.  Values for sblur and tblur are usually small, such as 0.001, because 1.0 covers the entire width of the texture.

The "blur" token may be used to conveniently set "sblur" and "tblur" to the same value.

#### Bias

The shadow bias option may be overidden in the call to shadow() by explicit use of the "bias" token.

The "bias" token is only honored by the shadow() function.

#### Multiple maps

The first argument to shadow() may be a comma-delimited list of filenames instead of a single filename.  Each file is a shadow map rendered from a different view.  This allows RenderDotC to approximate true soft shadows as cast from area lights.

#### Source

The "source" parameter takes one, two, three, or four points in current space.  It defines an area light as a point, line, triangle, or quadrilateral, respectively.  Soft shadows will be created by sampling this area.  For best results, multiple shadow maps should be rendered from points on this area and all passed to shadow().

#### Gap bias

A shadow map is essentially a height field taken from a particular perspective.  Rays traced from the origin of that perspective intersect the height field unambiguously.  However, rays traced from different angles may pass between a high and a low value in the height field.  If the high and low values were caused by the same object, then the ray intersects that object.  However, if the values are from two different objects, the ray may be passing between them.  Shadow maps do not contain the necessary information to determine if two consecutive pixels are the result of the same object.  There's no "topology" information.

Consecutive pixels in a shadow map whose values differ by gapbias or less are inferred to be generated by the same object.  For more information, see the tutorial on creating soft shadows.

### Built-in Functions:

This section describes built-in functions which are new or behave differently than as described in the RenderMan Interface Specification 3.1.

#### vector and normal transforms

vector vtransform(string tospace; vector v)
vector vtransform(string fromspace, tospace; vector v)
normal ntransform(string tospace; normal n)
normal ntransform(string fromspace, tospace; normal n)
These are corrolaries to the original transform() functions.  Where transform() works correctly on points, vtransform is for vectors and ntransform is for normals.  Vectors and normals transform differently than points.  Care should be taken to choose the form that is correct for the application.

If fromspace is not specified, "current" is the default.

point transform(matrix m, point p)
vector vtransform(matrix m, vector v)
normal ntransform(matrix m, normal n)
These explicitly multiply a point, vector or normal by a matrix.  Again, vectors and normals transform differently than points.
point transform(string space, matrix m, point p)
vector vtransform(string space, matrix m, vector v)
normal ntransform(string space, matrix m, normal n)
This form first transforms the point, vector, or normal from "space" to "current" and then multiply by the matrix.

#### distance from point to a line

float ptlined(point P0, point P1, point Q)
Returns the euclidean distance from point Q to the nearest point on the line segment from P0 to P1.  Note that if the point nearest Q on the infinite line through P0 and P1 does not lie between P0 and P1, it will be clamped to that range before the distance is taken.  In other words, ptlined() does not return the distance between Q and an infinite line.

#### cell noise

float cellnoise(float x)
float cellnoise(float s, float t)
float cellnoise(point pt)
float cellnoise(point pt, float t)
point cellnoise(float x)
point cellnoise(float s, float t)
point cellnoise(point pt)
point cellnoise(point pt, float t)
color cellnoise(float x)
color cellnoise(float s, float t)
color cellnoise(point pt)
color cellnoise(point pt, float t)
Returns a pseudorandom value that depends upon the floor of all its arguments.  The random values are uniformly distributed over the range from 0 to 1.  The results are repeatable: given the same input (or input similar enough that the floor of all arguments is identical) cellnoise will return the same value.

#### min and max of many values

float min(float a, b, ...)
point min(point a, b, ...)
point min(vector a, b, ...)
point min(normal a, b, ...)
color min(color a, b, ...)
float max(float a, b, ...)
point max(point a, b, ...)
point max(vector a, b, ...)
point max(normal a, b, ...)
color max(color a, b, ...)
The min and max functions can now take two or more arguments.  The min/max of all arguments is returned.

#### clamp and mix of all types

float clamp(float a, min, max)
point clamp(point a, min, max)
point clamp(vector a, min, max)
point clamp(normal a, min, max)
color clamp(color a, min, max)
float mix(float fg, bg; float value)
point mix(point fg, bg; float value)
point mix(vector fg, bg; float value)
point mix(normal fg, bg; float value)
color mix(color fg, bg; float value)
The clamp and mix functions now operate on all of the scalar data types. Those with more than one component are computed separately on each component.

#### four dimensional noise

float noise(point pt, float t)
color noise(point pt, float t)
point noise(point pt, float t)
Four dimensional noise is now supported.  The return value is repeatable and depends on all four input values.  A common application is to use time as the fourth dimension.

#### periodic noise

float pnoise(float v, uniform float period)
float pnoise(float u, float v, uniform float uperiod, uniform float vperiod)
float pnoise(point pt, uniform point period)
float pnoise(point pt, float t, uniform point pperiod, uniform float tperiod)
point pnoise(float v, uniform float period)
point pnoise(float u, float v, uniform float uperiod, uniform float vperiod)
point pnoise(point pt, uniform point period)
point pnoise(point pt, float t, uniform point pperiod, uniform float tperiod)
color pnoise(float v, uniform float period)
color pnoise(float u, float v, uniform float uperiod, uniform float vperiod)
color pnoise(point pt, uniform point period)
color pnoise(point pt, float t, uniform point pperiod, uniform float tperiod)
Periodic noise behaves the same as regular noise but with the additional property that the return value repeats itself with the frequency given by the period argument.  In other words, pnoise(v, period) == pnoise(v + period, period) For best results, all periods should be specified with integers.

#### reciprocol of square root

float inversesqrt(float x)
This returns 1.0 / sqrt(x), for people who would rather type "inverse" than "1/".

#### string concatenation

string concat(string a, b, ...)
Concatenates two or more strings and returns one long string.

#### formatted strings

string format(string pattern; ...)
Much like sprintf(3S), pattern is a format control string that may contain conversion specifiers such as "%f".  Arguments which follow are converted and formatted under control of the string.  The resulting string is returned.

regular expression matching

float match(string pattern, subject)
Returns 1.0 if the regular expression specified by pattern is found anywhere in the subject string, and returns 0.0 otherwise.  The format of the regular expression is defined as follows:
1. A character matches itself, unless it is a special character (metachar): . \ [ ] * + ^ \$
2. A period "." matches any character.
3. A backslash "\" matches the character following it, except when followed by a left or right round bracket, a digit 1 to 9 or a left or right angle bracket (see [7], [8] and [9]). It is used as an escape character for all other meta-characters, and itself. When used in a set ([4]), it is treated as an ordinary character.
4. A set of characters enclosed in square brackets "[" and "]" matches one of the characters in the set. If the first character in the set is "^", it matches a character NOT in the set, i.e. complements the set. A shorthand S-E is used to specify a set of characters S upto E, inclusive. The special characters "]" and "-" have no special meaning if they appear as the first chars in the set.  For example:
• [a-z] matches any lowercase alpha
• [^]-] matches any char except ] and -
• [^A-Z] matches any char except uppercase alpha
• [a-zA-Z] matches any alpha.
5. Any regular expression form [1] to [4], followed by an asterisk "*" matches zero or more matches of that form.
6. A plus sign "+" behaves the same as [5], except it matches one or more.
7. A regular expression in the form [1] to [10], enclosed by "\(" and "\)" matches the regular expression but also creates a set of tags, used for [8] and for pattern substution. The tagged forms are numbered starting from 1.
8. A backslash "\" followed by a digit 1 to 9 matches whatever a previously tagged regular expression ([7]) matched.
9. A regular expression starting with a "\<" construct and/or ending with a "\>" construct, restricts the pattern matching to the beginning of a word, and/or the end of a word. A word is defined to be a character string beginning and/or ending with the characters A-Z a-z 0-9 and _. It must also be preceded and/or followed by any character outside those mentioned.
10. A composite regular expression xy where x and y are in the form [1] to [10] matches the longest match of x followed by a match for y.
11. A regular expression starting with a caret "^" character and/or ending with a dollar "\$" character, restricts the pattern matching to the beginning of the line, or the end of line. Elsewhere in the pattern, ^ and \$ are treated as ordinary characters.

#### color transforms

color ctransform(string fromspace, tospace; color c)
color ctransform(string tospace; color c)
These functions convert colors from one space to another.  Both fromspace and tospace may be any of the following: "rgb", "hsv", "hsl", "XYZ", "xyz", "xyY", or "YIQ".  In the second form, fromspace is left unspecified and defaults to "rgb".

#### rotate point

point rotate(point q; float angle; point p0, p1)
Returns point q after being rotated by angle radians (note: not degrees) about the axis that passes from p0 through p1.  The left-hand rule is followed, assuming the axis is in the direction from p0 towards p1.

#### determinant

float determinant(matrix m)
Returns the determinant of matrix m.

#### inverse

matrix inverse(matrix m)
Inverts a matrix.  This is equivalent to (1 / m).  If the matrix is singular (i.e. uninvertible), an error message will be printed and the function will return the identity matrix.

#### translate, rotate, and scale matrices

matrix translate(matrix m, point t)
matrix rotate(matrix m, float angle, vector axis)
matrix scale(matrix m, point s)
These functions post-multiply m by a translation, rotation, or scale matrix, respectively.  Note that the rotation angle is in radians.

#### matrix components

float comp(matrix m; float row, column)
void setcomp(matrix m; float row, column; float newvalue)
These functions get and set individual components of matrices.

#### printing matrices

printf("%m", m1)
The printf function has been augmented to handle matrices.  Use the "%m" conversion specifier.

#### filterstep

float filterstep(float edge, s1; ...)
float filterstep(float edge, s1, s2; ...)
Much like step(float edge, s1) except filtered so that there is a smooth transition from 0 to 1.  In both forms, a filter kernel is centered at s1.  filterstep() returns the fraction of the area under the curve that lies to the right of edge.  In the case of a catmull-rom filter, the return value may lie slightly out of the range from 0 to 1 due to the filter shape.

In the first form, the range is automatically computed as abs(Du(s1)*du) + abs(Dv(s1)*dv), resulting in a smooth step over the size of one surface element.  The usual warning about not using "area operators" inside of varying conditionals apply to the first form of filterstep.  In the second form, the range equals abs(s2 - s1).  This form may be used inside varying conditionals.

The parameter list may contain token-value pairs.  To specify which filter kernel to use, include the token "filter" followed by "box", "triangle", "catmull-rom", or "gaussian".  For example, to select the Gaussian filter kernel:

filterstep(edge, s1, "filter", "gaussian");
The default filter is "catmull-rom".

To adjust the amount of overfilter, use the token "width" (or "swidth" synonymously) followed by a floating point number indicating a factor to multiply the filter width by.  For example, to double the filter size:

filterstep(edge, s1, "width", 2.0);
Each filter kernel has an inherent width of support.  Outside of that area of support, filterstep returns either 0 or 1.  For reference:
filterstep(x, 0, 1, "filter", "box") is defined over the range x = -0.5 to 0.5 (width 1).
filterstep(x, 0, 1, "filter", "triangle") is defined over the range x = -1 to 1 (width 2).
filterstep(x, 0, 1, "filter", "catmull-rom") is defined over the range x = -2 to 2 (width 4).
filterstep(x, 0, 1, "filter", "gaussian") is defined over the range x =-1.5 to 1.5 (width 3).
Increasing the difference between s0 and s1 or increasing the amout of overfilter with the "width" token will increase the area of support for all of the filter kernels.  In other words, the total support width of a filter is the product of its inherent width, the overfilter factor, and the range (either abs(Du(s1)*du) + abs(Dv(s1)*dv) or abs(s2 - s1)).

All of the filters used by filterstep() change their shape to match the width.  Contrast that with the pixel filters RiCatmullRomFilter() and RiSincFilter() which retain their shape but are merely windowed by xwidth and ywidth.

#### bidirectional reflectance distribution function (BRDF)

color specularbrdf(vector tolight, normal n, vector tocamera, float roughness)
The specularbrdf function allows users to write their own illuminance loop and use exactly the same BRDF that is currently in effect in RenderDotC (which is selectable with an Option.)  The arguments are a vector towards the light, the normal vector to the surface, a vector towards the camera, and the surface roughness.  A simple illuminance loop like the one in the specular() function might look like this:
color C = 0;
point Nn;
Nn = normalize(N);
V = normalize(V);
illuminance(P, Nn, PI/2)
C += Cl * specularbrdf(normalize(L), Nn, V, roughness);
Note that specularbrdf returns an attenuation that does not depend upon the color of the light.  All vectors passed to specularbrdf must be normalized first.

#### alternate forms of spline

float spline(string basis; float v, f1, f2, f3, f4, ...)
color spline(string basis; float v; color c1, c2, c2, c4, ...)
point spline(string basis; float v, point p1, p2, p3, p4, ...)
These functions evaluate a cubic spline over four or more control vertices at parametric point v.  Note that v varies from 0 to 1 over the entire length of the spline.  Values outside of the range 0 and 1 will be clamped.

The difference between this form and the standard one from the RenderMan spec is that the basis of the spline may be specified.  The old form is still supported with the implied basis "catmull-rom".  With the new form, the basis may be "catmull-rom", "bezier", "hermite", "linear", or "bspline" (note: not "b-spline").  The "linear" basis needs some further explanation.  The first and last control vertices are completely ignored.  Points on the spline are linearly interpolated between the nearest two control vertices.

float spline([string basis;] float v; float f[])
color spline([string basis;] float v, color c[])
point spline([string basis;] float v, point p[])
The control vertices may optionally be passed to spline() in a shading language array of four or more values.  If the basis is omitted, it defaults to "catmull-rom".

#### Accessing renderer state

The following functions allow shaders to query state variables internal to the renderer.  In all cases, the first argument is a case-sensitive string which is the name of a renderer variable, the second argument is a variable to contain the result, and the return value is non-zero upon success.
float attribute(string, variable)

 value of string variable type RenderMan Interface call "ShadingRate" uniform float ShadingRate "Matte" uniform float Matte "GeometricApproximation:motionfactor" uniform float GeometricApproximation "motionfactor" "Sides" uniform float Sides "displacementbound:sphere" uniform float Attribute "displacementbound" "sphere" "displacementbound:coordinatesystem" uniform string Attribute "displacementbound" "coordinatesystem" "identifier:name" uniform string Attribute "identifier" "name"
RenderMan attributes can vary from one primitive to the next.  The data returned by attribute() is within the context of the primitive being shaded.  Note that "displacementbound:coordinatesystem" always returns "camera", and that "displacementbound:sphere" returns the bound in camera coordinates.
float option(string, variable)

 value of string variable type RenderMan Interface call "Format" uniform float[3] RiFormat "DepthOfField" uniform float[3] RiDepthOfField "Shutter" uniform float[2] RiShutter "Clipping" uniform float[2] RiClipping

float textureinfo(filename, string, variable)

 value of string variable type description of data returned "resolution" uniform float[2] x and y dimensions of highest resolution subimage in texture map "type" uniform string "texture", "shadow" or "environment" "channels" uniform float number of channels in texture map (e.g. "rgb" is 3 channels) "viewingmatrix" uniform matrix world-to-light transformation matrix in a shadow map "projectionmatrix" uniform matrix world-to-NDC transformation matrix in a shadow map

float rendererinfo(string, variable)

 value of string variable type description of data returned "renderer" uniform string name of renderer "version" uniform float [4] the renderer version as four separate numbers "versionstring" uniform string version of the renderer as a string

### Message passing

Shaders, when working on the same piece of geometry, can now pass values to one another through the "message passing" facility.  This requires cooperation between the sending and receiving shaders.

#### Receiving messages

On the receiving end, the shader uses one of the following new built-in functions:

float displacement(string varname; {float|point|color|string} value)
float lightsource(string varname; {float|point|color|string} value)
float surface(string varname; {float|point|color|string} value)
float atmosphere(string varname; {float|point|color|string} value)

The name of the function matches the type of shader from which you wish to receive a message.  The first argument is a string containing the name of a variable that may be exported from the sending shader.  If the sending shader exists, and it has an output parameter varname, and it's the same type as value, then the value of the output parameter is stored in value and the function returns 1.  Otherwise, the function returns 0 and value is left unchanged.  If the parameter is uniform and value is varying, then it is promoted.  The lightsource() function may only be called within an illuminance loop.

It makes more sense to pass messages "downstream" to shaders that have not yet been evaluated.  For a given piece of geometry, the order of evaluation of shaders in RenderDotC is displacement, all of the lights, surface, and atmosphere.  Attempting to pass messages "upstream" will yield the default value from the parameter initializer.

#### Sending messages

To make a variable available for other shaders to pick up, make it a parameter of the shader and declare it as "output":

displacement waves(float ampl = 1;
output varying vector displaced = 0;)

As a convenience, all parameters whose names begin with a double underscore are automatically flagged as output.

#### nondiffuse and nonspecular lights

The built-in functions diffuse(), specular(), and phong() each attempt to receive a message from lightsources.  If a light source sends the message __nondiffuse and its value is non-zero, then it will be ignored by diffuse().  Similarly, lightsources passing non-zero __nonspecular are ignored by specular() and phong().  These parameters may be declared either as uniform or varying.

#### Light categories

Light source shaders may declare a special parameter called "__category" as follows:

lightsource blacklight(uniform string __category = "uv,black")

This string variable may contain one or more group names, separated by commas, indicating that this light is a member of each of these groups.  These names are defined by the user.  The renderer does not predefine any category names.

The benefit of declaring a light to be a member of a category is that other shaders may selectively ignore whole categories of lights.  The illuminance() function now takes an optional first argument which is a string:

illuminance("black", P)

In the example above, the illuminance loop will only be executed for lights that belong to the category "black".  Note that the category argument to illuminance() may not be a comma-delimited list but rather a single category.

The category string may optionally be prefixed with a minus sign:

illuminance("-black", P)

This causes illuminance() to explicitly exclude a category and accept all others.

### Scoping changes

#### Lexically scoped functions

User functions may now be defined within shaders or other functions, anywhere you could put a statement.  Such functions are invisible outside the scope in which they were defined.

#### Variable Declarations are Statements

RenderDotC now accepts local variable declarations anywhere in a shader where a statement could go.  It used to be that variable declarations needed to appear at the beginning of the shader/function, before any actual code.  The C language also had this limitation.  Now variables can be declared immediately before use just like in C++.

#### New keywords: void, output, and extern

Functions may now be declared to return void.  These functions should not have a return statment and should not be called from within expressions.

The output keyword may be used in shader parameter lists for the purpose of sending messages.

Variable declarations beginning with extern are completely ignored.  This keyword exists only for compatibility with other renderers.  In RenderDotC, globals and variables declared in outer scopes are always available to user-defined functions, provided that they are not hidden by local variables of the same name.