[Closed] Using RenderBitmap in C#
I’m trying to render out an ITexMap to a bitmap on disk. I’d like this to work exactly the same as selecting the map in the Slate Material Editor and going to Utilities > Render Map…
There’s very sparse information online for either C# or C++, but I’ve found a few examples, and it seems like this is exactly what you’re supposed to do:
//Step 1: Create bitmap info w/ appropriate values
var bitmapInfo = Global.BitmapInfo.Create();
bitmapInfo.SetWidth(width);
bitmapInfo.SetHeight(height);
bitmapInfo.SetType((int)BitmapType.BMM_TRUE_64); //Note: tried 32 and 24; 32 has no difference, 24 fails w/ error
bitmapInfo.SetFlags((uint)MapFlags.MAP_HAS_ALPHA); //Note: also tried MAP_NOFLAGS
bitmapInfo.SetCustomFlag(0);
bitmapInfo.SetPath(path); //TODO: Need this?
bitmapInfo.SetName(bitmapPath);
//Step 2: Create bitmap from said info
IBitmap bitmap = Max.Global.TheManager.Create(bitmapInfo);
if (bitmap == null)
{
throw new Exception("Unable to create bitmap: " + bitmapInfo.Name);
}
//Step 3: Call RenderBitmap
map.RenderBitmap(0, bitmap, 50f, true); //50 is suggested per https://forums.autodesk.com/t5/3ds-max-programming/exporting-resulting-bitmaptex/td-p/6987392
//Step 4: Save bitmap to disk
var openResult = bitmap.OpenOutput(bitmapInfo);
var writeResult = bitmap.Write(bitmapInfo, 0);
var closeResult = bitmap.Close(bitmapInfo, (int)BitmapClose.BMM_CLOSE_COMPLETE);
var deleteResult = Max.Global.TheManager.DelBitmap(bitmap);
Max.Log($"Rendered bitmap: open:{openResult}, write:{writeResult}, close:{closeResult}, del:{deleteResult}");
Here’s the problem: it works in that it creates a file, but it creates the wrong file. When I test it with a simple Bitmap map, it gives me a black and white version of the original bitmap referenced by the Bitmap map. It doesn’t take into account UV scaling, and it doesn’t take into account other things like output invert.
Below is the original image (mushroom), result of using Slate Material Editor > Utilities > Render Map… (4 inverted mushrooms), and my code above (black and white single mushroom).
Original:
Utilities > Render Map… (Correct):
My code (incorrect):
I’ve been trying every tweak I can, and checking and rechecking the examples online. I can’t see anything that I’m doing differently.
Any idea why this code isn’t producing a correct render map?
Alright, I figured out one tiny thing: why it’s outputting black and white (though not how to fix it). Apparently it doesn’t respect the Bitmap’s format when performing the actual RenderBitmap function. Apparently it uses whatever value has been last set in the Slate Material Editor > Utilities > Render Map… window. If you go to that window, and click on Files… (as shown), you’ll get a Render Output File window. If you then click the Setup button, you’ll see the window below, where you can set output format. Whatever you’ve last set here is what is used by the RenderBitmap function in C#.
So now there’s a new mystery: how can you change the output settings of RenderBitmap in C# like you do in this window?
And there’s still the original mystery: why doesn’t the output respect the UV settings, output settings, etc. of the map that you’re trying to render?
Alright, I figured it out. Here it is for the next sorry soul that tries to render maps to disk. But I have to warn you: it’s dark magic.
First up, the problem with not getting UV / Output parameters applied to the image: Under the hood, it appears that RenderBitmap is actually calling Max.CoreInterface.RenderTexmap. This method has more options, so apparently it’s just setting some default values for you, and one of those default values is bake = true. Setting it to false adds in the UVs.
Second, the problem with getting the files written in the correct format. To summarize: with the code I first posted, you’ll get the exact image you want in the bitmap (e.g. if you preview it, it looks correct), but when you write it to disk, it doesn’t respect the values in BitmapInfo, even though you’re supplying BitmapInfo as an argument when you write to disk. After much searching, I discovered that there is a PNGIO library that provides its own settings to all PNGs written to disk, regardless of where you’re doing the writing from. (There are libraries for bmp, tga, etc. – you can find more info in the maxsdk > istdplug.h.)
I found that there’s an interface for accessing this library called IIBitmapIO_Png. After seriously 4 hours of poking through the maxsdk c++ examples and locating magic numbers, I finally figured out how to get an instance of it. Here’s the code:
public IIBitmapIO_Png GetPNGIO()
{
var pngClassID = ObjectFactory.CreateClassID(PNGCLASSID, 0);
IClassEntry pngClassEntry = Global.DllDir.Instance.ClassDir.FindClassEntry(BMM_IO_CLASS_ID, pngClassID);
var pngClassDesc = pngClassEntry.FullCD;
//var pngioInterfaceID = ObjectFactory.CreateInterfaceID(0x2c65369, 0x10be38b0); //from istdplug.h
//var bitmapIO_PngInterfaceID = ObjectFactory.CreateInterfaceID(0x1d7c41db, 0x328c1142); //from somewhere in C++ SDK
//Note: this is incorrectly labeled as BMPIO_INTERFACE in istdplug.h
var pngioInterfaceID = ObjectFactory.CreateInterfaceID(0x374F288F, 0x19E460D6); //(927934607, 434397398); //Got through trial and error iterating through pngClassDesc's interfaces
var pngioInterface = pngClassDesc.GetInterface(pngioInterfaceID);
IIBitmapIO_Png pngio = Global.IBitmapIO_Png.Marshal(pngioInterface.NativePointer);
return pngio;
}
So once you have that, here’s how you put it all together:
//map.RenderBitmap(0, bitmap, 50f, true); //What I was doing before; apparently has bake setting set to true
Max.CoreInterface.RenderTexmap(map, bitmap, 50f, true, true, 0f, 0, false); //Use this instead
//Add this somewhere before you call bitmap.Write(). Immediately after RenderTexmap is fine.
var pngMgr = Max.Utils.GetBitmapIO_Png();
pngMgr.Alpha = true;
pngMgr.GetType_ = (int)BitmapType.BMM_TRUE_24;
Everything else from the code I posted above works correctly.
I have the same issue using render to texture via maxscript.
There’s no way to automate it that I’ve found; to specify the exact bitdepth you want. It uses whatever was set manually the last time in the file save dialog. Super annoying.
It looks like you can also access pngio using Maxscript, and it’s actually a bit easier than in C#:
so, e.g.:
pngio.setType #true24