next up previous contents
Next: 3.19 Displacement Shaders Up: 3. Using and Writing Previous: 3.17 Lens Shaders

Subsections

3.18 Output Shaders

 Output shaders are functions that are run after rendering has finished. They modify the resulting image or images. Typical uses are output filters and compositing operations. Output shaders can directly access all rendered  frame buffers and make modifications to these frame buffers, but they do not return a result like other shaders do. They still get a result pointer as their first argument, for symmetry with the other types of shaders, but this pointer should not be used.

Until version 2.0.20 of mental ray, output shaders had a different prototype (there was no result pointer argument) and a different state called miOutstate, which contained frame buffers but very few other state variables, so output shaders could not use any shader interface function that required an argument of type miState. For backwards compatibility, these shaders are still supported, but not recommended and not described in this book. mental ray distinguishes old-style output shaders by a null or missing version number in the declaration. Old-style output shaders did not support  shader initialization and cleanup and the  mi_eval family of functions; new ones do.

It is important to specify a non-null  shader version number in the declaration of output shaders! If none is specified, mental ray issues a warning and calls the output shader with an miOutstate, which will probably crash it.

All  output shaders must be declared like any other type of shader, and the same types of arguments can be declared. This includes textures and lights. Nonprocedural textures can be looked up using functions like  mi_lookup_color_texture and  mi_query. Lights can also be looked up with  mi_query. Since rendering has completed, it is not possible to look up procedural textures or to use tracing functions such as mi_sample_light.

Here is a simple output shader that depth-fades the rendered image towards total transparency:

    struct out_depthfade {
        miScalar   tnear;   /* no fade closer than this */
        miScalar   tfar;    /* farther objects disappear */
    };

    miBoolean out_depthfade(
        void                  *result,
        register miState      *state,
        struct out_depthfade  *paras)
    {
        register int          x, y;
        miColor               color;
        miScalar              depth, fade;
        miScalar              tnear, tfar;
        miImg_image           *fb_color, *fb_depth;

        tnear    = *mi_eval_scalar(&paras->tnear);
        tfar     = *mi_eval_scalar(&paras->tfar);
        fb_color = state->options->image[miRC_IMAGE_RGBA].p;
        fb_depth = state->options->image[miRC_IMAGE_Z].p;

        for (y=0; y < state->camera->y_resolution; y++) {
                if (mi_par_aborted())
                        break;
                for (x=0; x<state->camera->x_resolution; x++) {
                        mi_img_get_color(fb_color, &color, x, y);
                        mi_img_get_depth(fb_depth, &depth, x, y);

                        if (depth >= tfar || depth == 0.0)
                                color.r=color.g=color.b=color.a = 0;
                        else if (depth > tnear) {
                                fade = (tfar - depth) / (tfar - tnear);
                                color.r *= fade;
                                color.g *= fade;
                                color.b *= fade;
                                color.a *= fade;
                        }
                        mi_img_put_color(fb_color, &color, x, y);
                }
        }
        return(miTRUE);
    }

Note the call to  mi_par_aborted, which stops the shader if the user has instructed mental ray to stop whatever it is doing. This call was inserted in the line loop because it should be called periodically, but not too often to avoid slowing it down. Also, note that the parameters are named tnear and tfar instead of near and far because some older early-1980's compilers reserve these as keywords.

This shader is stored in a file out_depthfade.c and installed in the .mi file with a code statement and a  declaration:

     code "out_depthfade.c"
     declare shader
         "out_depthfade" (scalar "near", scalar "far")
         version 1
     end declare

It is actually more efficient to precompile the shader, store it in a DSO file, and use the link statement instead of code. See the beginning of this chapter (page [*]) for details. The declaration should appear before the first reference to the shader in a camera statement. Note the non-null version statement. The shader is referenced in an output statement in the camera:

     camera "cam"
         output "rgba,z" "out_depthfade"
                              ("near" 10.0, "far" 100.0)
         output "rgb" "filename.rgb"
         samples 0 1
         ...
     end camera

