Implementing Advanced Material and Texture Map Plug-ins with Nitrous

Advanced Material and Texture Map Display API

Materials and texture maps can use MetaSL programmable shaders to describe their appearance when displayed in the Nitrous viewport, or when rendered with the Quicksilver renderer. Using MetaSL shaders allows for a more realistic and complex visual appearance than when ISimpleMaterial is used to represent materials and texture maps. There are three steps involved in adding MetaSL shader support to a material or texture map plug-in:

  1. Write a MetaSL shader to represent the material or texture map.
  2. Create an IShaderManager instance that represents the shader.
  3. Optionally, implement the IParameterTranslator interface to control the translation of plug-in parameter values to shader parameters.

The materials and texture maps often have submaps (slots) which can be either other materials or texture maps. Furthermore, the user can feed a certain material or texture map parameter such as the diffuse color with the output color of another texture map. Thus several material and texture map plug-ins, each with underlying MetaSL shaders are effectively connected into a shader graph. The following figure shows the 3ds Max material and texture map graph (left) and its corresponding MetaSL shader graph (right).

MetaSL Shader

The full details of writing a MetaSL shader is beyond the scope of this topic, but mental mill can be downloaded from the Autodesk 3ds Max Services and Support web site. You will need a license of 3ds Max 2011 or later to run it.

Once the shader is written, the msl and xmsl files need to be installed into the appropriate directories inside <3dsMax_Root>\NVIDIA\shaders_3rdparty\metaSL.

MetaSL Shader Optimizations

3ds Max optimizes the MetaSL code used by materials and texture maps by compiling the MetaSL shader graph into a DirectX compatible format such as HLSL. To produce efficient GPU code, 3ds Max supports the MetaSL const keyword. This allows the shader graph compiler to inline usages of const parameters and greatly reduce the number of instructions to execute. The drawback is that the shader needs to be recompiled whenever the parameter that is declared as const changes. Currently the system supports only const int and const bool parameters.

IShaderManager

A material or texture map with shaders needs to create an IShaderManager instance for each of their shader. 3ds Max creates the shader graph based on the IShaderManager instances created by the plug-ins.

IShaderManagerCreator::CreateShaderManager() needs to be used by the plug-in to create a IShaderManager instance for each of the shader it supports. 3ds Max will query the plug-in for its IShaderManager through a request for the interface identified by ISHADER_MANAGER_INTERFACE_ID. Typically, a plug-in creates an instance of IShaderManager in response to this request in its override of Animatable::GetInterface(Interface_ID) and deletes it in its destructor by calling IShaderManagerCreator::DeleteShaderManager().

Note that the IShaderManager instances are immutable objects. If the plug-in needs to use a new shader it will have to create a new IShaderManager instance for it.

See maxsdk\include\graphics\ishadermanager.h for a code snippet illustrating how a plug-in would typically manage the lieftime of an IShaderManager instance.

IParameterTranslator

Material and texture map parameter values need to be translated to the underlying shader parameters whenever the plug-in's parameters change. 3ds Max handles this translation automatically for ParamBlock2 based material and texture map parameters, if their names and types match those of the shader. Plug-ins that need control over the translation process can implement the IParameterTranslator interface.

IParameterTranslator::GetParameterValue() allows the plug-in to provide a shader parameter, specified by name and type, with a value returned by the method. This method is called for every shader parameter found when the system loaded the MetaSL shader. See maxsdk\include\graphics\iparametertranslator.h for a code snippet illustrating how a plug-in would typically implement this method.

IParameterTranslator::GetShaderInputParameterName() allows the plug-in to assist 3ds Max in creating the shader graph that corresponds to the material and texture map graph by specifying the input shader parameter name to which the shader representing a submap of the plug-in must be connected. The following code sample illustrates how this method would be implemented to assist in setting up the shader graph in figure above.

