Keywords: main

Next: 3.9 Texture Shaders Up: 3. Using and Writing Previous: 3.7 Shader Versioning

# 3.8 Material Shaders

Material shaders are the primary type of shaders. All materials defined in the scene must at least define a material shader. Materials may also define other types of shaders, such as shadow, volume, and environment shaders, which are optional and of secondary importance.

When mental ray casts a visible ray, such as those cast by the camera (called primary rays) or those that are cast for reflections and refractions (collectively called secondary rays), mental ray determines the next object in the scene that is hit by that ray. This process is called intersection testing. For example, when a primary ray cast from the camera through the viewing plane's pixel (100, 100) intersects with a yellow sphere, pixel (100, 100) in the output image will be painted yellow. (The actual process is slightly complicated by supersampling and filtering, which can cause more than one primary ray to contribute to a pixel.)

The core of mental ray has no concept of ``yellow.'' This color is computed by the material shader attached to the sphere that was hit by the ray. mental ray records general information about the sphere object, such as point of intersection, normal vector, transformation matrix etc. in a data structure called the state, and calls the material shader attached to the object. More precisely, the material shader, along with its parameters (called shader parameters), is part of the material, which is attached to or inherited by the polygon or surface that forms the part of the object that was hit by the ray. Objects are usually built from multiple polygons and/or surfaces, each of which may have a different material.

The material shader uses the values provided by mental ray in the state and the variables provided by the .mi file in the shader parameters to calculate the color of the object, and returns that color. In the above example, the material shader would return the color yellow. mental ray stores this color in its internal sample list, which later gets filtered to compute frame buffer pixels, and then casts the next primary ray. Note that if the material shader has a bug that causes it to return infinity or NaN (Not a Number) in the `result` color, the infinity or NaN is stored as 1.0 in color frame buffers. This results in white pixels in the rendered image. This is true for subshaders such as texture shaders also.

With an appropriate `output` statement (see page ), mental ray computes depth, label, normal-vector, and motion vector frame buffers in addition to the standard color frame buffer, and any user frame buffer defined with `frame buffer` statements in the options block. The color returned by the first-generation material shader is stored in the color frame buffer (unless a lens shader exists; lens shaders also have the option of modifying colors). The material shader can control what gets stored in the depth, label, normal-vector, and motion-vector frame buffers by storing appropriate values into state - > point.z, state - > label, state - > normal, and state - > motion, respectively. It can also store data in the user frame buffers with an appropriate call to mi_fb_put. Depth is the negative Z coordinate.

Material shaders normally do quite complicated computations to arrive at the final color of a point on the object:

- The shader parameters usually include constant ambient, diffuse, and specular colors and other parameters such as transparency, and possibly optional textures that need to be evaluated to compute the actual values at the intersection point. If textures are present, texture shaders are called by using one of the lookup functions provided by mental ray. Alternatively, shader assignment may be used for texturing. Texture shaders are discussed in the next section.

- The illumination computation sums up the contribution from various light sources listed in the shader parameters. To obtain the amount of light arriving from a light source, a light shader is called by calling a light trace or sample function provided by mental ray. Light shaders are discussed in a separate section below. After the illumination computation is finished, the ambient, diffuse, and specular colors have been combined into a single material color (assuming a more conventional material shader).

- If the material is reflective, transparent, or using refraction, as indicated by appropriate shader parameters, the shader must cast secondary rays and apply the result to the material color calculated in the previous step. (Transparency is a variation of refractive transparency where the ray continues in the same direction, while refraction rays may alter the direction based on an index of refraction.) Secondary rays, like primary rays, cause mental ray to do intersection testing and call another material shader if the intersection test hit an object. For this reason, material shaders must be reentrant. In particular, a secondary refraction or transparency ray will hit the back side of the same object if `face both` is set in the camera and the object is a closed volume.

