Coding the shader itself

Below is the entire code for the actual shader - the bit that gives you the required results. As it's so short I'll just present all the code first, then discuss the various functions afterwards.

// BMFlip
// bmflip.cpp

// includes
#include "c4d.h"
#include "bmflip.h"
#include "xbmflip.h"
#include "c4d_symbols.h"

SHADERINFO BitmapFlip::GetRenderInfo(BaseShader *sh)
{
    return SHADERINFO_BUMP_SUPPORT;
}

Bool BitmapFlip::Init(GeListNode *node)
{
    shader = nullptr;
    flipX = false;
    flipY = false;
    return true;
}

void BitmapFlip::FreeRender(BaseShader *sh)
{
    if(shader != nullptr)
        shader->FreeRender();
    shader = nullptr;
}

Bool BitmapFlip::Message(GeListNode *node, Int32 type, void *msgdat)
{
    BaseContainer *data;

    data = ((BaseShader*)node)->GetDataInstance();
    HandleInitialChannel(node, BMFLIPSHADER_TEXTURE, type, msgdat);
    HandleShaderMessage(node, (BaseShader*)data->GetLink(BMFLIPSHADER_TEXTURE, node->GetDocument(), Xbase), type, msgdat);

    return true;
}

INITRENDERRESULT BitmapFlip::InitRender(BaseShader *sh, const InitRenderStruct& irs)
{
    BaseContainer *data;
    data = sh->GetDataInstance();
    // get gadget values
    flipX = data->GetBool(BMFLIPSHADER_FLIPX);
    flipY = data->GetBool(BMFLIPSHADER_FLIPY);
    shader = (BaseShader*)data->GetLink(BMFLIPSHADER_TEXTURE, irs.doc, Xbase);
    if(shader != nullptr)
    {
        return shader->InitRender(irs);
    }

    return INITRENDERRESULT_OK;
}

Vector BitmapFlip::Output(BaseShader *sh, ChannelData *cd)
{
    Vector res, uv;

    if(shader == nullptr) return Vector(1.0, 0.0, 0.0); // return red if no bitmap present

    uv = cd->p;
    if(flipX)
        cd->p.x = 1.0 - cd->p.x;
    if(flipY)
        cd->p.y = 1.0 - cd->p.y;
    res = shader->Sample(cd);
    cd->p = uv;

    return res; // otherwise return the colour from the bitmap
}

// register the plugin
Bool RegisterBMFlip(void)
{
    String name;
    name = GeLoadString(IDS_BMFLIP);
    return RegisterShaderPlugin(ID_BMFLIP, name, 0, BitmapFlip::Alloc,"Xbmflip", 0);
}

How it works

Recall from the previous page that six virtual functions are declared, which we now have to provide definitions for:

Init()

This function does what it says - lets you initialise anything you need to when the plugin is run. Here, we just initialise two variables flipX and flipY, and more importantly a pointer to a BaseShader called, simply, 'shader'. Why do we need this? The plugin interface will contain a link field allowing the user to select a bitmap. If a bitmap is loaded into that field, a shader of type Xbitmap will be created, and we need to call that shader's own InitRender() and FreeRender() functions so that we can sample the colour data from it later. To do this, we need a pointer. We initialise it to nullptr here on the grounds of good practice - never leave an uninitialised pointer around, you don't know what it will point to.

GetRenderInfo()

When this function is called by C4D, the plugin should return what it needs from the rendering system. The values it can return are defined in the SDK. For this one, we could probably return zero, but for safety's sake we indicate that the shader supports the bump system.

Message()

Here, the plugin would respond to messages sent to it by C4D. In fact, there aren't any we need to deal with in this plugin, but we do need to call two functions - HandleShaderMessage() and HandleInitialChannel(). These are both explained very well in the SDK, so I won't consider them further here.

InitRender()

As you might expect, this is called prior to a render taking place. (Remember that the preview in the material editor is also a mini-render, so this function is called frequently.) We can perform whatever calculations we need to here, so that we don't do it during the actual rendering process, which would slow it down.

Firstly, we get the base container of our shader, then use that to get the value of the two on/off switches in the plugin interface. These values are assigned to our two variables that were initialised to FALSE in the Init() function. Then we get the pointer to the Xbitmap shader in the bitmap link field, if there is one. Provided that a valid pointer is returned, we call its own InitRender() function. We must do this because we are going to sample colour from the bitmap later. Notice that the InitRender() call to our plugin (which is a ShaderData object) is not the same as the call to an actual shader (which is a BaseShader object).

The return value will either be the value returned from the Xbitmap shader's InitRender() call, or the value INITRENDERRESULT_OK, which indicates no errors. Other values are shown in the SDK.

Output()

This is the real meat of the shader - the part that determines the output colour. Note that it returns a vector holding the RGB colour values.

The first thing we do is determine if a bitmap was loaded into the link feld. If there isn't one, the pointer to the Xbitmap shader that we got in InitRender() will be a nullptr. In that case there's nothing we can do, so we'll just return a uniform red colour. You can return whatever you like, of course.

Assuming there is an Xbitmap shader, we have a pointer to some data contained in a ChannelData structure. One of the members of the structure is 'p' - the texture position in UVW coordinates. If we are going to flip the bitmap horizontally, all we need to do is alter the ChannelData structure so that we point to the opposite side of the bitmap. The same applies for vertical flipping, of course. Then we put the new values back into the ChannelData, and call the Sample() function of the Xbitmap shader to sample the colour at the modified coordinates. (What this does is return the colour value at the point cd->p, but we've changed the value of 'p' to different coordinates.)

Finally we restore the original values into the ChannelData, and return the result of the Sample() call. This colour is what will be displayed on screen.

FreeRender()

If we allocated any memory in the InitRender() call, that should be freed here. All we need to do in this case is call the Xbitmap shader's own FreeRender() function (if there was a valid pointer to the Xbitmap) and then set the pointer of the 'shader' variable back to nullptr, ready for the next call to InitRender(). We do this in case the user clears the bitmap from the link field before the next render - otherwise we would be working with an invalid pointer.

RegisterBMFLip()

This is the function called to register the plugin with C4D. All it does is load the name of the plugin from the global string file c4d_strings.str and then call the function RegisterShaderPlugin(). The main points of interest in that function are the call to the Alloc() function defined in the header file (see the previous page) which returns a new instance of the plugin class, and the last but one parameter in the function call. This is a string - 'Xbmflip' - which is the name of the resource file to be loaded for this plugin. Recall from the first page that there are three files, xbmflip.res, xbmflip.h, and xbmflip.str, and Cinema will look for these in standard locations relative to the plugin itself.

Conclusion

Writing channel shader plugins is mostly straightforward. The difficult bit lies in calculating the colour to be returned from the Output( ) function. In this case it was very simple, in others it will be extremely complex. Hopefully this will show you how to set up the framework for a channel shader, which you can expand on as required. You can download the source and resource files for this plugin from the link below.

Download source files Download the source files (4K, .zip file)

Page last updated June 23rd 2021