bool StandardMaterial::GetShaderInputParameterName(
	SubMtlBaseType type, 
	int subMtlBaseIndex, 
	MSTR& shaderInputParamName
)
{
       DbgAssert(type == MaxSDK::Graphics::IParameterTranslator::SubTexmap); 
 
       if (MaxSDK::Graphics::IParameterTranslator::SubTexmap == type 
	    && 1 == subMtlBaseIndex) // 1 is the diffuse map slot index 
	{
		// Diffuse_Color is the name of this material's shader parameter 
		// we allow the shader of the diffuse map to be connected to
		shaderInputParamName = “Diffuse_Color”; 
		return true;
	}
	return false;
}
3ds Max queries the plug-in for the IParameterTranslator interface by passing IPARAMETER_TRANSLATOR_INTERFACE_ID to its override of Animatable::GetInterface(Interface_ID). If the interface is present on the plug-in, 3ds Max calls its methods to do the parameter translation. 3ds Max automatically detects when a plug-in's parameters, reference structure, or animatable structure changes, and calls the methods on IParameterTranslator to re-translate the plug-in's parameters to the shader.

Use HLSL to Customize the Viewport Display in Realistic Mode

Materials can use programmable shaders to describe their appearance when displayed in the Nitrous viewport and rendered with Quicksilver. Besides MetaSL shader ( See IShaderManager and IParameterTranslator for more details), 3ds Max 2014 supports HLSL shader. This enables the material plug-ins to use a DirectX effect to control how the material displays when Show realistic material in viewport (for this material) is selected.

IHLSLMaterialTranslator

IHLSLMaterialTranslator provides a chance in its UpdateHLSLMaterial() to update the parameters of HLSLMaterialHandle, such as parameter values and map channel values. See HLSLMaterialHandle for more details. Plug-ins derive from IHLSLMaterialTranslator. They rather need to create instances of HLSLMaterialHandle. 3ds Max queries the plug-in for its IHLSLMaterialTranslator through a request for the interface identified by IHLSL_MATERIAL_TRANSLATOR_INTERFACE_ID. Then, gets HLSLMaterialHandle using GetHLSLMaterialHandle() of IHLSLMaterialTranslator. Typically, a plug-in implements GetHLSLMaterialHandle() of IHLSLMaterialTranslator in response to this request, and its override of Animatable::GetInterface(Interface_ID) for IHLSLMaterialTranslator.

The following code snippet shows how a material plug-in might implement IHLSLMaterialTranslator:

#include <./graphics/IHLSLMaterialTranslator.h>

// A material plug-in has HLSLMaterialHandle and needs to update HLSLMaterial parameters.
class MyMtlPlugin : public MtlBase, public MaxSDK::Graphics::IHLSLMaterialTranslator {
    // omitted for brevity
private:
    HLSLMaterialHandle mHLSLMaterialHandle;
public:
    virtual bool UpdateHLSLMaterial(
        const TimeValue t, 
        GraphicFeatureLevel featureLevel)
    {
        mHLSLMaterialHandle.SetFloatParameter(
            _M("myFloatShaderParam"),
            floatValue);
        mHLSLMaterialHandle.SetIntParameter(
            _M("myIntShaderParam"), 
            intValue);
        return true;
    }

    virtual const HLSLMaterialHandle& GetHLSLMaterialHandle(
        GraphicFeatureLevel featureLevel)
    {
        return mHLSLMaterialHandle;
    }

    BaseInterface* GetInterface(Interface_ID iid)
    {

        if (IHLSL_MATERIAL_TRANSLATOR_INTERFACE_ID == iid)
            return static_cast<IHLSLMaterialTranslator*>(this);
        else
            return MtlBase::GetInterface(iid);
    }
}

HLSLMaterialHandle

HLSLMaterialHandle is a class to support HLSL custom material. This class is used for viewport display and Quicksilver. It is used by RenderItemHandle::SetCustomMaterial() to describe HLSL custom material. It can also be used for realistic material display. See IHLSLMaterialTranslator for more information.

The following code snippet shows how to use HLSLMaterialHandle:

HLSLMaterialHandle hMaterial;
hMaterial.Initialize(shaderFileName);
hMaterial.SetFloatParameter(_M("myFloatShaderParam"), floatValue);

Transparency Hints

The Nitrous viewport display system and the Quicksilver renderer optimize the display lists to work with transparency. A new MtlBase::GetTransparencyHint() method is used to get a boolean value that indicates whether transparency is supported at a certain time (a transparency "hint"), and to provide the validity interval for it. The validity of this hint is checked by 3ds Max in each frame.

Once it becomes invalid, MtlBase::GetTransparencyHint() is called again. By default, this method returns TRUE. If the material does not support transparency in any form, then the plug-in's override of this method must return FALSE. This is particularly important for custom materials that use shading languages. Only the shader or the host material knows what is happening internally and whether transparency is affected. Supporting this method helps optimize their drawing and rendering.