A hardware shading node plug-in example

 
 
 

The hwPhongShader node plug-in is available in the Maya API Devkit. In this example, a cube-environment map is used to perform per pixel Phong shading. The light direction is currently fixed at the eye position.

Initializing and un-initializing the Plug-in

Initializing the plug-in is a straight forward process. In initialize, the swatch classification string is built and the node and drag and drop behavior classes are registered. Note that the drag and drop behavior does not require a unique MTypeId. A simple text string to name the behavior is sufficient.

MStatus initializePlugin( MObject obj )
{
    MStatus status;
    const MString& 
    swatchName =	MHWShaderSwatchGenerator::initialize();
    const MString UserClassify( "shader/surface/utility/:swatch/"+swatchName );
    MFnPlugin plugin( obj, "Autodesk", "4.5", "Any");
    status = plugin.registerNode( "hwPhongShader", hwPhongShader::id,
        hwPhongShader::creator, hwPhongShader::initialize,
        MPxNode::kHwShaderNode, &UserClassify );
    if (!status) {
        status.perror("registerNode");
        return status;
    }
    plugin.registerDragAndDropBehavior("hwPhongShaderBehavior",
        hwPhongShaderBehavior::creator);
    return MS::kSuccess;
}
    

Un-initializing the plug-in de-registers the Phong node and the drag and drop behavior.

MStatus uninitializePlugin( MObject obj )
{
    MStatus status;
    MFnPlugin plugin( obj );
    // Unregister all chamelion shader nodes
    plugin.deregisterNode( hwPhongShader::id );
    if (!status) {
        status.perror("deregisterNode");
        return status;
    }
    plugin.deregisterDragAndDropBehavior("hwPhongShaderBehavior");
    return MS::kSuccess;
}
    

Initializing the node

The hardware shader plug-in’s initialize() method’s attributes that are used in the Dependency Graph are pre-configured. Standard color attributes are added and flagged with properties based on how they are meant to work. Attributes are first created, then added and then finally relationships between the attributes are set. This example configures attributes as cached and internal for performance.

MStatus hwPhongShader::initialize()
{
    MFnNumericAttribute nAttr; 
    // Create input attributes
    aColor = nAttr.createColor( "color", "c");
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.1f, 0.1f, 0.1f);
    nAttr.setCached( true );
    nAttr.setInternal( true );

    aDiffuseColor = nAttr.createColor( "diffuseColor", "dc" );
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(1.f, 0.5f, 0.5f);
    nAttr.setCached( true );
    nAttr.setInternal( true );

    aSpecularColor = nAttr.createColor( "specularColor", "sc" );
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0.5f, 0.5f, 0.5f);
    nAttr.setCached( true );
    nAttr.setInternal( true );

    // This is defined as a point, so that users can easily enter
    // values beyond 1.
    aShininess = nAttr.createPoint( "shininess", "sh" );
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(100.0f, 100.0f, 100.0f);
    nAttr.setCached( true );
    nAttr.setInternal( true );

    aGeometryShape = nAttr.create( "geometryShape", "gs", MFnNumericData::kInt );
    nAttr.setStorable(true);
    nAttr.setKeyable(true);
    nAttr.setDefault(0);
    nAttr.setCached( true );
    nAttr.setInternal( true );

    // create output attributes here
    // outColor is the only output attribute and it is inherited
    // so we do not need to create or add it.
    //
    // Add the attributes here
    addAttribute(aColor);
    addAttribute(aDiffuseColor);
    addAttribute(aSpecularColor);
    addAttribute(aShininess);
    addAttribute(aGeometryShape);

    attributeAffects (aColor,			outColor);
    attributeAffects (aDiffuseColor,	outColor);
    attributeAffects (aSpecularColor,	outColor);
    attributeAffects (aShininess,		outColor);

    return MS::kSuccess;
}

Compute method

The following is a description of the simple implementation of compute() on this class. This method is configured to only handle the outColor attribute. Any other attributes passed to this method will result in MS::kUnknownParameter being returned, which will cause Maya to handle these attributes. Otherwise, the attribute aDiffuseColor is accessed from the datablock and its value is used to set the out color.