Note that the shader parameters of a material shader are under no obligation to define and use classical parameters like ambient, diffuse, and specular color and reflection and refraction parameters. Here is a full-featured example for the C source of the shader declared in the previous section:

         #include <stdio.h>
         #include <mi/shader.h>

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

         miBoolean my_material(
            miColor            *result,
            miState            *state,
            struct my_material *paras)
         {
            miColor            *diffuse, *specular;
            miVector           bump, dir;
            miColor            color;
            int                num, i_lights, n_lights;
            miTag              *lights;
            miScalar           factor;

            /*
             * bump map
             */
            state->tex = state->tex_list[0];
            mi_call_shader((miColor *)&bump, miSHADER_TEXTURE,
                           state, *mi_eval_tag(&paras->bump));

            if (bump.x != 0 || bump.y != 0) {
                mi_vector_to_object(state, &state->normal,
                                    &state->normal);
                state->normal.x+=bump.x * state->bump_x_list->x
                                +bump.y * state->bump_y_list->x;
                state->normal.y+=bump.x * state->bump_x_list->y
                                +bump.y * state->bump_y_list->y;
                state->normal.z+=bump.x * state->bump_x_list->z
                                +bump.y * state->bump_y_list->z;
                mi_vector_from_object(state, &state->normal,
                                      &state->normal);
                mi_vector_normalize(&state->normal);
                state->dot_nd = mi_vector_dot(&state->normal,
                                              &state->dir);
            }

            /*
             * illumination
             */
            *result  = *mi_eval_color  (&paras->ambient);
            diffuse  =  mi_eval_color  (&paras->diffuse);
            specular =  mi_eval_color  (&paras->specular);
            i_lights = *mi_eval_integer(&paras->i_lights);
            n_lights = *mi_eval_integer(&paras->n_lights);
            lights   =  mi_eval_tag    ( paras->lights);

            for (num=0; num < n_lights; num++) {
                miColor    color, sum;
                miInteger  samples = 0;
                miScalar   dot_nl;

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

                     sum.r += dot_nl * diffuse->r * color.r;
                     sum.g += dot_nl * diffuse->g * color.g;
                     sum.b += dot_nl * diffuse->b * color.b;

                     factor = mi_phong_specular(
                                 *mi_eval_scalar(&paras->shiny),
                                 state, &dir);
                     sum.r += factor * specular->r * color.r;
                     sum.g += factor * specular->g * color.g;
                     sum.b += factor * specular->b * color.b;
                }
                if (samples) {
                     result->r += sum.r / samples;
                     result->g += sum.g / samples;
                     result->b += sum.b / samples;
                }
            }
            result->a = 1;

            /*
             * reflections
             */
            factor = *mi_eval_scalar(&paras->reflect);
            if (factor > 0) {
                miScalar f = 1 - factor;
                result->r *= f;
                result->g *= f;
                result->b *= f;

                mi_reflection_dir(&dir, state);
                if (mi_trace_reflection (&color,state,&dir) ||
                    mi_trace_environment(&color,state,&dir)) {

                     result->r += factor * color.r;
                     result->g += factor * color.g;
                     result->b += factor * color.b;
                }
            }

            /*
             * refractions
             */
            factor = *mi_eval_scalar(&paras->transparency);
            if (factor > 0) {
                miScalar ior = *mi_eval_scalar(&state->ior);
                miScalar f = 1 - factor;
                result->r *= f;
                result->g *= f;
                result->b *= f;
                result->a  = f;

                if (mi_refraction_dir(&dir, state, 1.0, ior)
                 && mi_trace_refraction (&color, state, &dir) ||
                    mi_trace_environment(&color, state, &dir)) {

                     result->r += factor * color.r;
                     result->g += factor * color.g;
                     result->b += factor * color.b;
                     result->a += factor * color.a;
                }
            }
            return(miTRUE);
         }

Four steps are required for computing the material color in this shader. First, the normal is perturbed by looking up a vector in the vector texture, and using the bump basis vectors to determine the orientation of the perturbation (the lookup always returns an XY vector). The second step loops over all light sources in the light array parameter, adding the contribution of each light according to the Phong equation. In the case of area lights, the light is sampled more than once, until the light sampling function is satisfied.

Finally, reflection and refraction rays are cast if the appropriate parameters are nonzero. In both cases, first the direction vector `dir` is computed using a built-in function, and a ray is cast in that direction. If either trace function returns `miFALSE`, indicating that no object was hit, the material's environment map that forms a sphere around the entire scene is evaluated. When all computations are finished, the calculated color, including the alpha component, is returned in the `result` parameter. The shader returns `miTRUE` indicating that the computation succeeded.

The mi_eval functions permit shader assignments, which let shaders base their computations on parameters that are driven by other shaders. For example, the diffuse component of the above example is a good candidate for attachment to a texture shader. This works only if mi_eval is used properly.

Next: 3.9 Texture Shaders Up: 3. Using and Writing Previous: 3.7 Shader Versioning