Shader Parameter Declarations

In addition to the state variables that are provided by mental ray and are shared by all shaders, every shader has shader parameters. In the .mi scene file, shader references look much like a function call: the shader name is given along with a list of parameters. Every shader call may have a different list of parameters. mental ray does not restrict or predefine the number and types of shader parameters, any kind of information may be passed to the shader. Typical examples for shader parameters are ambient, diffuse, and specular colors for material shaders, attenuation parameters for light shaders, and so on. An empty parameter list in a shader call (as opposed to a shader declaration) has a special meaning; see the note at the end of this chapter.

In this manual, the term "parameters" refers to shader parameters in the .mi scene file; the term "arguments" is used for arguments to C functions.

Shaders need both state variables and shader parameters. Generally, variables that are computed by mental ray, or whose interpretation is otherwise known to mental ray, and that are useful to different types or instances of shaders are found in state variables. Variables that are specific to a shader, and that may change for each instance of the shader, are found in shader parameters. mental ray does not access or compute shader parameters in any way, it merely passes them from the .mi file to the shader when it is invoked.

To interpret these parameters in the .mi file, mental ray needs a declaration of parameter names and types that is equivalent to the C struct that the shader later uses to access the parameters. The declaration in the .mi file must be exactly equivalent to the C struct, or the shader will mis-interpret the parameter data structure constructed by mental ray. (Using the mkmishader utility ensures that both declarations agree, see section mkmishader.) This means that three parts are needed to write a shader: the .mi declaration, the C parameter struct, and the C source of the shader. The .mi declaration is normally stored in a separate file that is included into the .mi scene file using a $include statement.

The syntax of .mi shader declarations is fully described in section declaration. Here, only a brief overview is given. Shaders must be declared with shader name, return type, and the types and names of all parameters. Options such as the shader version may be specified also:

