Keywords: main

Next: 3.18 Displacement Shaders Up: 3. Using and Writing Previous: 3.16 Lens Shaders
Subsections

- 3.17.1 Parallel Output Shaders

# 3.17 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:

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

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

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

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

            near     = *mi_eval_scalar(&paras->near);
            far      = *mi_eval_scalar(&paras->far);
            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 >= far || depth == 0.0)
                           color.r=color.g=color.b=color.a = 0;
                      else if (depth > near) {
                           fade = (far - depth) / (far - near);
                           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.

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, anmd 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.17.1 Parallel Output Shaders

Output shaders can be parallelized. If the declaration includes the `parallel` flag2.1, mental ray 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 state - > shader - > user.p 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              near, far;
            miImg_image           *fb_color, *fb_depth;

            near     = *mi_eval_scalar(&paras->near);
            far      = *mi_eval_scalar(&paras->far);
            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:

         code "out_depthfade.c"
         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.

Next: 3.18 Displacement Shaders Up: 3. Using and Writing Previous: 3.16 Lens Shaders