S22: Question about going from a custom shader's ShaderData::Output() function back to the texture tag of an object



  • I have a question about the correct path from the ShaderData::Output() function which gets overriden by a custom shader to the texture tag that links to the material which hosts the shader in one of its slots. Before stating the problem, let me explain the reason why I would like to do this:

    I am trying to reuse the same material across multiple objects. This material hosts a custom shader called Object Controlled Shader in its Color channel's Texture slot, as indicated by the orange arrow in the image below:

    Orange Arrow points to Custom Shader assigned to a material's Color channel's Texture slot

    This custom shader is meant to enable me to drive the color from a custom user data Color property that is associated with a texture tag attached to an object. I can use this user data Color property to set a per object texture tag color for the material and be able to modify it based on various criteria, without having to physically change the material, since it is also shared by other objects. I can do this while reusing all other aspects of the material. So, in effect, this would enable me to parameterize various aspect of a material via custom shaders on a per object per texture tag basis. This is somewhat similar to what the Mograph Color Shader currently does, except that the Color Shader allows for only one point of customization control, since it is hard wired to an Object's Basic Color (ID_BASEOBJECT_COLOR) property.

    It should be noted that the built in Mograph Color Shader works correctly to set the color both in Cinema 4d's Viewports and at render time, which is important as I will explain, below.

    We start with the following overriden function in my custom shader:

    Vector MyShader::Output(BaseShader* pShader, ChannelData* pChanelData)
    {
      ...
    }
    

    Now let's look at what we have available in this function:

    • this - A pointer to an instance of my custom shader class derived from ShaderData
    • pShader - A pointer to the BaseShader that hosts the ShaderData derived object
    • pChannelData - A pointer to an object of type ChannelData

    After much trial and error, I have been able to figure out that the following code (potentially) gets me the texture tag user data based color property I am trying to extract and use that to return a per object texture tag specific color from the Output() function:

    Vector MyShader::Output(BaseShader* pShader, ChannelData* pChanelData)
    {
        int MY_COLOR_ID=1; // Id inside a texture BaseTag's user data
        Vector COLOR_VECTOR_NOT_PRESENT=Vector(1.0, 0.0, 0.0); // Bright red color - to indicate errors
    
        Vector colorVector=COLOR_VECTOR_NOT_PRESENT;
    
        // If the ChannelData has a valid pointer to a VolumeData object (vd), and this object points to a valid
        //   TexData object (tex), which itself points to a valid BaseTag object (link)
        if (pChannelData && pChannelData->vd && pChannelData->vd->tex && pChannelData->vd->tex->link)
        {
            // Then, I can get the texture tag linked to the material hosting my custom shader
            BaseTag *pTag=pChannelData->vd->tex->link;
    
            // ..., and that also means that I can get any UserData associated with this texture tag (if present)
            const BaseContainer *pUserData=pTag->GetDataInstance()->GetContainerInstance(ID_USERDATA);
    
            // If user data is present
            if (pUserData)
            {
                // ..., and it contains a Vector data item at MY_COLOR_ID with a color (i.e., Vector) property type
                colorVector=pUserData->GetVector(MY_COLOR_ID, COLOR_VECTOR_NOT_PRESENT);    
            }
        }
    
        // Return the color vector from the user data to customize the color of the shader on a per texture tag
        // basis (or COLOR_VECTOR_NOT_PRESENT, bright red, if the texture tag does not have this user data item)
        return colorVector;
    }
    

    This seems to work, but it only works during renders. I cannot, for the life of me, figure out how to get the texture tag of an object that is assigned a material using my custom shader for purposes of display in Cinema 4d's Viewports, regardless of the Viewport's Display Shading setting. I have tried, seemingly, every possible path through the pointers available in the objects passed by pointer to the Output() function.

    I have read many reports online that code like this: pChannelData->vd->op->link->GetCacheParent() , assuming of course that all pointers along the way are valid, gets the object that hosts the texture tag linked to the material hosting the custom shader, but this does not work in S22 - the latest C4D release. I only get some internal "virtual" or perhaps "cached" object named "Object" back and every object path through the hierarchy that I have tried using that link pointer seems to get me no closer - it's always BaseObject objects named "Object" which are clearly not my object but some sort of cached copies. I am trying to get my actual object (e.g., an object named "Cube" in the Object Manager's node hierarchy), so that I can access its texture tag and get the per object per texture tag specific user data I am looking for in order to properly set the color that my shader returns from its Output() function when called for that object to display it in a Viewport.

    Is there any possible way to get from a shader to a texture tag (or a BaseObject that hosts that tag) during calls to Output() while an object is being drawn in a Viewport, so that in Output() I can use some user data from this texture tag to set the color vector that my custom shader returns, similar to how I do it in the code above which works for renders. Perhaps my understanding is flawed and the Output() function is not what Viewports use to texture objects, but then where is the correct place to do this in my ShaderData derived custom shader and what code do I need to do this?

    As shown in the shader usage example in this post, my custom shader would enable me to get each object's color in the Viewport to match the color as specified in the user data associated with its assigned Texture tag and make each object get colored differently, assuming my custom shader is used in a material's Color layer's Texture slot.

    Of course, the ultimate goal of this exercise is to be able to use the custom shader in any place of a material where a shader can be placed (i.e., any Texture property in any channel of the material) and be able to customize the material on a per object texture tag user data basis.

    Update with additional info:

    Over the weekend, it has become clear to me that my custom shader's override of the ShaderData::Draw() function, gets called when an object that is assigned a material with my shader in a Texture Tag, gets refreshed and/or drawn in a Cinema 4D Viewport. What is not clear at all is how do I communicate any object texure tag specific customizations from my shader during the call to Draw(), assuming that this is the right place to do that for purposes of Viewport display of an object's material.

    To be clear, unlike the Output() member function which returns a Color Vector containing the color calculated by my custom shader, based on the input parameters supplied to Output(), namely: BaseShader* sh, ChannelData* cd, the Draw() function only returns a Bool success or failure value.

    It is not clear to me, assuming of course that the Draw() function is the right place to make per object per texture tag changes/updates on behalf of an object and based on my shader's custom rules, how I go about communicating this information back to Cinema 4D, so that the object can be properly drawn in a Viewport.

    Perhaps this can be done by making changes to some member variables of the objects, pointers to which are passed to Draw(). That is: BaseShader * sh, BaseObject *op, BaseTag *tag, BaseDraw *bd, BaseDrawHelp *bh .

    If this is the right way to do it. It is not clear to me what changes to make to what members of those objects, or what member functions to call using the pointers to the various objects passed to Draw(), in order to send a Color vector back to Cinema 4D so that a particular object in the scene can be drawn in a custom way using a material that holds my shader in the Texture property of its Color layer.



  • Hi @mikegold10 thanks for reaching out us.

    With regard to your request, you've to distinguish two cases:

    • during a rendering process the public VolumeData member of the ChannelData (not of the BaseShader as shown in your code) is valid, you can retrieve the linked BaseTag and, from that, you can access you userdata attached to the TextureTag to find the color of interest
    • outside a rendering process, you can use the ShaderData::Draw() to find the BaseTag and, from that, you can access you userdata attached to the TextureTag to find the color of interest.

    Best, R



  • @r_gigante

    I understand what you are saying in your bullet point 1:

    • during a rendering process the public VolumeData member of the ChannelData (not of the BaseShader as shown in your code) is valid, you can retrieve the linked BaseTag and, from that, you can access you userdata attached to the TextureTag to find the color of interest

    I made a typo when I used pShaderData several times, both in the code which I wrote from memory and the original post. Obviously, there is no *VolumeData vd member variable in the BaseShader class, so where I originally typed pShaderData, I was referring to pChannelData - my apologies.

    I have edited the original post and corrected this. I just want to add that I have no issue during the rendering process, since I do exactly what you described in your first bullet point (using pChannelData of course). It's "outside a rendering process" where I run into problems and so let's go to your bullet point 2:

    • outside a rendering process, you can use the ShaderData::Draw() to find the BaseTag and, from that, you can access you userdata attached to the TextureTag to find the color of interest.

    The issue is not one of finding the color of interest in Draw(), this part is easy! The issue is giving per object tag based color information back to Cinema 4D at "the right time" for this information to be used to draw in Viewports that show the object (and by the way where does this happen??).

    During renders, within the Output() function, it is clear that you return the color vector from your shader and this is how you tell Cinema 4D what color your shader returns.

    But, during Viewport drawing, it is not clear how to give this color information from a custom shader (after getting color information based on the BaseTag parameter to Draw()) back to Cinema 4D, so that it uses it as part of the Material of an object.

    Unlike the Output() function, Draw() returns a boolean value, not a ColorVector, so how do I give a ColorVector back to Cinema 4D so that it knows how to draw an object, one that has a TextureTag using a material with my shader, correctly in a Viewport?

    That's my dilemma, having solved the render question.

    Instead of seeing this in the viewport (with red indicating that my shader could not get object info for purposes of viewport display and the default error (exception) color is being used, because I do not know how to supply the per object color back to Cinema 4D [presumably in the call to Draw()]):

    Incorrect Viewport Display
    Incorrect Viewport Display

    I want to see this - objects get correctly colored, after you explain to me how to perform Step #2, below:

    Inside the call to Draw():

    Step 1) Fetch the color info from the object's texture tag's user data (how to do this is obvious)
    Step 2) Hand this information to Cinema 4D inside Draw() (or where else??), so that it can use it to correctly draw the object in a Viewport

    Correct Viewport Display
    Correct Viewport Display

    Thanks for your help and advice,

    Michael



  • I think what @r_gigante was implying, is that you should establish a fallback node with the arguments of Draw, upon which you can rely outside of fully populated rendering informations in Output.

    Which should work, but admittedly is not the prettiest solution.

    Cheers,
    zipit



  • @zipit I tried this route and it doesn't work, at least not in the way I expected, because for this to work, it is critical that the ordering between Draw() and Output() functions across all object nodes and their respective tags in the node hierarchy, has to be in a certain well-defined and compatible (with this idea) way. Otherwise, the color you cache in Draw() based on the TextureTag received is not necessarily the correct color when Output() gets called (and if Output() gets called, because it almost never called unless you render, but Draw() gets called a lot).

    The reason things don't work as expected using this method of caching color info in Draw(), to be used later in Output (if object info is not available), is because either the object changed or the "current" texture tag is different by the time Output() is called, but often Draw() has not yet been called to update the cached color info. Also, aside from rendering, as I mentioned previously, calls to output are far and few in between. For example, if you use the mouse to move or rotate objects in a view, only Draw() gets called (for each texture tag/object), with no subsequent call to Output() as far as I can tell.



  • Hi,

    jeah, now that you mention it, you are absolutely right, I did not think of the fact that your shader is object dependent. Anyways, I was only mentioning this, because I was under the impression that there was a misunderstanding between you two. Now I am also a bit unsure how @r_gigante meant this part.

    It simply might be that this is not possible without deeper access (the mograph color shader thingy does something similar). I also always thought that accessing the geometry for which your shader is being sampled is unnecessarily hard in Cinema (compared to how easy it is in other environments). Maybe a point for improvement in the future.

    Cheers,
    zipit



  • @zipit I am hopeful that @r_gigante will take a look at my reply to his post and address the question in Step 2 of said post:

    How do you "Hand color information to Cinema 4D inside Draw() (or where else??), so that it can use it to correctly draw the object in a Viewport" ?



  • Hi @mikegold10 sorry for the late reply, there is no way to define the color of an object, you should just draw the object yourself with the custom color.
    Here an example that reads the TextureName and sets the color according to this textureTag. Not very safe but you get the idea.
    However, as you can see this is not the most optimal because the object draws self but the shader also draws the object... So you have an object draw 2 times but this is the only way (and also why the variation shader doesn't support Viewport visualization.

    virtual Bool Draw(BaseShader* sh, BaseObject* op, BaseTag* tag, BaseDraw* bd, BaseDrawHelp* bh)
    {
    	iferr_scope_handler{
    		return false;
    	};
    
    	bd->SetMatrix_Screen();
    
    	Float f = maxon::String(tag->GetName()).ToFloat() iferr_return;
    	bd->DrawObject(bh, op, DRAWOBJECT::USE_CUSTOM_COLOR, DRAWPASS::OBJECT, op->GetUp(), Vector(f));
    
    	return true;
    }
    

    Hope this helps.
    Cheers,
    Maxime.



  • Hi Maxime,

    Thanks for confirming that I was not missing something in the docs and this is just not possible.


Log in to reply