Writing the Shader: An Overview

Under Softimage, a native shader is made up of three essential pieces:

• The shader SPDL (which you use to generate a .preset file).

• A C structure defining the input parameters to the shader.

• The shader C implementation.

This section describes these components and the relationship among them.

To describe these components, the Wireframe example shader is used. You can find the example shader here:

    %XSI_HOME%\XSISDK\examples\shader

For information on running the shader in Softimage, see Using the Shader Example.

This section contains the following topics:

• Basic Shader Definition

• Parameter Layout

• Shader Callbacks

• Shader Implementation

• Using the Shader Example

Basic Shader Definition

The Show_edges shader highlights the edges of the triangulated object. In this shader, the edge of each triangle resulting from mental ray’s tesselation of the object is highlighted using a user-specified color. Below is an example result, where the insides of triangles are computed using sib_lambert (giving the shaded impression), and the edges are rendered constant white.

The shader takes three parameters:

Parameter | Type | Description
thickness | scalar | thickness of the edges to draw
edgecolor | color | color to use for the edgesatl_splatter.spdl
incolor | color | color to use for the interior of the triangles

Parameter Layout

The shader SPDL is the method used for communicating to the raytracer how to interact with the shader: the name of the shader, the output type of the shader, the input parameters the shader is expecting, and their order.

It is very important that the shader implementation accesses the input parameters in precisely the same order as specified in the SPDL, or undefined failures including crashing can occur. The raytracer does not ensure the shader evaluates parameters correctly. Note that the Shader Wizard (see Using the Shader Wizard) takes care of this detail for you.

To access parameters, a C structure must be created with each parameter of the shader, containing all the input parameters in their proper order. For example, consider the following SPDL (only relevant portions shown) that defines the shader show_edges with its three input parameters:

    SPDL
    Version = "2.0.0.0";
    // Parameter definitions for the shader show_edges
    Reference = "{53581D20-1945-11D5-AE9B-00A0C96E63E1}";
    PropertySet "show_edges_params"
    {
       // Output parameter of the shader, of type color
       Parameter "out" output
       {
           title = "Output";
           guid = "{4C6879FF-7EC8-11D0-8E3B-00A0C90640EC}";
           type = color;
           flags  = 0;
       }
       // Thickness of the edges.
       Parameter "thickness" input
       {
           title = "Edge Thickness";
           guid = "{3B00E0E1-0F1E-11D5-AE9A-00A0C96E63E1}";
           type = scalar;
       }
       // Color to use for the edges of the triangles.
       Parameter "edgecolor" input
       {
           title = "Edge Color";
           guid = "{3B00E0E2-0F1E-11D5-AE9A-00A0C96E63E1}";
           type = color;
       }
       // Color to use for the interior of the triangles.
       Parameter "incolor" input
       {
           title = "Interior Color";
           guid = "{3B00E0E3-0F1E-11D5-AE9A-00A0C96E63E1}";
           type = color;
       }
    }
    ...

In the implementation, the following structure is used to describe the input parameters:

    typedef struct {
       miScalar m_thickness; /* Thickness of the edges. */
       miColor m_edgecolor; /* Color to use for the edges of the triangles. */
       miColor m_incolor; /* Color to use for the interior of the triangles.*/
    } show_edges_params;

This structure will be used to retrieve the input parameters of the shader, which is described in Shader Callbacks.

Notice that the output parameter is not included in this structure; the raytracer uses an alternate mechanism for communicating this data, which is also described in in Shader Callbacks.

For almost all the parameter types, there is a one-to-one correspondence between the SPDL parameter and the member of the implementation structure. For example, the parameter incolor of type color has an equivalent member incolor of type miColor in the structure. The notable exception to this one-to-one correspondence is the array type, which requires three separate parameters in the implementation structure.

Shader Callbacks

