Implementing Simple Material and Texture Map Plug-ins with Nitrous

Simple Material and Texture Map Display API

Nitrous SDK provides automatic support for displaying material and texture map plug-ins in both the Nitrous viewport mode and in Quicksilver renderings. 3ds Max will create a simple visual representation of these plug-ins with basic colors, diffuse and opacity texture maps which can be rendered quickly, and which will appear the same in both Nitrous viewport mode and in Quicksilver.

The following are the APIs to support simple and fast material and texture map display:

Materials and texture maps with complex parameters or requiring richer visual representations in the Nitrous viewport display or in the Quicksilver renderer can be developed using HLSL (High Level Shader Language) shaders. For more information see the section Porting Advanced Material and Texture Map Plug-ins to Nitrous.

ISimpleMaterial

Any material plug-ins can directly get ISimpleMaterial interface from MtlBase to customize its display in shaded mode. The following code shows how to get the ISimpleMaterial interface.

ISimpleMaterial* pSimpleMaterial = (ISimpleMaterial *)pMtlBase->GetProperty(PROPID_SIMPLE_MATERIAL);

ISimpleMaterial supports basic material and texture color properties such as ambient, diffuse, specular, and emissive, and supports a few basic shading and fill modes. The Nitrous viewport will query the ISimpleMaterial properties when it displays MtlBase plug-ins.

ISimpleMaterial supports two texture map channels: diffuse and opacity. The Nitrous viewport display driver will also set these up on an ISimpleMaterial instance, but it is the plug-in's responsibility to create and manage the lifetime of the texture resource associated with a TexHandle object. The TexHandle can be generated by calling TexHandleMaker::MakeHandle().

ITextureDisplay and DisplayTextureHelper

ITextureDisplay is an interface that allows texture map and material plug-ins to control how their diffuse and opacity textures appear in the Nitrous viewport. This can be useful when a plug-in needs to do custom blending of textures, or when the texture maps have different requirements such as different texture coordinates. Creating and maintaining the handles to these textures is the responsibility of the DisplayTextureHelper interface.

To have its diffuse and opacity texture appear correctly in the Nitrous viewport, the plug-in must create them by implementing ISimpleMaterial::SetTexture(). For this, the plug-in needs to derive from the ITextureDisplay interface and use DisplayTextureHelper::MakeHandle() to implement ITextureDisplay::SetupTextures() in that interface.

Impact on plug-ins

In practice, all but the most simple materials and texture map plug-ins will require additional code to provide a correct simple visual representation in the Nitrous viewport. The following scenarios provide more details.

Scenario 1: Plug-in supports the display of at most one texture map

If the material or texture map plug-in supports displaying only one texture map, Nitrous will use that map in the diffuse texture map channel by default. If this default behavior is acceptable, there is no additional code required by the plug-in other than the regular code for supporting the display of the texture map. See Scenario 2 below if the default behavior is not acceptable. Note that in order for the plug-in to be able to override Nitrous' default support, it will need to return TRUE from an overridden implementation of the MtlBase::SupportsMultiMapsInViewport() virtual method even if it has only one map.

The key methods a plug-in should implement for supporting one texture map are outlined below:

class Scenario1 : public Texmap 
{ 
    TexHandle* texHandle;
    // Details elided for brevity 
}

// Supports displaying textures
BOOL Scenario1::SupportTexDisplay() {return TRUE;}

// Does not support displaying multiple textures
BOOL Scenario1::SupportsMultiMapsInViewport() {return FALSE;}

// Helper method
void Scenario1::DiscardTexHandle() 
{
    if (texHandle) 
    {   
        texHandle->DeleteThis();
        texHandle = NULL;
    }
}

DWORD_PTR Scenario1::GetActiveTexHandle(TimeValue t, TexHandleMaker& maker) 
{
    if (texHandle) 
    {
        if (texHandleValid.InInterval(t))
                return texHandle->GetHandle();
            else 
            DiscardTexHandle();
        }
    texHandle = maker.MakeHandle(GetVPDisplayDIB(t, maker, texHandleValid));
    return texHandle->GetHandle();
}

The class Scenario1 above uses Texmap::GetVPDisplayDIB() to generate a representation of itself. The returned BITMAPINFO is then used by the TexHandleMaker::MakeHandle() to access the raw bitmap data and generate the texture resource needed by the display system. If the texture map is procedural in nature, like marble, it is important to note that the generated result will not look the same as the one rendered by a production renderer; it is a mere interpretation of the procedural effect. For a more accurate viewport representation of the plug-in, HLSL shaders must be used.

Scenario 2: Plug-in supports displaying multiple texture maps

To display multiple texture maps in the viewport, a plug-in must override MtlBase::SupportsMultiMapsInViewport() to return TRUE. If it does not, ITextureDisplay::SetupTextures() will not be called.

It is important to note that ITextureDisplay supports only the diffuse and opacity texture maps, although the plug-in might support more. For advanced texture map support in the Nitrous viewport, the HLSL shader language needs to be used.

The key code changes a plug-in should implement for supporting multiple texture maps in the Nitrous viewport mode are outlined below:

Scenario 3: Plug-in's viewport and rendered representation differs

It is common for a material plug-in to change its appearance in the viewport compared to its rendered representation, as it might want to display debugging information or additional visual hints in the viewport. In this case the plug-in will want to override the default representation that Nitrous created for it by implementing ITextureDisplay::SetupTextures() as in Scenario 2. In addition to setting up the texture maps, the plug-in can call any of the Set functions in ISimpleMaterial to override the various colors, shade, and fill modes Nitrous has set automatically.

Texture resource management

It is important to note that in all of the above scenarios the texture resource used by the Nitrous viewport display system is maintained as a TexHandle instance. The plug-in must create a TexHandle for each texture map that is to be displayed in the viewport, even if Nitrous' default texture map handling is not overwritten by the plug-in (see Scenario 1 above). If changes in the plug-in's parameters render the texture map invalid, then the TexHandle must be deleted by calling TexHandle::DeleteThis(), which ensures texture resources that may be used by the GPU are freed. Next time the system requests a TexHandle, or when setting up the textures for a ISimpleMaterial instance, if the TexHandle is invalid, the plug-in must create a new handle by calling TexHandleMaker::MakeHandle().

For an example that demonstrates the use of these interfaces, see maxsdk/samples/materials/stdmtl2.cpp .