declare shader
    [ type ] "shader_name" (
        type "parameter_name",
        type "parameter_name",
        type "parameter_name"
    [ version versionint ]
    [ options ]
end declare

For example, a simple material shader containing ambient, diffuse, and specular colors, transparency, an optional array of bump map textures, and an array of lights could be declared in the .mi file as:

declare shader  
    color "my_material" (  
        color "ambient",  
        color "diffuse",  
        color "specular",  
        scalar "shiny",  
        scalar "reflect",  
        scalar "transparency",  
        scalar "ior",  
        vector texture "bump",  
        array light "lights"  
    version 1  
end declare  

If there is only one array, there is a small efficiency advantage in listing it last. It is recommended that the largest array (arrays of large structs are larger than arrays of primitives) is given as the last parameter. The material shader declared in this example can be used in a material statement like this:

material "mat1"  
    "my_material" (  
        "specular" 1.0 1.0 1.0,  
        "ambient" 0.3 0.3 0.0,  
        "diffuse" 0.7 0.7 0.0,  
        "shiny" 50.0,  
        "bump" "tex1",  
        "lights"[ "light1", "light2","light3" ],  
        "reflect" 0.5  
end material  

Note that the parameters can be defined in any order that does not have to agree with the order in the declaration, and that parameters can be omitted. Omitted parameters default to 0. This example assumes that the texture tex1 and the three lights have been defined prior to this material definition. Again, be sure to use the names of the textures and lights, not the names of the texture and light shaders. All names in the above two examples were written as strings enclosed in double quotes to disambiguate names from reserved keywords, and to allow special characters in the names that would otherwise be illegal.

When choosing names, avoid double colons and periods, which have a special meaning when accessing structured shader return values and interface parameters in phenomenon subshaders.

When the shader my_material is called, its third argument will be a pointer to a data structure built by mental ray from the declaration and the actual parameters in the .mi file. In order for the C source of the shader to access the parameters, it needs an equivalent declaration in C syntax that must agree exactly with the .mi declaration. The type names can be translated according to the following table:

.mi syntax C syntax
boolean miBoolean
integer miInteger
scalar miScalar
vector miVector
transform miMatrix
color miColor
spectrum miSpectrum_para as shader argument, miSpectrum else
data miTag of an miUserdata
shader miTag of an miFunction
color texture miTag of an miFunction or miImg_image
scalar texture miTag of an miFunction or miImg_image
vector texture miTag of an miFunction or miImg_image
light miTag of an miInstance for a light
light miTag of an miInstance for a light group
material miTag of an miMaterial
geometry miTag of an miObject, miGroup, or miInstance
lightprofile miTag of an miLight_profile
struct struct
array int, int, type[1]

A light group is an instance group that contains instances of lights. For the purpose of shading, the group is treated as if it were a single light.

It is strongly recommended to use the same parameter names in the C declaration as in the .mi declaration. Also, structures in either declaration should be reflected in the other, even if not enclosed in arrays, to ensure that mental ray inserts the same padding as the C compiler.

Arrays are more complicated than the types in this table because the size of the array is not known at declaration time. The C declaration of an array consists of a start index prefixed with i_, the size of the array prefixed with n_, and the array itself, declared as an array with one element. mental ray will allocate the structure as large as required by the actual array size at call time. To access array element i in the range 0…n_array−1, the C expression array[i_array + i] must be used. This expression allows mental ray to store the shader parameters in virtual shared memory regardless of the base address of the shader parameter structure, which is different on every machine on the network.

Here is the C structure for the above example .mi declaration:

struct my_material {
    miColor  ambient;
    miColor  diffuse;
    miColor  specular;
    miScalar shiny;
    miScalar reflect;
    miScalar transparency;
    miScalar ior;
    miTag    bump;
    int      i_lights;
    int      n_lights;
    miTag    lights[1];

Note that here the order of structure members must match the order in the .mi declaration exactly. For example, suppose a shader has a .mi declaration containing an array of integers:

declare shader
    color "myshader" ( array integer "list" )
    version 1
end declare

The C declaration for the shader's parameters is:

struct myshader {
    int       i_list;
    int       n_list;
    miInteger list[1];

A shader that needs to operate on this array, for example printing all the array elements to stdout, would use a loop like this:

int *list  =  mi_eval_integer(paras->list);
int i_list = *mi_eval_integer(&paras->i_list);
int n_list = *mi_eval_integer(&paras->n_list);
int i;
for (i=0; i < n_list; i++)
    mi_info("%d", list[i_list + i]);

assuming that paras is the third shader argument and has type struct myshader *. The use of the i_list parameter may seem strange to C programmers, who may wish to hide it in a macro like

#define EL(array,nel) array[i_##array + nel]

This macro requires an ANSI C preprocessor to work properly. It is not predefined in the common header file shader.h. The reason for this peculiar way of accessing arrays is improved performance. The array list[1] has space for only one element, because the actual number of array elements depends on the shader instance in the .mi file, which may list an arbitrary number of elements. Since mental ray is based on a virtual shared database that moves pieces of data such as shader parameters transparently from one machine to another, no such piece of data may contain a pointer. Pointers would not be valid in another machine's virtual address space. Adjusting the pointer on the other machine is impractical because it would significantly reduce performance for some scenes, and would require knowledge of the structure layout for finding the pointers that may not be available in versions of mental ray not based on a .mi front-end parser. Therefore, the array is appended to the parameter structure, so the entire block can be moved to another machine in a single network transfer. It is safe to access the first element of the array, because space for it is always allocated by declarations such as list[1], but the second is a problem because in a C declaration like

struct myshader {
    int       i_list;
    int       n_list;
    miInteger list[1];
    miScalar  factor;
    miBoolean bool;

the second element, list[1], occupies the same address as factor, and the third overlays bool. The situation becomes more complex for arrays of structures. The solution is to put the value of the first element after the last "regular" shader parameter, bool in this example, followed by the other element values. This means that the first few C array elements that overlay other parameters must be skipped. The i_ variable tells the shader exactly how many. In the example, i_list would be 3. Assuming the following shader instance, used as part of a material, texture, or some other definition requiring a shader call:

"myshader" (
    "factor" 1.4142136,
    "list"   [ 42, 123, 486921, 777 ],
    "bool"   on

mental ray would arrange the values in memory like this:

C declaration Value Value access
i_list 3
n_list 4
list[1] 0
factor 1.4142136 factor
bool miTRUE bool
42 list[i_list + 0]
123 list[i_list + 1]
486921 list[i_list + 2]
777 list[i_list + 3]
← 4 bytes →

On multithreaded machines, it is possible that any given shader runs in multiple threads simultaneously. When this happens, they share all static variables defined in the sources, all shader parameters, and the user pointer. The only things that are separate in separate threads are "auto" variables on the stack, and the state variables. Also, mental ray makes sure that an init shader runs once, in a single thread, before the actual shader runs in one or more threads. This makes init shaders the natural place to set up the shader user pointer, perhaps to store preprocessed shader parameters. See section multithreading for more information on multithreaded shader design.

Shaders should never write to shader parameters because they might be accessed by other threads or hosts or by future frames, and because shader and interface assignments might exist that would be destroyed. (In general, no shader should ever modify the scene.)

Copyright © 1986, 2013 NVIDIA Corporation