To run a shader, the raytracer calls one or more of four functions from the shader .dll or .dso, based on the Name specified in the MetaShader > Renderer section of the SPDL:

    MetaShader "show_edges"
    {
       Name = "show_edges_declare";
       Type = material, texture
       Renderer "mental ray"
       {
           Name = "show_edges";
           Options
           {
              "version" = 1;
           }
       }
    }

The four functions that may be called are:

• show_edges

• show_edges_version

• show_edges_init

• show_edges_exit

All but show_edges are optional. For a description of the _init and _exit functions, please refer to Programming mental ray.

The next section focuses on two of the four entry points:

• show_edges Callback

• show_edges_version Callback

show_edges Callback

The function show_edges has the form:

    DLLEXPORT miBoolean show_edges(miColor *out_pResult, miState *state, show_edges_params *in_pParams);

This function is the main callback for the shader, called every time the raytracer needs the shader to be evaluated.

• The first parameter, out_pResult, is used to return the result of the shader.

• The second parameter, state, is the current state of the raytracer when the shader is called.

• The final parameter, in_pParams, will be used to access the input parameters.

| There are several important points to note about the function prototype show_edges: • The case of the function name is relevant—it must be the same as in the SPDL (show_Edges or Show_Edges would not work). • The return type is always miBoolean. • The first parameter is a pointer to the same type as the type of the output specified in the SPDL. • The second parameter is always of type miState * (and should always be called state, as will be seen later). • The third parameter, in_pParams, is always a pointer to the type of your parameter structure. • The function definition is prefixed with DLLEXPORT, to ensure code outside of the shader DLL can access the shader.

It is necessary define the function using this form, or the raytracer will not be able to find the shader.

show_edges_version Callback

The version function is called once: the first time a shader is used. It is used by the raytracer to ensure that the shader defined in the SPDL and the shader implemented in the code match. If they do not match, it may indicate that there has been an installation problem, where the two files have become out of date.

In the case where there is a mismatch between the SPDL and the shader implementation, a warning is printed in the Softimage history log, and rendering continues. The raytracer will still try to use the shader.

The shader version number is specified in the SPDL in the MetaShader section:

           Name = "show_edges";
           Options
           {
              "version" = 1;
           }

As with the show_edges function, it is very important that the show_edges_version function has the following prototype:

    DLLEXPORT int show_edges_version(void) { return 1; }

Calbacks in C++

For C++ development, all shader callbacks (including show_edges_init, show_edges_exit , etc.) must be wrapped in an extern “C”construct to ensure that the entry point is found by the raytracer:

    extern "C" {
       DLLEXPORT miBoolean show_edges ( miColor *out_pResult,
                  miState *state,
                  show_edges_params *in_pParams );
    }

Shader Implementation

Once the shell of the shader has been created, it’s time to write the code that computes the shader result.

In general, a shader implementation evaluates the input parameters, computes a result, and returns the result in *out_pResult. The shader then returns miTRUE to signal to the raytracer that the execution succeeded, miFALSE otherwise.

Evaluating Parameters and the Render Tree

Most shaders require inputs to determine what the final result will be. To do this, the shader can use one of the macros available from mental images (in the file shader.h), depending on the type of the parameter. The following are the most commonly used of the macros:

    miColor *mi_eval_color(miColor *in_pParameter)
    miScalar *mi_eval_scalar(miScalar *in_pParameter)
    miBoolean *mi_eval_boolean(miBoolean *in_pParameter)
    miVector *mi_eval_vector(miVector *in_pParameter)

All the mi_eval_... macros expand to a call to the function mi_eval, with one parameter being the current miState, which is passed in to the shader as the second parameter. These macros assume that the variable containing the pointer to the state is called state, therefore it is important to use this variable name when declaring the shader.

To evaluate a parameter, the shader simply needs to call the appropriate mi_eval_... with the address of the parameter to be evaluated. For example:

    #include <shader.h>
       ...
       miScalar l_thickness = *mi_eval_scalar( &(in_pParams->m_thickness) );
       miColor l_incolor = *mi_eval_color( &(in_pParams->m_incolor) );
       miColor l_edgecolor = *mi_eval_color( &(in_pParams->m_edgecolor) );
       ...

