8.5 SDK



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 27/11/2003 at 13:03, xxxxxxxx wrote:

    User Information:
    Cinema 4D Version:   8.500 
    Platform:   Windows  ; Mac  ;  Mac OSX  ; 
    Language(s) :     C++  ;

    ---------
    Here's some stuff that might help you - while Mikael updates & enhances the docs for 8.5 this should get you started...
    SDK Migration to 8.5

    The main change of the new C4D V8.5 API is the updated material system. This document will provide you with all necessary information related to those changes.
    Do old plugins need to be recompiled?
    -------------------------------------
    Plugins don't need to be recompiled in order to run. However to be fully integrated into the new material system an adaption for some plugins is necessary.
    If we're talking about an old R6.x/R7.x plugin you definitely should consider to move up to R8.5 now - there are too many inconveniences your users will encounter otherwise. Also, starting with the next release of C4D R6 & R7 shaders will no longer be supported!
    Which types of plugins are affected?
    ------------------------------------
    If your plugin is not a shader or material and does not deal with BaseChannels there's no need to change a thing.
    Of course there are cool API enhancements (like Attributes Manager formatting) that might be tempting to use...
    How much work is it to make the transition?
    -------------------------------------------
    If your shader/material plugin doesn't have anything to do with BaseChannels then it's a matter of minutes, otherwise it's a bit more work. Once you've understood the underlying concept it should be pretty straightforward though.
    What are the limitations of pre 8.5 shaders/materials?
    ------------------------------------------------------
    Old shaders will not play well with other shaders/materials. E.g. the bumping will not use the new unified bump system (resulting in visual differences), the layering with other shaders will be limited, your shader will not be able to use other shaders as subchannels and your material won't show up in the material editor. We strongly recommend to move up to 8.5!
    Why is there a new shader system?
    ---------------------------------
    The old system showed its age - several shaders didn't fully work together. Some of them also were using a different user interface and were not integrated into C4D R8's new Attributes Manager architecture. In the new system those limitations are gone - all shaders can be combined freely, there's one continuous bump system and all properties of an object can be read/modified through the SDK & XPresso.
     
    Shader Changes (Channel Shader)
    -------------------------------
    First let's take a look at shader descriptions.

    Here's how one of C4D's built in shader looks:

        
        
          
        CONTAINER Xcloud  
        {  
         NAME Xcloud;
        
        
        
        
         INCLUDE Mpreview;  
         INCLUDE Xbase;
        
        
        
        
         GROUP ID_SHADERPROPERTIES  
         {  
          GRADIENT CLOUDSHADER_COLOR {}  
          REAL    CLOUDSHADER_SCALEX { MIN 0.0; STEP 0.01; }  
          ...  
         }  
        }  
        
    

    The include Mpreview needs to be added so that your shader gets a preview in the Attributes Manager (AM) / Material Editor (ME).
    The include Xbase is not new, but now contains the MIP controls (strength/offset) that belong to the shader. Xbase should be included after Mpreview.
    The group ID_SHADERPROPERTIES is used by all shaders as base group. You should use it for the most important elements, even when you're using multiple groups.

    V8.5 supports the possibility to show the shaders in an organized way - this can be achieved through a change of the registation name.
    E.g. in the following example the name DEFAULTFILENAME_SHADER_SURFACES is placed in front of the shader name:
    RegisterShaderPlugin(Xmy,(GeGetDefaultFilename(DEFAULTFILENAME_SHADER_SURFACES)+String("MyShader")).GetString(),0,MyClass::Alloc,"Xmy",0);
    It is important that the "+" operation is done using filenames and not using strings as this will lead to a different result (a filename adds separator characters)! The shader will now be placed in the "Surfaces" category. There is also the "Effects" category and the top level hierarchy. Only shaders that are used extensively by the user should be placed here! For all other shaders it might be better to place them either below, in one of the existing categories or under an own submenu (which will be automatically generated if you register two or more shaders and you do not specify the extra filename part in the name).
    To place the shader on the top level in the upper region you need to create a name like this: (Filename("#$15")+String("MyShader")).GetString() where the number determines the order. Existing values used by C4D are 10 (Color), 65 (Colorizer), 50 (Filter), 25 (Fresnel), 55 (Fusion), 20 (Gradient), 60 (Layer), 15 (Noise), 70 (Posterizer).
    Now let's take a look at the flags returned by the method ::GetRenderInfo.
    CHANNEL_DIRECTBUMP does not exist anymore. If possible your shader should not directly generate a normal as the result cannot be processed and used by any other shader. E.g. think of your shader as child of Posterizer, where the result is used in Fusion with a bitmap. What shall happen if your shader returns a normal? Posterizer does only work with colors... and to mix the result in a usable way after the normal was generated is impossible. That's where the new bumping system comes in handy: it always generates four samples - it doesn't matter if your shader is a 2d shader or 3d shader. Those 4 samples are always calculated for shader tree and then at the end used to create the distortion of the normal vector.
    You have 3 ways to deal with it:
    1.) The "I don't care!" approach
        In this case you change CHANNEL_DIRECTBUMP to CHANNEL_DIRECTBUMP_EX and you're done. However keep in mind that this shader will most likely create no useful results when used within other shaders.
       
    2.) The "What the hell is bumping?" approach
        If you never ever heard of bumping before or your shader just does a standard UV / 3D based calculation, then you don't need to set any flags for bumping. Your shader will automatically be called 4 times by C4D with shifted UV / 3D coordinates.
    3.) The "I want to take full control" approach
        This is where you set the new flag CHANNEL_BUMP_SUPPORT. It shows C4D that you are aware of the 4point bump system and you'll take all necessary steps to get the desired result.
        E.g. a shader like Posterizer will do nothing but set this flag to avoid that C4D shifts the input UV/3D coordinates. It then samples the sub channel.
        A shader like Noise will set the flag, but shift the UV/3D coordinates based on its own settings. Within the ::Output method it looks like this:

        
        
          
          if (GET_TEX_CHANNEL(cd->texflag) == CHANNEL_BUMP) // is this a bump calculation ?   
          {  
           switch (GET_TEX_BUMP_SAMPLE(cd->texflag)) // which of the four samples are we talking about ?  
           {  
            case 0: uv.x -= mysampleoffset; break; // shift the UV surface point  
            case 1: uv.x += mysampleoffset; break;  
            case 2: uv.y -= mysampleoffset; break;  
            case 3: uv.y += mysampleoffset; break;  
           }  
          }  
        
    

    The GET_TEX_CHANNEL macro is a nice thing - you can now determine which channel is being calculated! E.g. you can return different results depending on being called in a transparency channel or bump channel.
       
        Using GET_TEX_BUMP_SAMPLE we can determine the number of the sample.
       
        The convention is:
        - Sample 0: left of the original position
        - Sample 1: right of the original position
        - Sample 2: above the original position
        - Sample 3: below the original position
       
        This of course holds true for a 2D surface shader only. But what happens if you shader is based on 3D input? In that case you do the same, just move your sample along the DDU/DDV vectors instead of U/V. The code looks like that:

        
        
          
          if (GET_TEX_CHANNEL(cd->texflag) == CHANNEL_BUMP)   
          {  
           switch (GET_TEX_BUMP_SAMPLE(cd->texflag))   
           {  
            case 0: pos -= mysampleoffset*cd->vd->ddu; break;  
            case 1: pos += mysampleoffset*cd->vd->ddu; break;  
            case 2: pos -= mysampleoffset*cd->vd->ddv; break;  
            case 3: pos += mysampleoffset*cd->vd->ddv; break;  
           }  
          }  
        
    

    If your shader supports a different output for alpha calculation (used e.g. by the Layer Shader or in the C4D main material Alpha channel) you need to specify the CHANNEL_ALPHA_SUPPORT flag. During ::Output you need to return the alpha value if (cd->texflag&TEX_ALPHA) is TRUE. Keep in mind that this has nothing to do with GET_TEX_CHANNEL(cd->texflag)==CHANNEL_ALPHA, which simply tells you that a calculation is done within a material's alpha channel (in which TEX_ALPHA might not be evaluated).

    Material Changes (Volume Shader)
    --------------------------------
    This is the description of C4D's main material:

        
        
          
        CONTAINER Mmaterial  
        {  
         NAME Mmaterial;
        
        
        
        
         INCLUDE Mpreview;  
         INCLUDE Mbase;
        
        
        
        
         GROUP Obaselist  
         {  
          BOOL MATERIAL_USE_COLOR     { PARENTMSG ID_MATERIALGROUP_COLOR; }  
          BOOL MATERIAL_USE_DIFFUSION   { PARENTMSG ID_MATERIALGROUP_DIFFUSION; }  
          ...  
         }
        
        
        
        
         GROUP ID_MATERIALGROUP_COLOR  
         {  
          DEFAULT 1;  
          COLOR MATERIAL_COLOR_COLOR { OPEN; }  
          ...  
         }
        
        
        
        
         ...  
           
         INCLUDE Massign;  
        }  
        
    

    The include Mpreview needs to be added so that your material gets a preview in the Attributes Manager (AM) / Material Editor (ME).
    The include Mbase follows to give you access to the Obaselist group, including the material name. Mbase should be included after Mpreview.
    Within Obaselist all control elements are placed to activate/deactivate/navigate to other groups. One of those groups in this example is the color group (ID_MATERIALGROUP_COLOR). New is the ability to specify a "PARENTMSG" for Boolean elements. The ME detects this property and makes sure that this group is shown on the right side when the user clicks on a Boolean on the left side.
    There are also navigation groups that have no checkbox (as they're always present). To implement them use the following syntax:

        
        
          
        BOOL MATERIAL_PAGE_ILLUMINATION { PAGE; HIDDEN; PARENTMSG ID_MATERIALGROUP_ILLUMINATION; }  
        
    

    It's like a boolean element, just with the additional flags "PAGE" and "HIDDEN". Page makes sure that there will be no checkbox in front of the name. "HIDDEN" will hide the element in the AM, where there is no navigation control like in the ME. The ME then automatically unhides this element.
    At the end of the material description there's the Massign-include. Include it if the Assignment-Tab makes sense for your material.

    V8.5 supports the possibility to show materials in an organized way - this can be achieved through a change of the registation name.
    E.g. in the following example the name DEFAULTFILENAME_SHADER_VOLUME is placed in front of the shader name:
    RegisterMaterialPlugin(Mmy,(GeGetDefaultFilename(DEFAULTFILENAME_SHADER_VOLUME)+String("MyMaterial")).GetString(),0,MyClass::Alloc,"Mmy",0);
    It is important that the "+" operation is done using filenames and not using strings as this will lead to a different result (a filename adds separator characters)! The material will now be placed in the "Shader" category.
    Data Storage (both Channel and Volume Shader)
    ---------------------------------------------
    It is strongly recommended that you store all data within the shader/material's BaseContainer. If you need to store data that does not fit into the container you can implement your own CustomDataType enhancments.
    If your shader/material did not use a BaseContainer before you should increase the disklevel, remove the ::Write method and add some code like this for ::Read :

        
        
          
        Bool MyShader::Read(GeListNode *node, HyperFile *hf, LONG level)  
        {  
         if (level<MYSHADER_INCREASEDDISKLEVEL)  
         {  
          // read the old data and convert it to BaseContainer entries  
         }  
           
         return TRUE;  
        }  
        
    

    Sub-/Basechannels (both Channel and Volume Shader)
    --------------------------------------------------
    Though BaseChannels are still exist in the API for compatibility reasons they won't offer you the new functionality and are heavily restricted in the way they work.
    In the new material system a shader is a derivate of BaseList2D. Instead of having a BaseChannel data type that holds the shader and its contents now the parent shader/material just holds a BaseLink to the object. The object itself is inserted under the parent shader/material (or tag/videopost etc.) using BaseList2D::InsertShader.
    Let's do a side by side comparison to better outline the changes using the BitmapDistortionShader example.

        
        
          
        class BitmapData : public ShaderData  
        {  
         public:  
          BaseChannel   *bc;       // REMOVED IN 8.5  
          PluginShader *shader;   // ADDED IN 8.5  
          Real noise,scale,octaves;  
         public:  
          virtual Bool Init  (GeListNode *node);  
          virtual void Free  (GeListNode *node);                            // REMOVED IN 8.5  
          virtual Bool Read  (GeListNode *node, HyperFile *hf, LONG level);  
          virtual Bool Write (GeListNode *node, HyperFile *hf);             // REMOVED IN 8.5  
          virtual Bool Message(GeListNode *node, LONG type, void *data);  
          virtual Bool CopyTo (NodeData *dest, GeListNode *snode, GeListNode *dnode, LONG flags, AliasTrans *trn); // REMOVED IN 8.5  
          virtual Vector Output  (PluginShader *chn, ChannelData *cd);
        
        
        
        
          virtual LONG InitRender (PluginShader *chn, InitRenderStruct *irs);  
          virtual void FreeRender (PluginShader *chn);
        
        
        
        
          virtual Bool GetDParameter(GeListNode *node, const DescID &id,GeData &t_data,LONG &flags);       // REMOVED IN 8.5  
          virtual Bool SetDParameter(GeListNode *node, const DescID &id,const GeData &t_data,LONG &flags); // REMOVED IN 8.5
        
        
        
        
          static NodeData *Alloc(void) { return gNew BitmapData; }  
        };  
        
    

    As you can see several routines are no longer necessary - the subchannel handling has become a lot easier!
    The BaseChannel 'bc' has been replaced by the 'shader' variable. Other than before 'shader' is only valid during rendering - it is read in ::InitRender and set to NULL in ::FreeRender.
    - ::Free no longer is necessary
      In 8.2 the BaseChannel was deleted which is obsolete now.
    - ::Write no longer is necessary
      In 8.2 the BaseChannel was written which is obsolete now as the shader connection is saved as a BaseLink within the object's BaseContainer.
     
    - ::CopyTo no longer is necessary
      In 8.2 the BaseChannel was copied which is obsolete now as BaseChannel links are automatically transferred.
    - ::Get/SetDParameter no longer are necessary
      In 8.2 both routines needed to return information about the BaseChannel which is obsolete now.

    Now on to the changes
    - ::Init
     
      Instead of

        
        
          
            bc = BaseChannel::Alloc();  
            return bc!=NULL;  
        
    

    you now need to initialize the shader link:

        
        
          
           shader=NULL;  
           data->SetLink(BITMAPDISTORTIONSHADER_TEXTURE,NULL);  
        
    

    where BITMAPDISTORTIONSHADER_TEXTURE is the BaseContainer ID under which the link is stored.

    - ::Read
      Instead of

        
        
          
            hf->ReadChannel(bc);  
        
    

    you now need to convert the old BaseChannel:

        
        
          
            BaseContainer *data = ((BaseList2D* )node)->GetDataInstance(); // Get Shader/Material's BaseContainer  
           
            if (level<MYSHADER_INCREASEDDISKLEVEL) // as written in the last chapter you need to increase the disk level   
                                                   // (parameter of RegisterShaderPlugin/RegisterMaterialPlugin)  
                                                   // to identify old shaders  
           {  
             if (!hf->ReadChannelConvert(node,BITMAPDISTORTIONSHADER_TEXTURE)) return FALSE; // convert old basechannel  
           }  
        
    

    - ::Message
      Instead of

        
        
          
            if (type==MSG_GETALLSTRINGS)   
             return bc->Message(MSG_GETALLSTRINGS,data);    
        
    

    you now need to write

        
        
          
            BaseContainer *data = ((BaseList2D* )node)->GetDataInstance(); // Get Shader/Material's BaseContainer  
            HandleShaderMessage(node,(PluginShader* )data->GetLink(BITMAPDISTORTIONSHADER_TEXTURE,node->GetDocument(),Xbase),type,msgdat);  
        
    

    HandleShaderMessage automatically takes care of all internal shader messages that need to be distributed to subchannels. It must be called with the subchannel's shader (calculated by GetLink) which might be NULL.
     
     In 8.5 most shaders that have subchannels preserve an existing channel instead of replacing it. E.g. the user placed a Noise in the material's color channel. When he now chooses Posterizer from the menu the Noise is placed under Posterizer. It's up to you if you want to support this functionality. It can be achieved by the following line:

        
        
          
           HandleInitialChannel(node,BITMAPDISTORTIONSHADER_TEXTURE,type,msgdat);  
        
    

    Note that this must only be called for one single subchannel!
    - ::InitRender
      Instead of

        
        
          
            ret_value=bc->InitTexture(irs);  
        
    

    you now need to write

        
        
          
            BaseContainer *data = ((BaseList2D* )node)->GetDataInstance(); // Get Shader/Material's BaseContainer
        
        
        
        
            shader  = (PluginShader* )data->GetLink(BITMAPDISTORTIONSHADER_TEXTURE,irs->doc,Xbase); // read shader from BaseContainer  
            if (shader) // if a shader is present  
              ret_value=shader->InitRender(irs); // initialize shader  
            else  
            {  
             // best thing is to allow empty subchannels, return no error in that case and use a default color  
            }  
        
    

    - ::FreeRender
      Instead of

        
        
          
           bc->FreeTexture();  
        
    

    you now need to write

        
        
          
            if (shader)  
              shader->FreeRender();  
            shader=NULL;  
        
    

    This frees the shader resources (instead of the BaseChannel).
    - ::GetRenderInfo
      The BitmapDistortionChannel supports the new bumping system and returns CHANNEL_BUMP_SUPPORT.
    - ::Output
      Instead of

        
        
          
           ret_value=bc->Sample(cd->vd,&uv,&cd->d,&cd->n,cd->t,cd->texflag,cd->off,cd->scale);  
        
    

    you now need to write

        
        
          
           ret_value=shader->Sample(cd);  
        
    

    If you modify some of the values of ChannelData (cd), backup them before and restore them afterwards.

    Now the last part - the description:
      Instead of

        
        
          
          TEXGROUP BITMAPDISTORTIONSHADER_TEXTURE { }  
        
    

    you need to write

        
        
          
          SHADERLINK BITMAPDISTORTIONSHADER_TEXTURE { }  
        
    

    That's it!

    Subchannel write access (both Channel and Volume Shader)
    --------------------------------------------------------
    Normally you don't have to deal with this, but if you want to create or modify shader trees via the API you need to read the following lines.
    If you're programming shader links you need to make sure that every shader is referenced only once - it is not allowed that a shader is referenced multiple times. The the referencing object is a shader the referenced shader must be child of it, otherwise is must be inserted via InsertShader()
    Example 1: A Tag references a shader. The shader must be inserted into the Tag using tag->InsertShader();
    Example 2: A Shader (A) references another shader (B) : The shader B must be child of the shader A.

    Additional stuff, new routines
    ------------------------------
    The 8.5 API allows a lot of things that weren't possible before. Let's go through all additions:
    - PluginShader::SampleBump allows you to calculate bump mapping for a shader (and its childs) like C4D does
     
      SampleBump is called exactly like Sample. As second parameter you can specify SAMPLEBUMP_MIPFALLOFF if the additional bump change over distance shall be considered.
      The routine returns the delta vector that is added to the normal. The resulting normal is

        
        
          
          n_dst = !(n_src + SampleBump()); // normalize result  
        
    

    - PluginShader::GetBitmap returns the Bitmap if the plugin shader was of type Xbitmap. Note that this call is only allowed if enclosed by ::InitRender and ::FreeRender.
    - PluginShader::GetRenderInfo returns the shader render bits
    - PluginShader::InitRender/FreeRender/Sample work like their BaseContainer counterparts
    - PluginShader::GetDown/GetUp/GetDownLast allow you to browse through shader trees
    - BaseMaterial::GetRenderInfo returns the material render bits
    - BaseMaterial::GetAverageColor returns an average color for the material, based on the material preview
    - BaseMaterial::Displace calls the displacement routine of a material. Note that the passed VolumeData structure must be fully initialized and contain a render instance (can only be achieved by copying/initializing from an existing VolumeData)
    Displace changes VolumeData::p
    - BaseMaterial::ChangeNormal calls the displacement routine of a material. Note that the passed VolumeData structure must be fully initialized and contain a render instance (can only be achieved by copying/initializing from an existing VolumeData).
    ChangeNormal changes VolumeData::bumpn. Note that normalization is not done by the routine.
    - BaseMaterial::CalcSurface computes the surface properties of a material. Note that the passed VolumeData structure must be fully initialized and contain a render instance (can only be achieved by copying/initializing from an existing VolumeData).
    CalcSurface changes -depending on the parameters set in VolumeData- VolumeData::col/trans/refl/tray/rray/alpha.
    - BaseMaterial::CalcTransparency computes the transparency properties of a material (it is a specialized version of CalcSurface). Note that the passed VolumeData structure must be fully initialized and contain a render instance (can only be achieved by copying/initializing from an existing VolumeData).
    CalcTransparency changes VolumeData::trans
    - BaseMaterial::CalcAlpha computes the alpha properties of a material (it is a specialized version of CalcSurface). Note that the passed VolumeData structure must be fully initialized and contain a render instance (can only be achieved by copying/initializing from an existing VolumeData).
    CalcAlpha changes VolumeData::alpha
    - BaseMaterial::CalcVolumetric computes the color of a volumetric ray. Note that the passed VolumeData structure must be fully initialized and contain a render instance (can only be achieved by copying/initializing from an existing VolumeData).
    CalcVolumetric changes VolumeData::col/VolumeData::trans
    - BaseMaterial::InitCalculation needs to be called before CalcTransparency (type==INIT_TRANSPARENCY), CalcSurface (type==INIT_SURFACE) and CalcDisplacement (type==INIT_DISPLACEMENT) if (and only if) (Material::GetRenderInfo()&SHADER_INITCALCULATION) is TRUE.
    - CALC_TEXINFO helps you to create the 'texflag' variable when you Sample a subchannel e.g. in a material:

        
        
          
          ChannelData::texflag = CALC_TEXINFO(TexData::texflag,CHANNEL_TRANSPARENCY);  // sets up the flag so that the shader knows it's a transparency calculation.   
        
    

    Where TexData::texflag contains the TEX_TILE/TEX_MIRROR information of the texture tag.
     E.g. if you start a reflection calculation from a custom material it looks like this:

        
        
          
         ChannelData chnData(vd); // initialize ChannelData with VolumeData  
         chnData.p = ...; // set point  
         chnData.texflag = CALC_TEXINFO(vd->tex?vd->tex->texflag:0,CHANNEL_REFLECTION);
        
        
        
        
         color=shader->Sample(/chnData);  
        
    

    - CALC_TEXINFO_BUMP is also available with the additional 'sample' parameter (0..3 specifies the bump sample). However you normally don't need to use this, it's more convenient to call PluginShader::SampleBump. SampleBump automatically overrides the texflag to CHANNEL_BUMP, only TEX_TILE,TEX_MIRROR and TEX_ALPHA remain unchanged.
    - VolumeData::GetRayPolyState
      returns you information about the state of a polygon. The polygon can either be a triangle (RAYPOLSTATE_TRIANGLE), a quadrangle (RAYPOLYSTATE_QUADRANGLE) or a split quadrangle (that's the case when it is not coplanar) (RAYPOLYSTATE_QUADRANGLE|RAYPOLYSTATE_SPLIT).
    - VolumeData::GetWeights
      returns Barycentric coordinates for a point on the surface of a polygon. C4D uses enhanced interpolation routines for quadrangles, so you'll get a higher quality using it instead of considering a quadrangle as two triangles. The routine works for any type of polygon, including triangles and non-coplanar quadrangles.
    - VolumeData::Init
      Initializes the VolumeData structure from an existing structure. Only the most essential parts are copied over, including the render instance. Such a VolumeData can be used for TraceColorDirect, TraceGeometry, TraceColor etc.
      ::Init is faster than ::CopyTo, but doesn't allow subsequent access of functions like BaseMaterial::CalcSurface (only possible if all members are initialized by hand)
    - VolumeData::CalcFgBg
      Calculates the fore- or background color for a screen pixel.
     
     
    SDK example changes
    -------------------
    - BitmapDistortion Shader
     
      - shows how to build a shader with sub-channels
      - shows how to read data from older shader versions that used BaseChannels
      - shows the critical points:
        ::GetRenderInfo / CHANNEL_BUMP_SUPPORT
      ::Init
      ::InitRender
      ::FreeRender
      ::Message / HandleShaderMessage
    - ParticleVolume Shader
      - shows how to generate a custom material preview
      - shows how to place the shader into the main Shader menu
    - Simple Material
      - shows how to add an additional material preview item
      - shows how to add a user preview scene



  • THE POST BELOW IS MORE THAN 5 YEARS OLD. RELATED SUPPORT INFORMATION MIGHT BE OUTDATED OR DEPRECATED

    On 28/11/2003 at 02:29, xxxxxxxx wrote:

    Thanks for the early info!


Log in to reply