MStatus hwPhongShader::compute(
                               const MPlug& plug,
                               MDataBlock& block ) 
{ 
    if ((plug != outColor) && (plug.parent() != outColor))
        return MS::kUnknownParameter;

    MFloatVector & color = block.inputValue( aDiffuseColor ).asFloatVector();
    // set output color attribute
    MDataHandle outColorHandle = block.outputValue( outColor );
    MFloatVector& outColor = outColorHandle.asFloatVector();
    outColor = color;
    outColorHandle.setClean();
    return MS::kSuccess;
}

By implementing this method, the hardware shader node plug-in will be visible in software rendering. If software rendering is not required, there is no need to implement this method.

Bind, draw and unbind methods

The bind() and glBind() methods follow the same strategy. If required attributes have changed or the Phong texture has not been set, then the Phong texture is initialized.

MStatus	hwPhongShader::bind(const MDrawRequest& request, M3dView& view)

{
    if (mAttributesChanged || (phong_map_id == 0))
    {
        init_Phong_texture ();
    }
    return MS::kSuccess;
}


MStatus	hwPhongShader::glBind(const MDagPath&)
{
    if ( mAttributesChanged || (phong_map_id == 0))
    {
        init_Phong_texture ();
    }

    return MS::kSuccess;
}
        

The unbind() and glUnbind() methods in this example only return MS::kSuccess since there is an alternate way written for releasing resources. In the hwPhongShader examples, messages are checked via the API and resources are released before events such as file new, file open and file reference. Please consult the full example code for more details.

MStatus	hwPhongShader::unbind(const MDrawRequest& request, M3dView& view)
{
    // The texture may have been allocated by the draw; it's kept
    // around for use again. When scene new or open is performed this
    // texture will be released in releaseEverything().
    return MS::kSuccess;
}

MStatus	hwPhongShader::glUnbind(const MDagPath&)
{
    // The texture may have been allocated by the draw; it's kept
    // around for use again. When scene new or open is performed this
    // texture will be released in releaseEverything().
    return MS::kSuccess;
}

The geometry() and glGeometry() methods are set up similarly. Each makes a call to the interface independent draw() method to avoid duplicating the draw code.

MStatus	hwPhongShader::geometry( const MDrawRequest& request,
                                M3dView& view,
                                int prim,
                                unsigned int writable,
                                int indexCount,
                                const unsigned int * indexArray,
                                int vertexCount,
                                const int * vertexIDs,
                                const float * vertexArray,
                                int normalCount,
                                const float ** normalArrays,
                                int colorCount,
                                const float ** colorArrays,
                                int texCoordCount,
                                const float ** texCoordArrays)
{
    MStatus stat = MStatus::kSuccess;
    if (mGeometryShape != 0)
        drawDefaultGeometry();
    else
        stat = draw( prim, writable, indexCount, indexArray, vertexCount,
        vertexIDs, vertexArray, normalCount, normalArrays,
        colorCount, colorArrays, texCoordCount, texCoordArrays);
    return stat;
}

MStatus	hwPhongShader::glGeometry(const MDagPath & path,
                                  int prim,
                                  unsigned int writable,
                                  int indexCount,
                                  const unsigned int * indexArray,
                                  int vertexCount,
                                  const int * vertexIDs,
                                  const float * vertexArray,
                                  int normalCount,
                                  const float ** normalArrays,
                                  int colorCount,
                                  const float ** colorArrays,
                                  int texCoordCount,
                                  const float ** texCoordArrays)
{
    MStatus stat = MStatus::kSuccess;
    if (mGeometryShape != 0)
        drawDefaultGeometry();
    else
        stat = draw( prim, writable, indexCount, indexArray, vertexCount,
        vertexIDs, vertexArray, normalCount, normalArrays,
        colorCount, colorArrays, texCoordCount, texCoordArrays);
    return stat;
}
        

Drawing

The draw() method parameters are used to pass information to OpenGL for rendering the information to screen. It is very important to note that the indexArray is used to access information from the other arrays.