It is very important, when writing a shader, for Softimage to use mi_eval, directly or through the above macros, to evaluate the input parameters. The reason is due to the fact that in Softimage shaders can be connected to each other in the render tree. The function mi_eval automatically determines if such a connection is made, and recursively evaluates the attached shader transparently to the calling shader. If there is no shader attached, the constant value the user specified in the shader PPG is returned instead.

In the case of the show_edges shader, by using mi_eval, this implies that the color of the edges can be controlled by another shader, permitting the user to perform such effects as having the edge color controlled by a fractal pattern, for example.

Since a call to mi_eval may result in calling other shaders, who could in turn call other shaders, calls to mi_eval should only be done if necessary. For example, in the show_edges function, the shader uses the function OnEdge to determine if the point is on the edge of the triangle. If it is, it uses mi_eval to get the edge color edgecolor. Otherwise, it uses mi_eval to get the interior color incolor.

    DLLEXPORT miBoolean show_edges ( miColor *out_pResult,
       miState *state,
       show_edges_params *in_pParams)
    {
       ...
       if (OnEdge(&(state->point), P, l_thickness))
       {
           /* Point is on the edge of the triangle */
           /* Evaluate the edge color and store it in the result*/
            * out_pResult = *mi_eval_color( &(in_pParams->m_edgecolor) );
       }
       else
       {
           /* Point is on the interior of the triangle */
           /* Evaluate the interior color and store it in the result*/
            * out_pResult = *mi_eval_color( &(in_pParams->m_incolor) );
       }
       return miTRUE;
    }

Below is the full listing of the C implementation of the show_edges shader. The shader uses two helper functions.

• The first, GetVertices, gets the vertices of the triangle that the ray hit. The function converts the point from object or camera space to internal space using mi_point_from_object or mi_point_from_camera, respectively.

