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(¶s->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(¶s->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(¶s->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, ¶s->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.