next up previous contents
Next: 3.10 Volume Shaders Up: 3. Using and Writing Previous: 3.8 Material Shaders

3.9 Texture Shaders

    Texture shaders evaluate a texture and typically return a color, scalar, or vector (but like any shader, return values can be freely chosen and may even be structures of multiple values). Textures can either be procedural, for example evaluating a 3D texture based on noise functions or calling other shaders, or they can do an image lookup. The .mi format provides different texture statements for these two types, one with a function call and one with a texture file name. Refer to section [*] for details.

 Texture shaders are not first-class shaders. This means that mental ray never calls one by itself and provides no special support for them. Texture shaders are called exclusively by other shaders. There are four ways of calling a texture shader from a material shader or other shaders: either by simply calling the shader by name like any other C function, or by requesting the value of a shader parameter using  mi_eval (the recommended method, see below), or by using a built-in convenience function like  mi_lookup_color_texture, or with a statement like

     mi_call_shader_x(result, miSHADER_TEXTURE, state, tag, args);

The tag argument references the texture function. The texture function is a data structure in the scene database that contains a reference to the C function itself, plus a pointer to a user argument block arg that is passed to the texture shader when it is called. User arguments are rarely used, and  mi_call_shader_x is the only way to pass them to a shader. Although the texture shader could also be called directly with a statement such as

     soft_color(result, state, &soft_color_paras);

the caller would have to write the required arguments into the user argument structure soft_color_paras itself; it would not have access to shader parameters specified in the .mi file. The recommended way of calling subshaders is implicitly with  mi_eval, which does not require the calling shader to be aware that a subshader provides the value. This makes shaders very flexible by allowing them to be combined in arbitrary ways from the scene file, without changing and recompiling shader sources. However, there are cases when not the value of a shader but a shader itself is passed as a parameter, and  mi_call_shader_x provides a good way of calling such shaders.

Unlike material shaders, texture shaders usually return a simple color or scalar or other return value. There are no lighting calculations or secondary rays. This greatly simplifies the task of changing a textured surface. For example, a simple texture shader that does a simple, non-antialiased lookup in a texture image could be written as:

 

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

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

     miBoolean mytexture(
        register miColor    *result,
        register miState    *state,
        struct image_lookup *paras)
     {
        miImg_image         *image;
        int                 xs, ys;
        miTag               texture;

        texture = *mi_eval_tag(&paras->texture);
        image = mi_db_access(texture);
        mi_query(miQ_IMAGE_WIDTH,  state, texture, &xs);
        mi_query(miQ_IMAGE_HEIGHT, state, texture, &ys);
        mi_img_get_color(image, result, state->tex.x * xs,
                                        state->tex.y * ys);
        mi_db_unpin(texture);
        return(miTRUE);
     }

This shader assumes that the texture coordinate can be taken from state- >tex, where the caller (usually a material shader) has stored it, probably by selecting a texture coordinate from state- >tex_list. The caller can use  mi_query to determine the number of valid textures in state- >tex_list. A more complicated shader that better anti-aliases  image textures with a simple box filter might look like this:

 

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

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

     miBoolean mytexture2(
        register miColor    *result,
        register miState    *state,
        struct image_lookup *paras)
     {
        miTag               texture;
        miImg_image         *image;
        int                 xs, ys;
        miColor             col00, col01, col10, col11;
        register int        x, y;
        register miScalar   u, v, nu, nv;

        texture = *mi_eval_tag(&paras->texture);
        image = mi_db_access(texture);
        mi_query(miQ_IMAGE_WIDTH,  state, texture, &xs);
        mi_query(miQ_IMAGE_HEIGHT, state, texture, &ys);
        x = u = state->tex.x * (xs - 1);
        y = v = state->tex.y * (ys - 1);
        u -= x;
        v -= y;
        nu = 1 - u;
        nv = 1 - v;

        mi_img_get_color(image, &col00, x,   y);
        mi_img_get_color(image, &col01, x+1, y);
        mi_img_get_color(image, &col10, x,   y+1);
        mi_img_get_color(image, &col11, x+1, y+1);

        result->r = nv * (nu * col00.r + u * col01.r) +
                     v * (nu * col10.r + u * col11.r);
        result->g = nv * (nu * col00.g + u * col01.g) +
                     v * (nu * col10.g + u * col11.g);
        result->b = nv * (nu * col00.b + u * col01.b) +
                     v * (nu * col10.b + u * col11.b);
        result->a = nv * (nu * col00.a + u * col01.a) +
                     v * (nu * col10.a + u * col11.a);

        mi_db_unpin(texture);
        return(miTRUE);
     }

The implementation of the body of this shader is similar to the built-in  mi_lookup_color_texture function if called with paras- >texture, except that this function also recognizes if the texture is a shader (and calls  mi_call_shader in this case), and unlike the example above it supports filtered multi-level  pyramid texture filtering. Also, both examples above will fail if any texture coordinate is less than 0 or equal to or greater than 1.

This shader can further be extended by applying texture transformations to state- >tex before it is used for the lookup, for example for rotated, scaled, repeating, or cropped textures. The shader may also decide that a scaled-down texture was missed, and return miFALSE. The material shader must then skip this texture if  mi_call_shader returns miFALSE.

Both of the above shaders have shader parameters that consist of a single texture. Textures always have type miTag. Image file textures are read in by the translator and provided as a tag of type miSCENE_IMAGE.

A texture shader using higher quality elliptical filtering might look like this:

  

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

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

     miBoolean mytexture3(
        register miColor    *result,
        register miState    *state,
        struct image_lookup *paras)
     {
        miTag               texture;
        miVector            p[3], t[3];
        miMatrix            ST;

        texture = *mi_eval_tag(&paras->texture);

        mi_texture_filter_project(p, t, state,
                                  0.5, paras->space);

        my_remap_parameter(&t[0], &t[0], state, paras);
        my_remap_parameter(&t[1], &t[1], state, paras);
        my_remap_parameter(&t[2], &t[2], state, paras);

        mi_texture_filter_transform(ST, p, t);

        return(mi_lookup_filter_color_texture(result, state,
                            texture,
                            &paras->filter_options, ST));
     }

This example assumes that paras contains the definition of the  elliptical filter options and the texture space index. The implementation of the function my_remap_parameter is not given here, it would transform the texture coordinates according to some parameters in paras, for example perform texture replication, rotation or valid range checking. Note that the checking for miFALSE return values of mi_texture_filter_project and mi_texture_filter_transform is missing in this example.


next up previous contents
Next: 3.10 Volume Shaders Up: 3. Using and Writing Previous: 3.8 Material Shaders
Copyright 2000 by mental images