Chapter 8. Post Image Effects

Image effects, such as Dithering, Image Flare, Global Fog, differ from Particle post effects in that they read and modify channels of an image. For example, Global Fog effect reads the depth channel (also called 'Z' or distance) and blends in desired fog color accordingly.

To plug-in a new post image effect, three classes needs to be implemented:

  1. Rendering Class.

  2. Model Class

  3. View Class

Properties for post images are defined in 'real/code/r3postim.h' class.

This is the object the user creates from the select window's 'New' pop-up menu and drops into the desired post image. If post rendering is activated, the ray tracer renders the scene into the post image rather than into the final output device. The image effects associated with the post image are then called and rendered.

The model class specifies necessary attributes for the effect and handles read, write and other usual model specific stuff.

The only 'post image' specific thing in the model class is the method which sets up the rendering specific object for the renderer.


    static void *r3pefmm_renderbegin(R3CLASS *cl, R3OBJ *obj, R3OBJ *rt, int clid, R3TAG *tags)
    {
        R3IDATA *self = R3CL_IADDR(cl, obj);
        if(!clid) clid = R3CLID_MYIMAGEEFFECT;
        return R3DoSuper3(cl, obj, R3PEFMM_RENDERBEGIN, rt, (void *)clid,
                         MYEFFA_Foo, &self->foo,
                         ....,
                         R3TAG_MORE, tags);
    }

Property Gadget Class

This class implements user interface for the effect so that the user can define effect specific options through the property window.

Post effect gadgets are derived from the 'real/gadget/r3ieffgd.h' base class. Gadget uses standard model-view concept and gets its model in R3WGA_Model attribute.

Rendering Class

Post image rendering classes are derived from 'real/raytr/r3postpr.h' base class.

In the registration function, make sure the renderer supports post processing as well as channels you need.


    if(R3ClassFind(R3CLID_POSTPROCESSOR)) {
        /* register classes we need */
        if(!R3RegisterZChannelClass(apphnd)) return FALSE;
        if(!R3RegisterColorChannelClass(apphnd)) return FALSE;
        r3phase_postprocessing = (int)R3DoClassA(R3CLID_PHASESYSTEM, R3PHSYSCM_INSTALLPHASE, (void *)R3CLID_PHASEPOSTPROCESSING);
        if(r3phase_postprocessing) 
            return FALSE;
    if(R3ClassCreate(R3CLID_POSTPROCESSOR, R3CLID_FOGRENDER, R3Dispatch, sizeof(R3IDATA),
                     R3TAG_END) == NULL)
        return(FALSE);

Fetch also the channel names to a null terminated string pointer array so that you can return the array when the post processor asks for it using the tag R3POSTPRA_NeedsChannels (don't hard code channel names such as Alpha, Color.


    /* fetch channel names to null terminated array */
    R3GetClassAttrs(R3CLID_COLORCHANNEL, R3RCA_Name, &needschannels[0], R3TAG_END);
    R3GetClassAttrs(R3CLID_ZCHANNEL, R3RCA_Name, &needschannels[1], R3TAG_END);
    needschannels[2] = NULL;

The only attribute you have to handle in the R3RM_GET method is the above mentioned 'R3POSTPRA_NeedsChannels'. You don't have to handle your effect specific tags in the get method because nobody really needs them. Your class is the only class which studies them and renders its effect accordingly.

When the ray tracer has got the entire image rendered into the post image, the effects associated with the image will get R3POSTPRM_PROCESS method. In this method, you can modify the image in any way you want.

The method will be called as:


    static void *r3postprm_render(R3CLASS *cl, R3OBJ *obj, R3OBJ *drawport, R3MATRIX modelview, R3TAG *tags)

You can choose to render through the passed draw port or fetch the address of the actual image and manipulate image pixels directly. Fetch the address of the actual ram image by calling:


    R3GetAttrs(drawport, R3DRAWPA_Image, &img, R3TAG_END);

Fetch the image size as follows:


    /* fetch the resolution of the image */
    R3GetAttrs(img,
               R3DTYPEA_Width, &width,
               R3DTYPEA_Height, &height,
               R3TAG_END);

You could use R3DTYPEM_GETROW and R3DTYPEM_SETROW methods to process the image row by row. However, there is some overhead caused by moving data first out off the image and then back into the image. For faster access, fetch the addresses of the desired channels and function pointers which allow fast pixel read/write operation:


    R3DoA(rawimg, R3RAWM_SELECTCHANNELBYNAME, needschannels[0]); /* Color */
    if(!(colhandle = R3SendMsg3(img, R3DTYPEM_GETCHANNELHANDLE, NULL, NULL,
                        R3DTYPEA_ChannelReader, &ColorReader,
                        R3DTYPEA_ChannelWriter, &ColorWriter,
                        R3TAG_END)))
        goto cleanup;

    R3SendMsgA(rawimg, R3RAWM_SELECTCHANNELBYNAME, needschannels[1]); /* Depth */
    if(!(zhandle = R3SendMsg3(img, R3DTYPEM_GETCHANNELHANDLE, NULL, NULL,
                        R3DTYPEA_ChannelReader, &ZReader,
                        R3TAG_END)))
        goto cleanup;

You can then read and write pixels as follows:


    R3FLOAT distance;
    R3FLOATCOLOR3 col;

    for(j=0; j<height; j++) {
        for(i=0; i<width; i++) {
            /* read 'color' and 'z' */
            (*ColorReader)(colhandle, (R3FLOAT *)&col, i, j);
            (*ZReader)(zhandle, &distance, i, j);
            /* modify 'color' based on 'z' etc. */
            ....

            /* write modified color back to the image */
            (*ColorWriter)(colhandle, (R3FLOAT *)&col, i, j);
        }
     }

Example Source Code

For complete example see the directory 'samples/plugins/postprocessing/images/fog'.