6.1 Syntax
| 6.1.1 General Shader Structure | ||
| 6.1.2 Variable Types | ||
| 6.1.3 Variable Classes | ||
| 6.1.4 Parameter Lists | ||
| 6.1.5 Constructs |
6.1.1 General Shader Structure
| 6.1.1.1 Traditional Structure | ||
| 6.1.1.2 Shader Objects |
6.1.1.1 Traditional Structure
Traditional shaders are pre-RSL 2.0 shaders, they exhibit a very simple structure consisting of one main entry point and a certain number of supporting functions (a structure clearly inspired by the C programming language).
{.. Function declarations .. }
[surface|displacement|volume|imager|light] shadername( Parameters )
{
.... shader body ...
}
The output of such shaders depends solely on their type and is passed back to the renderer through a standard output variable (see section Predefined Shader Variables). Here is a summery for each shader type:
- surface
- Surface shaders define the color and the opacity (or transparency) of the material by setting the Ci and Oi output variables.
- displacement
- Displacement shaders can perturb the geometry of the material by modifying the P and N variables.
- volume
- Volume shaders are executed in-between surfaces or between the eye and some surface (for atmosphere shaders) to simulate various atmospheric effects. They modify Ci and Oi as in surface shaders.
- imager
- Imager shaders run on the image plane, on every pixel. They modify Ci and alpha variables.
- light
- Light shaders are slightly more complicated than other shaders in that they also declare an
illuminate()construct(24) (see The Illuminate Construct). They operate by setting Cl and L output variables.
6.1.1.2 Shader Objects
The modern structure, introduced in RSL 2.0 specifications, closely resembles C++ or Java classes. The general structures is as follows:
class shadername( ... shader parameters ... )
{
... member variables declarations ...
... member methods declarations ...
};
In a nutshell this new structure (an example is shown in Listing 6.1) provides interesting capabilities and some performance benefits:
- Shaders can call methods and access member variables of other shaders (only public variables and methods are accessible, as expected). This feature allows, finally, to build comprehensive shader libraries that are pre-compiled and re-usable. Intra-shader data and method access is performed using the
shadervariable type (see section Co-Shaders). - Member variables can retain shader state. This avoids ad-hoc message passing and other tricks as widespread traditional shaders.
- Access to co-shaders encourages a modular, layer-based, development of looks. For example, illumination models can be passed as co-shaders to surface shaders which can take care of the general structure.
- The separation of displacement, surface and opacity methods (see below) provides the render with greater opportunities to optimize renderings.
- Last but not least, shader objects provide a greater flexibility overall and promising future development perspectives.
Variables and method declarations have the usual form of traditional shaders but can be prefixed by the public keyword. So how does such a class shader define its function (surface, displacement, ... etc) ? By implementing predefined methods as shown in Table 6.1.
|
This shader structure allows the inclusion of both a surface and a displacement entry points. Listing 6.1 demonstrates how this is done and shows the clear benefits of retaining the state of variables in shader objects. In the context of traditional shaders, sharing data between different methods had semantics or was resolved using ad-hoc methods:
- Share the result of the computation using message passing (see section Message Passing and Information). This works but complicates the data flow.
- Re-do the same computation in both the surface and displacement shaders. Also works but time is lost in re-computation.
- Displacement is performed inside the surface shader. This is the most practical solution but sadly it doesn't give the renderer a chance to do some important render-time optimizations.
Shader objets resolve this
class plastic_stucco(
float Ks = .5;
float Kd = .5;
float Ka = 1;
float roughness = .1;
color specularcolor = 1;
float Km = 0.05;
float power = 5;
float frequency = 10;
color dip_color = color(.7,.0, .2) )
{
varying float magnitude = 0;
public void displacement(output point P; output normal N)
{
/* put normalized displacement magnitude in a global variable
so that the surface method below can use it. */
point PP = transform ("shader", P);
magnitude = pow (noise (PP*frequency), power);
P += Km * magnitude * normalize (N);
N = calculatenormal (P);
}
public void surface(output color Ci, Oi)
{
normal Nf = faceforward( normalize(N), I );
vector V = - normalize( I );
color specular_component =
specularcolor * Ks * specular(Nf, V, roughness);
color diffuse_component = Kd * diffuse(Nf);
/* attenuate the specular component in displacement "dips" */
specular_component *= (1-magnitude) * (1-magnitude);
Oi = Os;
Ci = ( Cs * (Ka * ambient() + diffuse_component) +
specular_component );
Ci = mix(dip_color*diffuse_component, Ci, 1-magnitude );
Ci *= Oi;
}
}
|
6.1.2 Variable Types
| 6.1.2.1 Scalars | ||
| 6.1.2.2 Colors | ||
| 6.1.2.3 Points, Vectors and Normals | ||
| 6.1.2.4 Matrices | ||
| 6.1.2.5 Strings | ||
| 6.1.2.6 Co-Shaders | ||
| 6.1.2.7 Arrays of Variables | ||
| 6.1.2.8 The Void Type |
6.1.2.1 Scalars
All scalars in the RenderMan shading language, including integers, are declared using the float keyword. As an example, the following line declares two scalars and sets the value of one of them to 1.
float amplitude = 1.0, frequency;
Some notes about scalars:
- Since there is no boolean type in RSL (such as the
boolkeyword in C++), scalars are also used to hold truth values. - Scalars can be promoted to almost any type. For example, a color can be initialized with a scalar:
Ci = 1;
6.1.2.2 Colors
The shading language defines colors as an abstract data type: it could be an RGB 3-tuple or an elaborate spectrum definition. But until now, all known implementations only allow a 3-tuple definition in various color spaces. A color definition has the following form (square brackets mean that enclosed expression is optional):
color color_name = [color "color_space"](x, y, z);
As an example, the following line declares a color in HSV space:
color foo = color "hsv" (.5, .5, .5);
If the space is not provided it is assumed to be RGB:
color foo = 1; /* set the color to (1,1,1) in RGB space. */
|
Operations on colors are: addition, multiplication and subtraction. All operations are performed channel per channel.
Transformation of points between the various color spaces is performed using the ctransform() shadeop (see ctransform)
6.1.2.3 Points, Vectors and Normals
Point-like variables are 3-tuples used to store positions, directions and normals in the euclidean 3-space. Such a 3-tuple definition is of the form:
{point|normal|vector} = [ {point|normal|vector} "coordsys"] (x, y, z);
Where `coordsys' can be one of standard coordinate systems (see Table 6.3) or any coordinate system defined by RiCoordinateSystem. As an example, all the following definitions are valid:
point p1 = (1, 1, 1); /* declare a point in current coordinate system */ vector v1 = vector "world" (0,0,0); /* a vector in world coordinates */ normal NN = normal 0; /* normal in current space. Note type promotion */
Transforming point-like variables between two coordinate systems is performed using the transform() shadeop (see transform shadeop). It is important to note that that shadeop is polymorphic: it acts differently for points, vectors and normals. The following code snippet transforms a vector from `world' to `current' space:
vector dir = transform( "world", "current", vector(0,1,0) );
NOTE3Delight keeps all points in the `current' coordinate system(25), which happens to be `camera'. This could sometimes lead to confusion:
point p1 = point "world" (0,1,0); printf( "%p\n", p1 );One could expect to see (0,1,0) output by
printf()but this is generally not the case: since p1 is stored in the `current' coordinate system, the printed value will be the point resulting from the transformation of p1 from `world' to `current'. So if the camera space is offset by (0,-1,0) the output ofprintf()would be (0,0,0).
A different implementation would have been possible (where points are kept in their respective coordinate systems as late as possible) but that would necessitate run-time tracking coordinate systems along with point-like variables, which is not handy and offer no real advantage.
Operations on Points
point + vector- The results is a point.
point - point- The result is a vector.
point * point- The result is point.
point * vector;- The result is point.
Operations on Vectors and Normals
Any vector type in the following table can be interchanged with a normal.
vector . vector- The result is a scalar (dot product);
vector + vectorvector - vector;- The result is a vector;
On Correctness
The shader compiler will not, by default, enforce mathematical correctness of certain operations. For example, adding two points together makes no real sense geometrically (one should add a vector to a point) but this is still accepted by the shader compiler (although with a warning). Historically, in the very first definitions of the RenderMan Shading Language, there were no vectors nor normals so everything had to be performed on points.
6.1.2.4 Matrices
A matrix in the shading language is a 4x4 homogeneous transformation stored in row-major order.
NOTEMatrices declared using
Ricalls or in a RIB file are column major.
A matrix initialization can have two forms:
matrix m = [matrix "space"] scalar;
matrix m2 = [matrix "space"] ( m00, m01, m02, m03, m10, m11, m12, m13,
m20, m21, m22, m23, m30, m31, m32, m33 );
The first form sets the diagonal of the matrix to the specified scalar with all the other positions being set to zero. So to declare an identity matrix one would write:
matrix identity = 1;
A zero matrix is declared similarly:
matrix zero = 0;
Exactly as for point-like variables (see section Points, Vectors and Normals, matrices can be declared in any standard or user-defined coordinate system (see Table 6.3):
matrix obj_reference_frame = matrix "object" 1;
Point-like variables can be transformed with matrices using the transform() shadoep (see transform shadeop).
6.1.2.5 Strings
Strings are most commonly used in shaders to identify different resources: textures, coordinate systems, other shaders, ...etc. A string declaration is of the form:
string name = "value";
One can also declare strings like this:
string name = "s1" "s2" ... "sN";
All operations on strings are explained in String Manipulation.
6.1.2.6 Co-Shaders
Co-shaders are both a variable type and an abstract concept defining a modular component. Co-shaders are declared in a shader using the shader keyword and can be loaded using special co-shader access functions as detailed in Co-Shader Access. For example,
shader specular_component = getshader( "specular_instance" );
if( specular_component != null )
Ci += specular_component->Specular( roughness ) * Ks;
Declares a specular_component shader variable and uses it to load and execute the `specular_instance' co-shader (which could be a Blinn or a Phong specular model depending on the co-shader). The co-shader has to be declared in the scene description using the RiShader() call. Member variables in a co-shader can be accessed using getvar() method:
float Ks; /* "has_Ks" will be set to 0 if no Ks variable is declared in the co- shader. Also note that the last output parameter "Ks" can be omit- ted in which case getvar serves to check the existence of the target member variable. */ float has_Ks = getvar( specular_component, "Ks", Ks );
The arrow operator can be used instead of getvar() to access a member variable in a more compact way:
Ks = specular_component->Ks;
The difference with getvar() is that 3Delight will print a warning if the variable is not declared in the target co-shader. Additionally, the arrow operator will only access variables in a read-only mode and cannot be used to write into variables, this means that to modify a member variable in a target co-shader one has to use a "setter" method.
6.1.2.7 Arrays of Variables
Any shading language variable can be used as a one-dimensional array. The general syntax for an array is:
type array_name[array_size] [= {x, y, z, ...}];
type array_name[array_size] [= scalar];
The total number of initializers must be equal or less than array_size(26). When initializing an array with a scalar all array's elements are set to that particular value. For example:
float t[4] = { 0, 1, 2, 3 };
color n[10] = 1; /* set first element to (1,1,1), the rest to 0 */
Note that the length of the array in the initializer can be omitted, in which case the size is deduced from the right-hand expression:
float t[] = { 1,2,3 }; /* create an array of size 3. */
The length of arrays can be fetched using the arraylength() shadeop (see arraylength shadeop:
float array_length = arraylength( t );
A particular element in an array can be accessed using squared brackets:
float t[4] = 1; float elem_2 = t[2];
6.1.2.8 The Void Type
This type is only used as a return type for a shading language function as explained in Functions.
6.1.3 Variable Classes
Additionally to variable types, the RenderMan shading language defines variables classes(27). Since the beginning, RSL shaders were meant to run on a multitude of shading samples at a time, a technique commonly called single instruction multiple data or in short: SIMD. SIMD execution was a natural specification for the shading language mainly because the first production oriented RenderMan-compliant renderer implemented a REYES algorithm - a type of algorithm well suited for such grouped execution. An inherent benefit of an SIMD execution pipeline is that the cost of interpretation is amortized: one instruction interpretation is followed by an execution on many samples, this makes interpretation in RSL almost as efficient as compiled shaders.
| 6.1.3.1 Uniform Variables | ||
| 6.1.3.2 Varying Variables | ||
| 6.1.3.3 Constant Variables | ||
| 6.1.3.4 Default Class Behaviour |
6.1.3.1 Uniform Variables
Uniform variables are declared by adding the uniform keyword in front of a variable declaration. For example,
uniform float i;
Defines a uniform scalar i. Uniform variables are constant across the entire evaluation sample set. In slightly more practical terms, a uniform variables is initialized once per grid. All variables that do not vary across the surface are good uniform candidates (a good example is a for loop counter). It is important to declare such variables as uniforms since this can accelerate shader execution and lower memory usage(28).
NOTEThe evaluation sample set is a grid in the primary REYES render but is not necessary a grid in the ray-tracer.
6.1.3.2 Varying Variables
Varying variables are variables that can change across the surface and is initialized once per micro-polygon. A perfect example is the u and v predefined shader variables (see section Predefined Shader Variables) or the result of the texture() call (see texture shadeop). To declare a varying variable one has to use the varying keyword:
varying color t;
Declaring varying arrays follows the same logic but one has to make sure a varying is really needed in such a case: a varying array can consume a large amount of memory.
NOTEAssigning varying variables to uniforms is not possible and the shader compiler will issue a compile-time error. Assigning uniforms to varyings is perfectly legal.
6.1.3.3 Constant Variables
Constant variables were introduced in RSL 2.0 and are meant to declare variables that are initialized only once during the render. This type of variables is further explained in Shader Objects.
6.1.3.4 Default Class Behaviour
If no uniform or varying keyword is used, a default course of action is dictated by the RenderMan shading language. This default behaviour is context depended:
- Member variables, declared in shader objects (see section Shader Objects) are
varyingby default. - Variables declared in shader and shader class parameter lists are
uniformby default. - Variables declared inside the body of shaders are
varyingby default. - Variables declared in function parameter lists inherit the class of the parameter and this behaviour also propagates inside the body of the function. This is possible since RSL functions are inlined, more on this in Functions.
It is recommended not to abuse the varying and uniform keywords where the default behaviour is clear, this makes code more readable.
6.1.4 Parameter Lists
Parameters list can be declared in three different contexts: shader parameters, shader class parameters and function parameters. Shader and shader class parameters have exactly the same form and will be described in the same section whereas function parameters has some specifics and will be described in their own section.
| 6.1.4.1 Shader and Shader Class Parameters | ||
| 6.1.4.2 Function Parameters |
6.1.4.1 Shader and Shader Class Parameters
This kind of parameters has three particularities:
- Each parameter must have a default initializer. This is to be expected since all parameters must have a valid state in case they are not specified in the RenderMan scene description (through
RiSurfaceor similar). - By default, each parameter is uniform (see section Variable Classes).
- They can be declared as output. Meaning that they can be passed to a display driver (as declared by
RiDisplay).
Follows an example of such a parameter list:
surface dummy(
float intensity = 1, amplitude = 0.5;
output color specular = 0; )
{
shader body
}
6.1.4.2 Function Parameters
Function parameters have the same form as shader parameters but have no initializer. Another notable difference is that, by default, parameters class is inherited from the argument. For example:
/* note that 'x' has no class specification. */
float sqr( float x; ) { return x*x; }
float uniform_sqr( uniform float x; ) { return x*x; }
surface dummy( float t = 0.5; )
{
/* function will be inlined in uniform form. */
Ci = sqr( t );
/* funciton will be inlined in varying form. */
Ci += sqr( u );
/* this will not compile! ('v' is uniform but uniform_sqr
expects a uniform argument) */
Ci += uniform_sqr( v );
}
More about functions in Functions.
6.1.5 Constructs
| 6.1.5.1 Conditional Execution | ||
| 6.1.5.2 Classical Looping Constructs | ||
| 6.1.5.3 Special Looping Constructs | ||
| 6.1.5.4 Functions | ||
| 6.1.5.5 Methods |
6.1.5.1 Conditional Execution
Similarly to many other languages, the conditional block is built using the if/else keyword pair:
if( boolean expression )
statement
[else
statement ]
The bracketed else part is optional.
6.1.5.2 Classical Looping Constructs
There are the two classical(29) looping constructs in the shading languag: the for loop and the while loop. Both work almost exactly as in many other languages (C, Pascal, etc...). The general form of a for loop is as follows:
if( expression ; boolean expression ; expression )
statement
For example:
uniform float i;
for( i=0; i<count; i += 1 )
{
... loop body ...
}
Note that i is uniform. This means that the loop counter is the same for all shading samples during SIMD execution. Declaring loop counters as varying makes little sense, usually. Also note that it is not possible to declare a variable in the loop initializing expression.
The while loop has the following structure:
while( boolean expression ) statement
For example,
uniform float i=0;
while( i<10 )
{
i = i + 1;
}
The normal execution of the for/while loop can be altered using two special keywords:
continue [l]- This skips the remaining code in the loop and evaluates loop's boolean expression again. Exactly as in the C language. The major difference with C is the optional parameter l that specifies how many loop levels to exit. The default value of l is 1, meaning the currently executing loop.
break [l]- This keyword exits all loops until level l. This is used to immediately stop the execution of the loop(s).
NOTEHaving the ability to exit l loops seems like a nice feature but usually leads to obfuscated code and flow. It is recommended not to use the optional loop level l, if possible.
6.1.5.3 Special Looping Constructs
This section describes constructs that are very particular to the RenderMan shading language. These constructs are meant to describe, in a clear and general way, a fundamental problem in rendering: the interaction between light and surfaces.
The illuminance Construct
This construct is an abstraction to describe an integral over incoming light. By nature, this construct is only meaningful in surface or volume shaders since only in these two cases that one is interested to know about incoming light (an example of a different usage is shown in Baking Using Lights). There are two ways to use illuminance:
- Non-oriented. Such a statement is meant to integrate light coming from all directions. In other words, integrate over the entire sphere centered at position.
illuminance ( [string category], point position, ... ) statement - Oriented. Such a statement will only consider light that is coming from inside the open cone formed by position, axis and angle.
illuminance ( [string category,] point position, vector axis, float angle, ... ) statement
Light Categories
The optional category variable specifies a subset of lights to include or exclude. This feature is commonly named Light categories. As explained in Predefined Shader Parameters, each light source can have a special __category parameter which lights the categories to which it belongs. Categories can be specified using simple expressions: a series of terms separated by the & or | symbols (logical and, logical or). Valid category terms are described in Table 6.4. If a category is not specified illuminance will proceed with all active light sources.
|
Follows a simple example.
illuminance( "specular&-crowd", P )
{
/* Execute all lights in the "specular" category but omit
the ones that are also in the "crowd" category. */
}
Message Passing
illuminance can take additional parameters, as shown in the general form above. These optional parameters are used for message passing and forward message passing. Forward message passing is performed using the special send:light: parameter and is used to set some given light parameter to some given value prior to light evaluation. For example,
uniform float intensity = 2;
illuminance( P, ..., "send:light:intensity", intensity )
{
... statements ...
}
Will set the intensity parameter of the light source to 2 and execute the light (overriding the value in light's parameter's list). The parameter must have the same type and same class (see section Variable Classes) in order for the forward message passing to work.
Getting values back from a light source is done through the same mechanism by using the light: parameter prefix. For example,
/* Some default value in case light source has no "cone_angle" parameter */
float cone_angle = -1;
illuminance( P, ..., "light:cone_angle", cone_angle )
{
/* do something with the "cone_angle" parameter ... */
}
Will retrieve the cone_angle parameter from the light source.
Both forward and backward message passing can be combined in the same illuminance loop:
/* Some default value in case light source has no "cone_angle" parameter */
uniform float intensity = 2;
float cone_angle = -1;
illuminance( P, ..., "send:light:intensity", intensity,
"light:cone_angle", cone_angle )
{
/* do something with the "cone_angle" parameter ... */
}
NOTEMessage passing is a powerful feature in the shading language but one has to be aware that improper use could complicate the rendering pipeline fundamentally since it introduces a bi-directional flow of data between light sources and surface shaders.
Working Principles
The mechanics of illuminance are straightforward: loop over all active lights withing the specified category and evaluate them to compute Cl, Ol and L (see section Predefined Shader Variables). These variables are automatically available in the scope of all illuminance loops. Note that the result of light evaluation is cached so that calling illuminance again within the same evaluation context doesn't trigger re-evaluation of light sources. This optimization feature can be disabled using a special `lightcache' argument to illuminate. In the example below, the evaluation light cache will be flushed and the light sources will be re-evaluated.
illuminance( ..., "lightcache", "refresh" )
{
... statements ...
}
Flushing the light cache can have a sever performance impact, it is advised not to touch it unless necessary.
NOTESome shadeops contain implicit
illuminanceloops. These shadeops are:specular,specularstd,diffuseandphong. These are all described in Lighting.
The illuminate Construct
The illuminate construct is only defined in light source shaders and serves as an abstraction for positional light casters. In a way, it could be seen as the inverse of an illuminance construct. There are two ways to use illuminate:
- Non-oriented. Such as a statement is meant to cast light in all directions.
illuminate( point position ) statement - Oriented. Such a statement will only cast light from a given position and along a given axis and angle. Surface points that are outside the cone formed by <position,axis,angle> will not be lit (Cl will be set to zero, see below).
illuminate( point position, vector axis, float angle ) statement
Inside the illuminate construct, three standard variables are of interest:
- The
Lvariable. This is set by the renderer to Ps - P. This means that L is a vector pointing from light source's position to the point on the surface being shaded. The length of L is thus the distance between the light and the point on the surface. - The
Clvariable. This variable is the actual color of the light and should be set inside the construct to the desired intensity. - The
Olvariable. This is the equivalent ofOiand describes light opacity. It is almost never used and has been deprecated in the context of shader objects (see section Shader Objects).
Listing Listing 6.2 shows how to write a standard point light. Note that the position given to illuminate is the origin of the shader space (see Table 6.3) and not some parameter passed to the light; this is desirable since the point light can be placed anywhere in the scene using RiTranslate (or similar) instead of specifying a parameter.
light pointlight( float intensity = 1 ; color lightcolor = 1 )
{
illuminate( point "shader" 0 )
{
Cl = intensity * lightcolor / (L.L);
}
}
|
The solar Construct
This construct is similar to illuminate but describes light cast by distant light sources. It also has two forms albeit only one is partially supported by 3Delight:
- Non-directional. This form decribes light coming from all points at infinity (such as a light dome).
solar( ) statementCorrectly implementing thesolarconstructs implies integration over all light direction inside shadeops such asspecular()anddiffuse()(30) . This is not yes implemented in 3Delight and the statement above is replaced by:solar( I, 0 ) statement - Directional. Describes light coming from a particular direction and covering some given solid angle.
solar( vector axis, float angle ) statementFor the same reason as in the case above, 3Delight doesn't consider the angle variable and considers this form as:solar( vector axis, 0 ) statement
An example directional light is listed in Listing 6.3. This light source cast lights towards the positive z direction and has to be properly placed using scene description commands to light in any desired direction.
light directionallight( float intensity = 1 ; color lightcolor = 1 )
{
solar( vector "shader" (0,0,1), 0 )
{
Cl = intensity * lightcolor;
}
}
solar. |
The gather Construct
This construct is similar in spirit to solar but explicitly relies on ray-tracing(31) to collect illumination and other data from the surrounding scene elements. In other words, this construct collects surrounding illumination through sampling. Incidentally, gather is well suited to integrate arbitrary reflectance models over incoming indirect light (light that is reflected from other surfaces). The general form of a gather loop is as follow:
gather( string category, point P, dir, angle, samples, ... )
statement
[else
stetement]
The <P, dir, angle> triplet specifies the sampling cone and samples specifies the number of samples to use (more samples will give more accurate results). The angle should be specified in radians and is measured from the axis specified by dir: an angle of zero implies that all rays are shot in direction dir and an angle of PI/2 casts rays over the entire hemisphere. gather stores it's result in the specified optional variables which depend on the specified category. The table below explains all supported categories and related output variables.
samplepattern-
This category is meant to run the loop without ray-tracing and without taking any further action but to provide ray's information to the user. This is useful to get the sampling pattern of
gatherto perform some particular operation. In this mode, one have access to the following output variables :- ` ray:origin'
- Returns ray's origin.
- ` ray:direction'
- Returns ray's direction. Not necessarily normalized.
color trans = 0; uniform float num_samples = 20; uniform float max_dist = 10; point ray_origin = 0; vector ray_direction = 0; gather( "samplepattern", P, N, PI/2, num_samples, "ray:direction", ray_direction, "ray:origin", ray_origin ) { vector normalized_ray_dir = normalize( ray_direction ); trans += transmission( ray_origin, ray_origin + normalized_ray_dir*max_dist ); } trans /= num_samples;Listing 6.4: An example usage of the `samplepattern' category in gather.
Note that in this mode, theelsebranch of the construct is never executed. illuminance-
In this case,
gatheruses ray tracing to perform the sampling. Additionally to variables available to the samplepattern category, any variable from surface, displacement or volume shaders can be collected:- ` surface:varname'
-
Returns the desired variable from the context of the surface shader that has been hit by the gather ray. Variables' values are taken after the execution of the shader. The typical variable is
surface:Cibut other variables, such assurface:Norsurface:P, are perfectly valid(32). Additionally, `varname' can be any output variable declared in the shader. - ` displacement:varname'
- ` volume:varname'
- Returns the desired variable from the context of the displacement or volume shader that has been evaluated for the gather ray. All comments for the surface case above also apply here. All returned variables are those encountered at the closest surface to be hit.
- ` ray:length'
- Returns distance to closest hit.
The following example demonstrates how to compute a simple ambient occlusion effect.
float occ = 0; uniform numSamples = 20; gather( "illuminance", P, Nf, PI/2, numSamples ) ;/* do nothing ... */ else occ += 1; occ /= numSamples; environment:environmentname-
The `environment' category is used to perform importance sampling on a specified environment map. Importance sampling can be used to implemented image based lighting as shown in the `envlight2.sl' shader provided with the 3Delight package. Note that no ray-tracing is performed: importance-sampled rays are returned and it is up to the user to perform ray-tracing if needed. This category unveils three optional parameters (further explained in Image Based Lighting):
- ` environment:color'
- Return the color from the environment map.
- ` environment:solidangle'
- Returns the sampled solid angle in the environment map. This is the size of the sampled region and is usually inversely proportional to the number of sampled rays - more samples will find finer details in the environment map.
- ` environment:direction'
- The direction pointing towards the center of the sampled region in the environment map. Note that `ray:direction' holds a randomized direction inside the region and this direction is suitable for sampling.
pointcloud:pointcloudfile-
Another special form of the
gather()construct can be used to gather points in a previously generated point cloud. This is better illustrated in Example 6.1./* Average 32 samples from a point cloud surrounding the current point using gaussian filtering. Assume point cloud has been saved in "object" space. */ point object_P = transform( "object", P ); normal object_N = ntransform( "object", N ); string category = concat( "pointcloud", ":", texturename ); color surface_color = 1, sum_color = 0; float total_atten = 0; point position = 0;; float point_radius; uniform float radius = 0.1; gather( category, object_P, object_N, PI, 32, "point:surface_color", surface_color, "radius", radius, "point:position", position ) { float dist = distance(object_P, position); dist = 2*dist / radius; float gaussian_atten = exp( -2. * dist * dist ); total_atten += gaussian_atten; sum_color += gaussian_atten * surface_color; } /* normalize final color */ sum_color *= total_atten;Example 6.1: Using gather() to access point cloud data.
As shown in the above example, specifying `pointcloud:filename' as the category togather()will give access to the following variables:- ` point:position'
- Position of the currently processed point
- ` point:normal'
- Normal of the currently processed point
- ` point:radius'
- Radius of the currently processed point
- ` point:varname'
- Any variable stored in the point cloud
Note
- Points from the point cloud will listed in
gather()from the closest to the furthest, relatively to the provided position P. - There is no guarantee that the actual number of points listed will be equal to the number of points asked. This can happen for example if there is no enough points in point cloud (still unlikely) or the provided radius limits the total number of points available in a certain position.
- Points outside the radius can still be listed if their extent is part of the gather sphere (points in a point cloud are defined by a point and a radius).
- The N and angle parameter provided to the point cloud version of
gather()are not considered.
gather accepts many optional input parameters and these are all explained in Table 6.10. Additionally, gather accepts the `distribution' parameter to sample arbitrary distributions(33), this parameter is described in Table 6.11. The next code snippet illustrates how to use an environment map as a sampling distribution to compute image based occlusion:
float occ = 0;
uniform numSamples = 20;
gather( "illuminance", P, Nf, PI/2, numSamples,
"distribution", "lightprobe.tdl" )
{
;/* do nothing ... */
}
else
occ += 1;
occ /= numSamples;
6.1.5.4 Functions
As in many other languages, functions in the RenderMan shading language are re-usable blocks of code that perform a certain task. The general form of an RSL function is as follows:
return_type function_name ( parameters list )
{
... function body ...
return return_value;
}
In traditional shaders (see section Traditional Structure) functions have to be declared prior to their calling points. In class shaders (see section Shader Objects), functions can be declared anywhere outside the class or inside the class, with functions declared in the class scope being accessible to other shaders. An example function is shown Listing 6.5.
void print_error( string tex; )
{ printf( "error loading texture %s.\n", tex ); }
color tiled_texture( string texture_name, float u, v;
float num_tiles_x, num_tiles_y;
output float new_u, newv; )
{
color red() { return color(1,0,0); }
if( texture_name == "" )
{
print_error( texture_name );
return red();
}
/* assuming u & v are in the range [0..1]. */
new_u = mod(u, 1/num_tiles_x) * num_tiles_x;
new_v = mod(v, 1/num_tiles_y) * num_tiles_y;
return texture( texture_name, new_u, new_v );
}
|
Some noteworthy points about the example above:
- Functions can have multiple exit points.
- Functions can return no value by using the
voidkeyword. - Functions can return their values using one or more
outputparameters. - Similarly to the Pascal programming language, functions can be declared inside other functions or in any new scope.
One particularly useful concept in the RenderMan shading language is that functions are polymorphic(34), meaning that the same function can be delcared with different signatures to accept different parameter types. For example:
float a_plus_b( float a, b; ) { return a+b; }
color a_plus_b( color a, b; ) { return a+b; }
6.1.5.5 Methods
Methods are declared as normal functions with the following differences:
- They are declared inside a shader object (see section Shader Objects).
- They are preceded by the
publickeyword. - Methods are not inlined as usual functions and are callable from other shaders.
- Methods cannot be declared inside other methods.
The general form for a RSL method is as follows:
public return_type function_name ( parameters list )
{
... function body ...
return return_value;
}
For example,
public void surface( output color Ci, Oi; )
{
}
Declares the standard surface() entry point in a shader object.
3Delight 8.5. Copyright 2000-2009 The 3Delight Team. All Rights Reserved.