MStatus	hwPhongShader::draw(int prim,
                            unsigned int writable,
                            int indexCount,
                            const unsigned int * indexArray,
                            int vertexCount,
                            const int * vertexIDs,
                            const float * vertexArray,
                            int normalCount,
                            const float ** normalArrays,
                            int colorCount,
                            const float ** colorArrays,
                            int texCoordCount,
                            const float ** texCoordArrays)
{
    if ( prim != GL_TRIANGLES && prim != GL_TRIANGLE_STRIP)	{
        return MS::kFailure;
    }

    {
        glPushAttrib ( GL_ENABLE_BIT );
        glDisable ( GL_LIGHTING );
        glDisable ( GL_TEXTURE_1D );
        glDisable ( GL_TEXTURE_2D );

        // Setup cube map generation
        glEnable ( GL_TEXTURE_CUBE_MAP_EXT );
        glBindTexture ( GL_TEXTURE_CUBE_MAP_EXT, phong_map_id );
        glEnable ( GL_TEXTURE_GEN_S );
        glEnable ( GL_TEXTURE_GEN_T );
        glEnable ( GL_TEXTURE_GEN_R );
        glTexGeni ( GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
        glTexGeni ( GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
        glTexGeni ( GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );

        // Could modify the texture matrix here to do light tracking...
        glMatrixMode ( GL_TEXTURE );
        glPushMatrix ();
        glLoadIdentity ();
        glMatrixMode ( GL_MODELVIEW );
    }

        // Draw the surface.
        //
    {
        glPushClientAttrib ( GL_CLIENT_VERTEX_ARRAY_BIT );
        glEnableClientState( GL_VERTEX_ARRAY );
        glEnableClientState( GL_NORMAL_ARRAY );

        glVertexPointer ( 3, GL_FLOAT, 0, &vertexArray[0] );
        glNormalPointer ( GL_FLOAT, 0, &normalArrays[0][0] );

        glDrawElements ( prim, indexCount, GL_UNSIGNED_INT, indexArray );

        // The client attribute is already being popped. You
        glPopClientAttrib();
    }

    {
        glMatrixMode ( GL_TEXTURE );
        glPopMatrix ();
        glMatrixMode ( GL_MODELVIEW );

        glDisable ( GL_TEXTURE_CUBE_MAP_EXT );
        glDisable ( GL_TEXTURE_GEN_S );
        glDisable ( GL_TEXTURE_GEN_T );
        glDisable ( GL_TEXTURE_GEN_R );

        glPopAttrib();
    }

        return MS::kSuccess;
}

Drawing the swatch

Drawing the swatch involves implementing the virtual renderSwatchImage() and using the MHardwareRenderer class in conjunction with MGeometryData and OpenGL to draw the image. The MImage passed to renderSwatchImage() contains information on the width and height of the output that is required.

MStatus hwPhongShader::renderSwatchImage( MImage & outImage )
{
    MStatus status = MStatus::kFailure;

    // Get the hardware renderer utility class
    MHardwareRenderer *pRenderer = MHardwareRenderer::theRenderer();
    if (pRenderer)
    {
        const MString& backEndStr = pRenderer->backEndString();

        // Get geometry
        // ============
        unsigned int* pIndexing = 0;
        unsigned int numberOfData = 0;
        unsigned int indexCount = 0;
        MHardwareRenderer::GeometricShape gshape =
            MHardwareRenderer::kDefaultSphere;
        if (mGeometryShape == 2)
        {
            gshape = MHardwareRenderer::kDefaultCube;
        }
        else if (mGeometryShape == 3)
        {
            gshape = MHardwareRenderer::kDefaultPlane;
        }
        MGeometryData* pGeomData =
            pRenderer->referenceDefaultGeometry( gshape, numberOfData, pIndexing, indexCount );
        if( !pGeomData )
        {
            return MStatus::kFailure;
        }

        // Make the swatch context current
        // ===============================
        //
        unsigned int width, height;
        outImage.getSize( width, height );
        unsigned int origWidth = width;
        unsigned int origHeight = height;

        MStatus status2 = pRenderer->makeSwatchContextCurrent( backEndStr,
            width, height );

        if( status2 == MS::kSuccess )
        {
            // NOTE: Must be called after makeSwatchContextCurrent()
            glPushAttrib ( GL_ALL_ATTRIB_BITS );

            // Get camera
            // ==========
            {
                // Get the camera frustum from the API
                double l, r, b, t, n, f;
                pRenderer->getSwatchOrthoCameraSetting( l, r, b, t, n, f );

                glMatrixMode(GL_PROJECTION);
                glLoadIdentity();
                glOrtho( l, r, b, t, n, f );
                glMatrixMode(GL_MODELVIEW);
                glLoadIdentity();
                // Rotate the cube a bit so we don't see it head on
                if (gshape == MHardwareRenderer::kDefaultCube)
                    glRotatef( 45, 1.0, 1.0, 1.0 );
                else if (gshape == MHardwareRenderer::kDefaultPlane)
                    glScalef( 1.5, 1.5, 1.5 );
            				else
                                glScalef( 1.0, 1.0, 1.0 );
            }

                // Draw The Swatch
                // ===============
                drawTheSwatch( pGeomData, pIndexing, numberOfData, indexCount );

                // Read pixels back from swatch context to MImage
                // ==============================================
                pRenderer->readSwatchContextPixels( backEndStr, outImage );

                // Double check the outing going image size as image resizing
                // was required to properly read from the swatch context
                outImage.getSize( width, height );
                if (width != origWidth || height != origHeight)
                {
                    status = MStatus::kFailure;
                }
                else
                {
                    status = MStatus::kSuccess;
                }

                glPopAttrib();
        }
        else
        {
            pRenderer->dereferenceGeometry( pGeomData, numberOfData );
        }
    }
    return status;
}

The method drawTheSwatch() is invoked from the renderSwatchImage() method. It performs the OpenGL drawing on the image.

void			
hwPhongShader::drawTheSwatch( MGeometryData* pGeomData,
                             unsigned int* pIndexing,
                             unsigned int numberOfData,
                             unsigned int indexCount )
{
    MHardwareRenderer *pRenderer = MHardwareRenderer::theRenderer();
    if( !pRenderer )	return;
    if ( mAttributesChanged || (phong_map_id == 0))
    {
        init_Phong_texture ();
    }

    // Get the default background color
    float r, g, b, a;
    MHWShaderSwatchGenerator::getSwatchBackgroundColor( r, g, b, a );
    glClearColor( r, g, b, a );
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LEQUAL);
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    glDisable ( GL_LIGHTING );
    glDisable ( GL_TEXTURE_1D );
    glDisable ( GL_TEXTURE_2D );
    {
        glEnable ( GL_TEXTURE_CUBE_MAP_EXT );
        glBindTexture ( GL_TEXTURE_CUBE_MAP_EXT, phong_map_id );
        glEnable ( GL_TEXTURE_GEN_S );
        glEnable ( GL_TEXTURE_GEN_T );
        glEnable ( GL_TEXTURE_GEN_R );
        glTexGeni ( GL_S, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
        glTexGeni ( GL_T, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
        glTexGeni ( GL_R, GL_TEXTURE_GEN_MODE, GL_NORMAL_MAP_EXT );
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_WRAP_T, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_CUBE_MAP_EXT, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

        glTexEnvi ( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );

        // Could modify the texture matrix here to do light tracking...
        glMatrixMode ( GL_TEXTURE );
        glPushMatrix ();
        glLoadIdentity ();
        glRotatef( 5.0, -1.0, 0.0, 0.0 );
        glRotatef( 10.0, 0.0, 1.0, 0.0 );
        glMatrixMode ( GL_MODELVIEW );
    }

        // Draw default geometry
    {
        if (pGeomData)
        {
            glPushClientAttrib ( GL_CLIENT_VERTEX_ARRAY_BIT );

            float *vertexData = (float *)( pGeomData[0].data() );
            if (vertexData)
            {
                glEnableClientState( GL_VERTEX_ARRAY );
                glVertexPointer ( 3, GL_FLOAT, 0, vertexData );
            }
            float *normalData = (float *)( pGeomData[1].data() );
            if (normalData)
            {
                glEnableClientState( GL_NORMAL_ARRAY );
                glNormalPointer ( GL_FLOAT, 0, normalData );
            }

            if (vertexData && normalData && pIndexing )
                glDrawElements ( GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, pIndexing );

            glPopClientAttrib();

            // Release data references
            pRenderer->dereferenceGeometry( pGeomData, numberOfData );
        }
    }

    {
        glMatrixMode ( GL_TEXTURE );
        glPopMatrix ();
        glMatrixMode ( GL_MODELVIEW );
        glDisable ( GL_TEXTURE_CUBE_MAP_EXT );
        glDisable ( GL_TEXTURE_GEN_S );
        glDisable ( GL_TEXTURE_GEN_T );
        glDisable ( GL_TEXTURE_GEN_R );
    }
}

Creative Commons License Except where otherwise noted, this work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License