• The second function, OnEdge, computes the distance from the input point to the edges of the triangles. As soon as it determines that it is close enough to an edge, it returns miTRUE. If it is not close enough to any of the edges, it returns miFALSE.

    #include <shader.h>

    /* -------------------------------------------------------------- */
    /* Define the parameter structure so that it matches the order in */
    /* the SPDL precisely. */
    typedef struct {
       miScalar m_thickness; /* Thickness of the edges. */
       miColor  m_edgecolor; /* Color to use for the edges of the triangles.*/
       miColor  m_incolor;   /* Color to use for the interior of the triangles.*/
    } show_edges_params;

    /* Two Helper functions for the show_edges shader*/
    static miBoolean GetVertices(miState *state, miVector *P);
    static miBoolean OnEdge(miVector *Q, miVector *P, miScalar in_thickness);

    /* -------------------------------------------------------------- */
    /* Version number function for the show_edges shader              */
    DLLEXPORT int show_edges_version(void) { return 1; }

    /* -------------------------------------------------------------- */
    /* Main entry point for the show edges shader.                    */
       DLLEXPORT miBoolean show_edges ( miColor           *out_pResult,
                                        miState           *state,
                                        show_edges_params *in_pParams)
    {
       /* Get the thickness of the lines to draw */
       miScalar l_thickness = *mi_eval_scalar( &(in_pParams->m_thickness) );
       miVector P[3];

       /* Get the points of the triangle the ray intersected */
       if (!GetVertices(state, P)) return miFALSE;

       if (OnEdge(&(state->point), P, l_thickness))
       {
           /* Point is on the edge of the triangle */
           /* Evaluate the edge color and store it in the result*/
            * out_pResult = *mi_eval_color( &(in_pParams->m_edgecolor) );
       }
       else
       {
           /* Point is on the interior of the triangle */
           /* Evaluate the interior color and store it in the result*/
            * out_pResult = *mi_eval_color( &(in_pParams->m_incolor) );
       }
       return miTRUE;

    /* -------------------------------------------------------------- */
    /* Function to get the vertices of the triangle in internal space */
    static miBoolean GetVertices(miState *state, miVector *P)
    {
       miVector *verts[3];
       int i;

       if (!mi_tri_vectors(state, 'p', 0, &verts[0], &verts[1], &verts[2] ))
       {
           /* Problem... */
           return miFALSE;
       }

       switch (state->options->render_space)

    {
       case 'o':
           {  /* points are in object space. */
              for (i=0; i<3; i++) mi_point_from_object(state, &P[i], verts[i]);
           } break;
       case 'c':
           {  /* points are in camera space. */
              for (i=0; i<3; i++) mi_point_from_camera(state, &P[i], verts[i]);
           } break;
       default:
           {
              /* mixed space?  Not handled. */
              return miFALSE;
           } break;
    }

       /* Success */
       return miTRUE;

    /* ----------------------------------------------------------------- */
    /* Function to determine if the intersection point Q is in_thickness */
    /* units away from the edges of the triangle P */
    static miBoolean OnEdge(miVector *Q, miVector *P, miScalar in_thickness)

    {
       int i;
       miScalar l_thicksq = in_thickness*in_thickness;

       /* Check against all three edges */
       for (i=0; i<3; i++)
       {
           miVector l_vPiQ, l_vEdge;
           miScalar l_sDotprod, l_sDistSq, l_vEdgeLen;

           /* Project the vector P[i]Q onto the edge P[i]P[(i+1)%3]. */
           mi_vector_sub(&l_vPiQ, Q, &P[i]);
           mi_vector_sub(&l_vEdge, &P[(i+1)%3], &P[i]);

           l_vEdgeLen = mi_vector_norm(&l_vEdge);
           if (l_vEdgeLen < miGEO_SCALAR_EPSILON)
           {
                  /* Degenerate triangle—trivially near the edge. */
                  return miTRUE;
           }

           l_sDotprod = mi_vector_dot(&l_vPiQ,&l_vEdge)/l_vEdgeLen;

           /* We now know two sides of the right angle triangle: */
           /* the base (l_sDotProd) and the hypotenuse (the length of l_vPiQ).*/
           /* Compute the last side, which is the distance of Q from the edge,*/
           /* using Pythagorus: a^2 + b^2 = h^2. */
           /* We use mi_vector_dot to compute the length of l_vPiQ, since we */
           /* want the square of the length anyways */
           l_sDistSq = mi_vector_dot(&l_vPiQ, &l_vPiQ) - l_sDotprod*l_sDotprod;

           /* We don't bother finding the true distance, since this involves a*/
           /* square root. Instead, we compare against the square of the */
           /* desired thickness */
           if (l_sDistSq <= l_thicksq)
           {
              /* This point is close enough to the edge */
              return miTRUE;
           }
       }

       /* We aren't close to any of the edges—we're on the inside */
       return miFALSE;
    }

Using the Shader Example

To use the show edges example in Softimage

1. Connect to the SDK examples examples workgroup .

2. Create a sphere (Get > Primitive > Surface > Sphere).

3. From Render, choose Get > Shader > Surface > More.

The Get Preset or Image browser appears. and waits for you to select a shader preset.

4. Locate the Softimage SDK examples workgroup (%XSISDK_ROOT%\examples\workgroup), and browse to Addons > ShowEdges > Data > DSPresets > Shaders > Texture to find show_edges. The shader will attach to the surface port of the material.

If the Softimage SDK example workgroup is the first workgroup in the list, then in the Get Preset dialog box you can choose Paths > Workgroup Add-ons to go to the root folder of the examples workgroup.

5. Create a render region in the viewer, such that the sphere will be rendered. The edges of the triangles should be highlighted in white. The lines may be jagged at the default render settings; increasing the sampling levels will improve the quality.

If you open the the shader’s property page, you can change the thickness, edgecolor, and/or interior color. As well, you can do more interesting effects by connecting a more complicated shader to the parameters. For example, an image node can be attached to the incolor parameter. As a result, the interiors of the triangles will be rendered with an image.

Autodesk Softimage v7.5