CustomMaterial: Server basics

From The Foundry MODO SDK wiki
Jump to: navigation, search

A CustomMaterial is plug-in server that implements a material that adds new material attributes and can contribute to shading.


Materials are used when you need to extend modo's advanced materials with custom attributes. These attributes can then be textured and finally converted into shading components at the end of the shading pipe. A custom material is always evaluated in 2 stages: first as a material and second as a shader to produce the shading components.


shade (lx-shade.hpp)

  • CLxImpl_CustomMaterial action (lx-action.hpp)
  • CLxUser_Evaluation package (lx-package.hpp)
  • CLxUser_AddChannel value (lx-value.hpp)
  • CLxUser_Attributes vector (lx-vector.hpp)
  • CLxUser_PacketService

Sample methods

The plug-in server class derives from CLxImpl_CustomMaterial, and exports the ILxCustomMaterial interface. For speed purposes the class will also want to keep the packet service ready to hand.

CLxUser_PacketService pkt_service;


Like a package, a value texture server must define its channels. The SetupChannels() method is identical to the one used by packages.

cmt_SetupChannels (
        ILxUnknownID		 addChan)
        CLxUser_AddChannel	 ac (addChan);

        ac.NewChannel ("myInput", "percent");
        ac.SetDefault (1.0, 1);
        return LXe_OK;

The value texture reads its channel values in a modifier context. That means that it has to declare the channels it wants to read using the Evaluation Interface. This is also a good place to cache the offsets for any vector packets that it wants to read.

cmt_LinkChannels (
        ILxUnknownID		 eval,
        ILxUnknownID		 item)
        CLxUser_Evaluation	 ev (eval);

        idx_value = ev.AddChan (item, "myInput");
        tin_offset = pkt_service.GetOffset (LXsCATEGORY_SAMPLE, LXsP_TEXTURE_INPUT);
        return LXe_OK;

Linking channels occurs once as items are added or their relationships are changed. Reading channels then happens any time channel values change. For example changing time may cause channels to be read again if they are animated. Values are read from an Attributes Interface using the index cached when linking channels. The values are then stored in an allocated data structure which is returned from the method.

cmt_ReadChannels (
        ILxUnknownID		 attr,
        void		       **ppvData)
        CLxUser_Attributes	 at (attr);
        RendData		*rd = new RendData;

        rd->value  = at.Float (idx_value);
        ppvData[0] = rd;
        return LXe_OK;

Material evaluation is the first step of the evaluation, it is intended to set the material attributes into of the shading packets. This may happen thousands or even millions of times per frame, so it's intended to be as fast as possible. The private data struct containing channel values is passed as data. In general we do this to extend the material attributes, it is therefore necessary to define a custom packet using the VectorPacket Interface and its effects using the PacketEffect Interface. In the following code snippet our custom packet is LXpMyPacket.

Note also that this is a void function. Anything that can fail, such as allocations, should already have been done as part of reading channels. The evaluation is assumed to succeed and there is no allowance for failure.

cmt_MaterialEvaluate (
        ILxUnknownID		 vector,
         void			*data)
         LXpMyPacket		*pack = (LXpMyPacket*) pkt_service.FastPacket (vector, pkt_offset);
        RendData		*rd = (RendData *) data;
        pack->color[0] = 1.0;
        pack->color[1] = 0.0;
        pack->color[2] = 0.0;

Shader evaluation is the second and final step of the evaluation, it is intended to read the material attributes from a sample vector packet and produce shading components. In this silly example we just replace the diffuse shading component with our custom color (red in this case)

cmt_ShaderEvaluate (
       ILxUnknownID            vector,
	ILxUnknownID		rayObj,
       LXpShadeComponents     *sCmp,
       LXpShadeOutput         *sOut,
       void                    *data)
        LXpMyPacket		*pack = (LXpMyPacket*) pkt_service.FastPacket (vector, pkt_offset);
        RendData		*rd = (RendData * ) data;
	raycast.set (rayObj);
	for (i=0;i<3;i++) {
	       sCmp->diff[i]  = pack->color[i];
	       sOut->color[i] = sCmp->diff[i] + sCmp->spec[i] + sCmp->refl[i] + sCmp->tran[i] + sCmp->subs[i] + sCmp->lumi[i];