Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    C++ Shader Plug-In Best Practices

    Cinema 4D SDK
    c++ r21 sdk windows
    3
    6
    911
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • W
      wuzelwazel
      last edited by

      Hello there,

      I've taken my first leap into the C++ SDK and experienced some early triumphs and defeats. I'm working slowly on a shader starting from the Xbitmapdistortion SDK example. I have the SDK documentation open in about 20 tabs but still feel unsure about how I'm approaching what I'd like to accomplish.

      I'm hoping to write a bare bones Ptex shader. I don't need to deal with most of the issues that surround Ptex as my only goal is to get a shader that I can bake into UDIMs 😁 I figured I'd start the effort by generating per-polygon UV coordinates. It took me a while but I came up with a working solution which exists mostly in the Output method of my young shader:

      Vector PtexData::Output(BaseShader* chn, ChannelData* cd)
      {
      	if (cd->vd == nullptr) // It took me a while to realize I needed this here :)
      		return Vector(0.0);
      
      	RayPolyWeight weight;
      	RayHitID hit = cd->vd->lhit;
      	Int32 faceid = hit.GetPolygon();
      	/* I'm not currently using the faceid, but I will need it to index into the sub-images
      	of the Ptex file; is this a bad idea? Is there another way?*/
      
      	cd->vd->GetWeights(hit, cd->vd->p, &weight);
      
      	Vector coord = Vector(weight.wa + weight.wb, weight.wb + weight.wc, 0.0);
      
      	return coord;
      }
      

      poly_uvw.png

      So first off I suppose I'm wondering: have I done anything horribly wrong or inefficient in the above code? That leads me to my next question: what is the best practice for loading an external texture (in this case Ptex)? I was not planning to build a Ptex file handler but instead to 'brute force' this and load the required Ptex images into a PtexCache in my InitRender() and use Ptex's getPixel() method to 'sample' directly using the coordinates I've generated. I have several concerns with this approach (do I need to consider MIP levels? even if my only goal is to bake the shader?). Unfortunately I don't even know enough to know whether they're valid concerns or not 🤷

      Does anyone have a suggestion for an acceptable (not necessarily best) approach for doing this?

      Finally, could anyone recommend additional avenues for getting up to speed on modern C++ more generally (this is the first time I've touched C++ since 2001) and the Cinema 4D SDK in particular?

      1 Reply Last reply Reply Quote 3
      • ManuelM
        Manuel
        last edited by Manuel

        Hello,

        Loading your texture in your InitRender function is a good approche.
        The Output function is called for each pixel in the scene, you should avoid calculation/process as much as possible to avoid your render time to be too slow.

        About how to create such a shader i'm afraid we are somehow a bit limited in that area (in the SDK team i mean)

        Be sure to handle the message MSG_GETALLASSET properly. This will allow the texture to be collected if needed.

        If you have more specific questions, we can probably ask the dev team.

        About the modern c++ question we don't have too much about this in our documentation :

        • c++ techniques
        • Programming advices
        • On our Code style guide you have some comment about using c++11

        Cheers,
        Manuel

        MAXON SDK Specialist

        MAXON Registered Developer

        W 1 Reply Last reply Reply Quote 0
        • W
          wuzelwazel @Manuel
          last edited by wuzelwazel

          @m_magalhaes

          Thanks Manuel!

          I realized that there are a couple of specific pieces of the puzzle that I'm not sure how to handle.

          I need to pass file paths between Cinema 4D and Ptex. I think the most straightforward way to do this is to use a CString to go between them, but I ran across several potential ways of approaching this. What is the preferred method? I'm currently trying this in InitRender():

          String filename = data->GetFilename(PTEX_FILE_PATH).GetString();
          Char* filestring = filename.GetCStringCopy();    // I noticed on a page in the docs that Char* was used as opposed to char* what is the reason for that?
          
          if (filestring[0] != '\0') // is this the best way to check for an empty CString?
          {
              ptexTexture->open(filestring, err, true);
              // ...set up additional data based on loaded texture
          }
          
          DeleteMem(filestring); // The SDK says this is necessary when using the GetCStringCopy() method
          

          Is this correct?

          Additionally, I've realized that in my Output() function I will need access to the object's name in order to determine which Ptex file to sample from. Would this be the way to get that information in the Output() function?

          String name;
          BaseObject* obj = cd->vd->op->link;
          
          if (obj != nullptr)    // If I'm checking for nullptr could I just use if(obj) as in Python?
              name = obj->GetName();
          

          Thanks as always for your awesome support! 😁

          1 Reply Last reply Reply Quote 0
          • P
            PluginStudent
            last edited by

            Hello,

            Char is just a typedef for maxon::Char, which itself is an alias for char. Using the MAXON types makes your code independent from changes to the underlying type and independent of different types on different platforms.

            If you want to check if the string is empty, why don't you simply check the original?

            String filename = GetString();
            if (filename.IsPopulated())
            {
            	// do stuff
            }
            else
            {
            	// do something else
            }
            

            There is no correct or un-correct way of handling the conversion. The question is, what is the safest way of handling this (memory leaks, nullptrs, etc.). You take ownership of the array returned by GetCStringCopy(). There are different ways of handling that ownership.

            maxon::String has the member function GetCString() which returns an array, that stores the C-array:

            const Filename fn { "C:\\some\\file\\somewhere.txt" };
            
            const maxon::String fileString = fn.GetString();
            
            const maxon::BaseArray<maxon::Char> cstr = fileString.GetCString() iferr_return;
            
            const char* arg = cstr.GetFirst();
            TheOtherFunction(arg);
            

            But to use that, you have to deal with error handling, since GetCString() returns a maxon::Result<>.

            W 1 Reply Last reply Reply Quote 0
            • W
              wuzelwazel @PluginStudent
              last edited by

              @PluginStudent

              Thanks for the suggestions. I will definitely use your super obvious (in hindsight) recommendation to check the emptiness of the original String!

              Could you encapsulate briefly why I might or might not want to take ownership of the CString array? I have a gut feeling that using the GetCString() method you suggested is safer but I have no knowledge to back that up 😁 Error handling is definitely something I need to better familiarize myself with.

              Lastly, when would I need to use the maxon namespace? I sort of assumed that these two would be equivalent:

              const String fileString = fn.GetString();
              const maxon::String fileString = fn.GetString(); // significance of namespace here?
              

              Is it a situation where if String also exists in another namespace then not providing the explicit namespace would result in ambiguity for the compiler?

              1 Reply Last reply Reply Quote 0
              • ManuelM
                Manuel
                last edited by

                hello,

                thanks @PluginStudent for the answer here 🙂

                if (obj != nullptr)    // If I'm checking for nullptr could I just use if(obj) as in Python?
                

                The correct way is to check against nullptr, that's a c++11 standard.
                if (obj) could lead to false positive and bugs.

                String is not the same thing as maxon::String. String is the classic API while maxon::String is the Maxon API. You can read more about that in the manual about strings

                Error handling is super eady to use, we got every thing to do so, you should definitely use it. Check Our manual about Error Handling

                Last but not least you can probably use our Url class to handle file or filename. Give it a look

                Cheers,
                Manuel

                MAXON SDK Specialist

                MAXON Registered Developer

                1 Reply Last reply Reply Quote 0
                • First post
                  Last post