Note that the output shader statement appears before the output file statement. The output shader must get a chance to change the output image before it is written to the file filename.rgb. It is possible to insert another file output statement before the output shader statement; in this case two files would be written, one with and one without depth fading.

Note also that the output shader has a type string "rgba,z". This string tells mental ray to render both an RGBA and a Z (depth) frame buffer. The RGBA buffer would have been rendered anyway because the file output statement requires it, but the depth buffer would not have been rendered without the z in the type string. In this case, all depth values returned by  mi_img_get_depth would be 0.0.

 By default, depth buffers are not interpolated; instead the min depth within the pixel is used. If the depths should be interpolated, use the output type "rgba,+z". Refer to the Functionality chapter for more information on frame buffer interpolation.

This shader does not anti-alias very well because there is only one depth value per pixel. The shader makes pixels for which a depth of 0.0 is returned totally transparent to fade edges of objects correctly that have no other object behind them. By definition,  mi_img_get_depth returns 0.0 for a position x, y if no object was hit at that pixel.

3.18.1 Parallel Output Shaders2.1

Output shaders can be parallelized. If the declaration includes the parallel flag2.13.2, mental ray 2.1 will call the output shader multiple times with small portions of the frame buffer. The calls are distributed over all local threads. This can improve the performance significantly if the local machine has more than one CPU, but requires special attention to multithreading issues in the shader.

mental ray splits the frame buffer into blocks of scanlines. Each call to the output shader is expected to operate on one such block. It can still read the entire frame buffer, but it is expected to write only the assigned scanlines. The call order is unpredictable. The shader writer must make sure that reading does not read lines that have already been written, or are currently being written, by another thread.

Basically, if the output shader reads lines outside its assigned block, it must use an init shader to make a copy of the frame buffer, and an exit shader to delete the copy when it has finished. Typically, the copy of the frame buffer is attached to the shader user pointer (obtainable with the miQ_FUNC_USERPTR mode of  mi_query) in the instance init shader. Special care must be taken to delete allocated pointers in case multiple output statements reference the same named shader, to avoid a memory leak.

The scanline block to operate on is defined by mental ray in the state variables raster_y, which defines the first scanline to write, and dist, which contains the number of scanlines. If dist is 0, this shader is not called in parallel mode; this must be checked to make the shader compatible with older mental ray versions that do not support parallel output shaders.

Here is the previous example shader again, with support for parallelism:

    miBoolean out_depthfade(
        void                  *result,
        register miState      *state,
        struct out_depthfade  *paras)
    {
        register int          x, y, ye;
        miColor               color;
        miScalar              depth, fade;
        miScalar              tnear, tfar;
        miImg_image           *fb_color, *fb_depth;

        tnear    = *mi_eval_scalar(&paras->tnear);
        tfar     = *mi_eval_scalar(&paras->tfar);
        fb_color = state->options->image[miRC_IMAGE_RGBA].p;
        fb_depth = state->options->image[miRC_IMAGE_Z].p;

        if (state->dist > 0) {
                y  = state->raster_y;    /* parallel mode */
                ye = state->dist + y;
        } else {
                y  = 0;                  /* sequential mode */
                ye = state->camera->y_resolution;
        }
        for (; y < ye; y++)
                ;/* same as before */

        return(miTRUE);
    }

The declaration can now be changed to include the parallel keyword:

     declare shader
         "out_depthfade" (scalar "near", scalar "far")
         parallel
         version 1
     end declare

Note that the version number does not have to be changed -- it refers to the shader interface, its return type and parameters, but not its implementation. Never specify the parallel flag in declarations of shaders that do not actually support parallelism because each call to the shader would not just write its assigned scanline block but the entire image.



Footnotes

...2.13.2
mental ray 3.x does not support parallel output shaders.

next up previous contents
Next: 3.19 Displacement Shaders Up: 3. Using and Writing Previous: 3.17 Lens Shaders
Copyright 2002 by mental images