Keywords: main

Next: 3.7 Shader Versioning Up: 3. Using and Writing Previous: 3.5 Shader Parameter Declarations

# 3.6 Parameter Assignments and mi_eval

The chapter on the .mi scene description language (page ) explains assignment of shaders and phenomenon interface parameters to shader parameters. Assignments replace the constant value of a parameter with a link to a shader that gets called automatically when the shader asks for the value of the parameter, or a link to the phenomenon interface which is looked up, respectively.

Shader assignments simplify the development of small, simple shaders called base shaders that can be combined into larger shaders or phenomena. For example, assignments make it possible to apply a texture to any shader parameter simply by replacing the constant value of a parameter with an assignment to a texture shader. It is no longer necessary for shader writers to anticipate which parameters should be texturable, and adding additional shader parameters of type `color texture` to the parameter list, just in case.

For example, consider this shader assignment example from page :

         declare shader
            color "phong" (color  "ambient",
                           color  "diffuse",
                           color  "specular",
                           scalar "shiny")
            version 1
         end declare

         declare shader
            struct {color "a", color "b"}
                  "texture" (color texture "picture")
            version 1
         end declare

         color texture "fluffy" "/tmp/image.rgb"

         shader "map" "texture" (
            "picture"   "fluffy")

         shader "mtlsh" "phong" (
            "shiny"     50,
            "ambient"   0.3 0.3 0.3,
            "diffuse"   = "map.a",
            "specular"  = "map.b")

The author of the phong shader can focus on the purpose of the shader (Phong illumination) without having to consider texturing at all. The texture is applied later as a shader assignment to the parameters of phong. This example further illustrates that shaders may have structured return values, in this case in the shader texture which returns both a and b, which can be picked up individually using the dot-member notation.

Frequently, shaders do not access all their parameters. Consider a multiplexing material shader that calls one of two material shaders depending on whether the incident ray hit the front face or the back face of the material. It would be very inefficient to evaluate both material shaders and then choose one of the two returned colors. For this reason, mental ray does not pre-evaluate all assignments before calling a shader. Instead, the shader itself must ask for the value of one of its parameters explicitly. This is done with the mi_eval function. The signature of mi_eval is

         void      *mi_eval           (miState    *state,
                                       void       *param)
         miBoolean *mi_eval_boolean   (miBoolean  *param)
         miInteger *mi_eval_integer   (miInteger  *param)
         miScalar  *mi_eval_scalar    (miScalar   *param)
         miVector  *mi_eval_vector    (miVector   *param)
         miScalar  *mi_eval_transform (miScalar   *param)
         miColor   *mi_eval_color     (miColor    *param)
         miTag     *mi_eval_tag       (miTag      *param)

The typed variants of mi_eval are implemented as macros that perform all necessary typecasting, as a convenience for shader writers. There are three cases handled by mi_eval:

- If there is no assignment to the parameter whose address is passed, mi_eval simply returns that parameter. Being a macro, it can do so without measurable loss of performance.

- If the parameter is assigned to a shader, the shader will be called automatically and a pointer to the returned value is returned. If the shader was called before in the same context (such as the current phenomenon execution), the shader is not called again and a pointer to the cached previous result is returned.

- If the parameter is assigned to the interface of the phenomenon the shader is defined in, mi_eval returns a pointer to the value stored in the interface.

In any case, the end result is that mi_eval returns a pointer to the parameter value, no matter where it comes from. If the shader accessed its parameters directly, without using mi_eval, it would get garbage (such as 0 or `NaN`) if the parameter is assigned. For arrays, mi_eval must be applied to the array itself, as well as the `i_`array and `n_`array integers. Once the address of the array has passed through mi_eval, the array pointer is subscripted normally without applying mi_eval to each element. This is a convention; only entire arrays should be assigned to other shaders or to the phenomenon interface if present, but not individual array members.

For example, the phong shader in the example above would be implemented as:

         struct phong {
            miColor       ambient;
            miColor       diffuse;
            miColor       specular;
            miScalar      shiny;
         };

         int phong_version(void) {return(1);}

         miBoolean phong(
            miColor       *result,    /* returned illum color */
            miState       *state,     /* ray tracer state */
            struct phong  *paras)     /* phong parameters */
         {
            miColor       *diffuse;   /* final diffuse param */
            miColor       *specular;  /* final specular param */
            miScalar      shiny;      /* phong exponent */
            int           n, i;       /* light counter */
            miTag         *lights;    /* global light list */
            miInteger     samples;    /* # of samples taken */
            miColor       color;      /* color from light */
            miColor       sum;        /* summed sample colors */
            miVector      dir;        /* direction to light */
            miScalar      dot_nl;     /* normal <dot> dir*/
            miScalar      spec_f;     /* specular factor */

            *result  = *mi_eval_color (&paras->ambient);
            diffuse  =  mi_eval_color (&paras->diffuse);
            specular =  mi_eval_color (&paras->specular);
            shiny    = *mi_eval_scalar(&paras->shiny);

            mi_query(miQ_NUM_GLOBAL_LIGHTS, state, 0, &n);
            mi_query(miQ_GLOBAL_LIGHTS,     state, 0, &lights);

            for (i=0; i < n; i++) {
                sum.r = sum.g = sum.b = 0;
                samples = 0;
                while (mi_sample_light(&color, &dir, &dot_nl,
                                       state, lights[i],
                                       &samples)) {

                    spec_f = mi_phong_specular(shiny, state,
                                                          &dir);
                    sum.r += color.r * (dot_nl * diffuse->r +
                                        spec_f * specular->r);
                    sum.g += color.g * (dot_nl * diffuse->g +
                                        spec_f * specular->g);
                    sum.b += color.b * (dot_nl * diffuse->b +
                                        spec_f * specular->b);
                }
                if (samples) {
                    result->r += sum.r / samples;
                    result->g += sum.g / samples;
                    result->b += sum.b / samples;
                }
            }
            result->a = 1;
            return(miTRUE);
         }

Note that mi_eval_color is called early to obtain pointers to parameters once. If there is a large number of lights, this saves a large number of mi_eval_color calls later. Although mi_eval et al. employ caching and fast lookups, this makes the shader faster than it were if the code were written like this:

        dot_nl * mi_eval_color(&paras->diffuse)->r

in the inner loop.

Next: 3.7 Shader Versioning Up: 3. Using and Writing Previous: 3.5 Shader Parameter Declarations