- Main Page
- Related Pages
- Modules
- Classes
- Examples

# hwAnisotropicShader_NV20.cpp

    //-
    // ==========================================================================
    // Copyright (C) 1995 - 2006 Autodesk, Inc. and/or its licensors.  All
    // rights reserved.
    //
    // The coded instructions, statements, computer programs, and/or related
    // material (collectively the "Data") in these files contain unpublished
    // information proprietary to Autodesk, Inc. ("Autodesk") and/or its
    // licensors, which is protected by U.S. and Canadian federal copyright
    // law and by international treaties.
    //
    // The Data is provided for use exclusively by You. You have the right
    // to use, modify, and incorporate this Data into other products for
    // purposes authorized by the Autodesk software license agreement,
    // without fee.
    //
    // The copyright notices in the Software and this entire statement,
    // including the above license grant, this restriction and the
    // following disclaimer, must be included in all copies of the
    // Software, in whole or in part, and all derivative works of
    // the Software, unless such copies or derivative works are solely
    // in the form of machine-executable object code generated by a
    // source language processor.
    //
    // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
    // AUTODESK DOES NOT MAKE AND HEREBY DISCLAIMS ANY EXPRESS OR IMPLIED
    // WARRANTIES INCLUDING, BUT NOT LIMITED TO, THE WARRANTIES OF
    // NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR
    // PURPOSE, OR ARISING FROM A COURSE OF DEALING, USAGE, OR
    // TRADE PRACTICE. IN NO EVENT WILL AUTODESK AND/OR ITS LICENSORS
    // BE LIABLE FOR ANY LOST REVENUES, DATA, OR PROFITS, OR SPECIAL,
    // DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES, EVEN IF AUTODESK
    // AND/OR ITS LICENSORS HAS BEEN ADVISED OF THE POSSIBILITY
    // OR PROBABILITY OF SUCH DAMAGES.
    //
    // ==========================================================================
    //+

    //
    // NOTE: PLEASE READ THE README.TXT FILE FOR INSTRUCTIONS ON
    // COMPILING AND USAGE REQUIREMENTS.
    //
    // DESCRIPTION: NV20-specific (Geforce3) sample shader.
    //              This shader is meant to produce a anisotropic shading effect.
    //              It allows the user to change the parameters of a anisotropic
    //              lookup table.
    //
    //  This shader builds on the foundation demonstrated in the
    //  hwUnlitShader.
    //
    //  PS: Thanks go to DAR from nVidia, for his help in making this
    //      shader more robust. ;-)
    //

    #ifdef WIN32
    #pragma warning( disable : 4786 )       // Disable STL warnings.
    #endif

    #include <maya/MIOStream.h>
    #include <math.h>

    #include <maya/MString.h>
    #include <maya/MPlug.h>
    #include <maya/MDagPath.h>
    #include <maya/MDataBlock.h>
    #include <maya/MDataHandle.h>
    #include <maya/MArrayDataHandle.h>
    #include <maya/MFnDependencyNode.h>
    #include <maya/MFnNumericAttribute.h>
    #include <maya/MFnTypedAttribute.h>
    #include <maya/MFnLightDataAttribute.h>
    #include <maya/MFloatVector.h>
    #include <maya/MFnStringData.h>
    #include <maya/MFnPlugin.h>
    #include <maya/MGlobal.h>
    #include <maya/MSceneMessage.h>

    #include <maya/MPoint.h>
    #include <maya/MMatrix.h>
    #include <maya/MVector.h>
    #include <maya/MEulerRotation.h>
    #include <maya/MFnLight.h>
    #include <maya/MFnNonAmbientLight.h>

    // Include NVIDIA's helper libraries.  These libraries have
    // copyright info in them so we cannot release them but we
    // can use them to verify that the API works correctly.
    //
    #include <GL/gl.h>
    #include <GL/glu.h>
    #include <GL/glext.h>

    #define GLH_EXT_SINGLE_FILE
    #include "glh_extensions.h"
    #undef GL_NV_vertex_array_range
    #include "glh_genext.h"
    #include "glh_obs.h"
    using namespace glh;

    #include "hwAnisotropicShader_NV20.h"
    #include "ShadingConnection.h"

    #define TANGENT_INDEX 1 // macro to make normall array indexing a bit clearer

    MTypeId hwAnisotropicShader_NV20::id( 0x00105444 );

    // Lookup table. Fixed at 256x256 resolution.
    /*static*/ const unsigned int hwAnisotropicShader_NV20::lookup_texture_size(256);

    void hwAnisotropicShader_NV20::postConstructor( )
    {
        setMPSafe(false);
    }

    // Static attribute instances.
    //
    MObject  hwAnisotropicShader_NV20::color;
    MObject  hwAnisotropicShader_NV20::colorR;
    MObject  hwAnisotropicShader_NV20::colorG;
    MObject  hwAnisotropicShader_NV20::colorB;

    MObject  hwAnisotropicShader_NV20::roughness;
    MObject  hwAnisotropicShader_NV20::kDiffuse;
    MObject  hwAnisotropicShader_NV20::kSpecular;

    void hwAnisotropicShader_NV20::printGlError( const char *call )
    {
        GLenum error;

        while( (error = glGetError()) != GL_NO_ERROR ) {
            assert(0);
            cerr << call << ":" << error << " is " << (const char *)gluErrorString( error ) << "\n";
        }
    }

    // The Vertex Program for the model shading effect.
    // Meant to be used as an anisotropic shader.
    //
    // Version for point lights with a decay factor
    //
    // CONSTANTS: (c)
    // c0- c3 4x4 ModelView-Projection composite matrix
    // c4     (.5, 1, 0, 0) vector for compute u,v
    // c9     light color
    // c11     light position (in object space)
    // c12    camera position in object space (possibly not normalized)

    // VERTEX REGISTERS (mapped so that standard gl calls work):
    // v0 - position
    // v2 - tangent
    // v3 - primary color

    // REGISTERS:
    //
    // R0 - normalized view (surface-to-camera) direction in object space.
    // R1 - normalized surface tangent in object space.
    // R2 - normalized light (surface-to-light) direction.
    // R3 - temporary computation of u,v values for lookup-texture to be
    //      placed int TEX[0]
    // R4,R5 - temporaries use to compute light decay
    //
    char vertexProgramStringPointDecay[] =
        "!!VP1.0\n"

            // Multiply the vertex coords by the modelview-projection composite matrix,
            // to get clip space coordinates.
            "DP4   o[HPOS].x, c[0], v[0];"
            "DP4   o[HPOS].y, c[1], v[0];"
            "DP4   o[HPOS].z, c[2], v[0];"
            "DP4   o[HPOS].w, c[3], v[0];"

            // Normalize the T (tangent), in case the modelview matrix is not a simple rotation.
            "MOV   R1, v[2];"
            "DP3   R1.w, R1, R1;"
            "RSQ   R1.w, R1.w;"
            "MUL   R1.xyz, R1, R1.w;"

            // Compute the normalized light vector in object space
            "ADD   R2, c[11], -v[0];"
            "DP3   R2.w, R2, R2;"
            "RSQ   R2.w, R2.w;"
            "MUL   R2.xyz, R2, R2.w;"

            // The vertex position, normal and light positions are expressed in object space at
            // this point. We need to find the view direction in object space too.
            "ADD   R0, c[12], -v[0];"       // view direction, from surface to camera.
            "DP3   R0.w, R0, R0;"           // normalize the view direction.
            "RSQ   R0.w, R0.w;"
            "MUL   R0.xyz, R0, R0.w;"

            // Find the texture coordinates to fetch from the toon-param texture:
            //      u = 1/2 (T dot L) + 1/2
            //      v = 1/2 (T dot V) + 1/2
            //
            // a) Do (T dot L) and (T dot V) and store in register R3
            "DP3    R3.x, R1, R2;"
            "DP3    R3.y, R1, R0;"

            // b) Scale by 1/2 and add 1/2 and store as texture coordinates
            //
            "MAD o[TEX0].x, R3.x, c[4].x, c[4].x;"
            "MAD o[TEX0].y, R3.y, c[4].x, c[4].x;"

            // Multiply diffuse lighting into material color.
            // Take into account decay
            "MOV R4, c[9];"
            "MOV R5, c[9];"
            "MUL R5.xyz, R4, R2.w;"
            "MUL o[COL0], v[3], R5;"

            "END";

    // Version for point lights
    //
    // CONSTANTS: (c)
    // c0- c3 4x4 ModelView-Projection composite matrix
    // c4     (.5, 1, 0, 0) vector for compute u,v
    // c9     light color
    // c11     light position (in object space)
    // c12    camera position in object space (possibly not normalized)

    // VERTEX REGISTERS (mapped so that standard gl calls work):
    // v0 - position
    // v2 - tangent
    // v3 - primary color

    // REGISTERS:
    //
    // R0 - normalized view (surface-to-camera) direction in object space.
    // R1 - normalized surface tangent in object space.
    // R2 - normalized light (surface-to-light) direction.
    // R3 - temporary computation of u,v values for lookup-texture to be
    //      placed int TEX[0]
    //
    char vertexProgramStringPoint[] =
        "!!VP1.0\n"

            // Multiply the vertex coords by the modelview-projection composite matrix,
            // to get clip space coordinates.
            "DP4   o[HPOS].x, c[0], v[0];"
            "DP4   o[HPOS].y, c[1], v[0];"
            "DP4   o[HPOS].z, c[2], v[0];"
            "DP4   o[HPOS].w, c[3], v[0];"

            // Normalize the T (tangent), in case the modelview matrix is not a simple rotation.
            "MOV   R1, v[2];"
            "DP3   R1.w, R1, R1;"
            "RSQ   R1.w, R1.w;"
            "MUL   R1.xyz, R1, R1.w;"

            // Compute the normalized light vector in object space
            "ADD   R2, c[11], -v[0];"
            "DP3   R2.w, R2, R2;"
            "RSQ   R2.w, R2.w;"
            "MUL   R2.xyz, R2, R2.w;"

            // The vertex position, normal and light positions are expressed in object space at
            // this point. We need to find the view direction in object space too.
            "ADD   R0, c[12], -v[0];"       // view direction, from surface to camera.
            "DP3   R0.w, R0, R0;"           // normalize the view direction.
            "RSQ   R0.w, R0.w;"
            "MUL   R0.xyz, R0, R0.w;"

            // Find the texture coordinates to fetch from the toon-param texture:
            //      u = 1/2 (T dot L) + 1/2
            //      v = 1/2 (T dot V) + 1/2
            //
            // a) Do (T dot L) and (T dot V) and store in register R3
            "DP3    R3.x, R1, R2;"
            "DP3    R3.y, R1, R0;"

            // b) Scale by 1/2 and add 1/2 and store as texture coordinates
            //
            "MAD o[TEX0].x, R3.x, c[4].x, c[4].x;"
            "MAD o[TEX0].y, R3.y, c[4].x, c[4].x;"

            // Multiply diffuse lighting into material color.
            "MUL o[COL0], v[3], c[9];"

            "END";

    // Version for directional lights
    //
    // CONSTANTS: (c)
    // c0- c3 4x4 ModelView-Projection composite matrix
    // c4     (.5, 1, 0, 0) vector for compute u,v
    // c9     light color
    // c11    light direction (in object space)
    // c12    camera position in object space (possibly not normalized)

    // VERTEX REGISTERS (mapped so that standard gl calls work):
    // v0 - position
    // v2 - tangent
    // v3 - primary color

    // REGISTERS:
    //
    // R0 - normalized view (surface-to-camera) direction in object space.
    // R1 - normalized surface tangent in object space.
    // R2 - normalized light (surface-to-light) direction.
    // R3 - temporary computation of u,v values for lookup-texture to be
    //      placed int TEX[0]
    //
    char vertexProgramString[] =
        "!!VP1.0\n"

            // Multiply the vertex coords by the modelview-projection composite matrix,
            // to get clip space coordinates.
            "DP4   o[HPOS].x, c[0], v[0];"
            "DP4   o[HPOS].y, c[1], v[0];"
            "DP4   o[HPOS].z, c[2], v[0];"
            "DP4   o[HPOS].w, c[3], v[0];"

            // Normalize the T (tangent), in case the modelview matrix is not a simple rotation.
            "MOV   R1, v[2];"
            "DP3   R1.w, R1, R1;"
            "RSQ   R1.w, R1.w;"
            "MUL   R1.xyz, R1, R1.w;"

            // Normalize the L (light) direction.
            "MOV   R2, c[11];"
            "DP3   R2.w, R2, R2;"
            "RSQ   R2.w, R2.w;"
            "MUL   R2.xyz, R2, R2.w;"

            // The vertex position, normal and light positions are expressed in object space at
            // this point. We need to find the view direction in object space too.
            "ADD   R0, c[12], -v[0];"       // view direction, from surface to camera.
            "DP3   R0.w, R0, R0;"           // normalize the view direction.
            "RSQ   R0.w, R0.w;"
            "MUL   R0.xyz, R0, R0.w;"

            // Find the texture coordinates to fetch from the toon-param texture:
            //      u = 1/2 (N dot L) + 1/2
            //      v = 1/2 (T dot V) + 1/2
            //
            // a) Do (T dot L) and (T dot V) and store in register R3
            "DP3    R3.x, R1, R2;"
            "DP3    R3.y, R1, R0;"

            // b) Scale by 1/2 and add 1/2 and store as texture coordinates
            //
            "MAD o[TEX0].x, R3.x, c[4].x, c[4].x;"
            "MAD o[TEX0].y, R3.y, c[4].x, c[4].x;"

            // Multiply diffuse lighting into material color.
            "MUL o[COL0], v[3], c[9];"

            "END";

    void initVertexProgram(const char vertexProgramCode[], GLuint* pVertexProgramId)
    {
        // Allocate and initialize the vertex program.
        glGenProgramsNV(1, pVertexProgramId);
        GLenum error = glGetError();
        assert(error == GL_NO_ERROR);

        // Load the program.
        unsigned int length = strlen(vertexProgramCode);
        glLoadProgramNV(GL_VERTEX_PROGRAM_NV, *pVertexProgramId, length,
            (const GLubyte *) vertexProgramCode);
        error = glGetError();

        // If an error occured, find the location in the vertex program
        // code and assert.
        if (error != GL_NO_ERROR)
        {
            // If an error occured, it's most likely due to a syntax or
            // logic error in the vertex program. The error position
            // below will contain the index in the vertex program
            // string that is faulty. See the NV_vertex_program
            // extension specification for more details.
            if (error == GL_INVALID_OPERATION)
            {
                int error_position = -2;

                glGetIntegerv(GL_PROGRAM_ERROR_POSITION_NV, &error_position);

                // Most likely a bug in the vertex program code...
                assert(0);
            }
        }
    }

    // Load the vertexProgram and fill in the necessary constants used in the vertex program.
    //
    void hwAnisotropicShader_NV20::loadVertexProgramGL()
    {
        GLenum error = glGetError();
        assert(!error);

        // If the vertex programs haven't been loaded yet,
        // do it now. (Note that they are shared between all contexts.)
        if (!fVertexProgramsLoaded)
        {
            initVertexProgram(vertexProgramString, &fVertexProgramDirectional);
            initVertexProgram(vertexProgramStringPointDecay, &fVertexProgramPointDecay);
            initVertexProgram(vertexProgramStringPoint, &fVertexProgramPointNoDecay);
            fVertexProgramsLoaded = true;
        }
    }

    // Anisotropic material lookup texture
    // (This function assumes that lookup_image and lookup_texture have been deallocated.)
    //
    void hwAnisotropicShader_NV20::make_lookup_texture()
    {
        // Re-calculate the look-up texture, if any of the material paramters
        // have changed
        //
        float   t_roughness = 0.025f;
        float   t_kd = 0.8f;
        float   t_ks = 0.2f;

        //MPlug colorPlug(thisMObject(), color);
        MPlug   roughPlug(thisMObject(), roughness);
        MPlug   diffPlug(thisMObject(), kDiffuse);
        MPlug   specPlug(thisMObject(), kSpecular);

        roughPlug.getValue(t_roughness);
        diffPlug.getValue(t_kd);
        specPlug.getValue(t_ks);

        if ( t_roughness < 0.0f ) t_roughness = 0.0f;
        if ( t_roughness > 1.0f ) t_roughness = 1.0f;
        if ( t_kd < 0.000000000001f ) t_kd = 0.000000000001f; // To prevent underflow in computation
        if ( t_kd> 1.0f ) t_kd= 1.0f;
        if ( t_ks < 0.0f ) t_ks= 0.0f;
        if ( t_ks > 1.0f ) t_ks = 1.0f;

        // Only recompute the lookup texture if the values have changed since the last bind.
        boolean dirty = false;
        if  (currentKd != t_kd) {
            dirty = true; currentKd = t_kd;
        }
        if (currentKs != t_ks) {
            dirty = true; currentKs = t_ks;
        }
        if (currentRoughness != t_roughness) {
            dirty = true; currentRoughness = t_roughness;
        }
        if (!dirty)
            return;

        unsigned int imgsize = lookup_texture_size;
        float imgsizeM1 = (float) (imgsize - 1);

        // Allocate the lookup_image and lookup_texture.
        if (lookup_table == NULL)
            lookup_table = new unsigned char[imgsize*imgsize*3];
        if (lookup_texture == NULL)
            lookup_texture = new tex_object_2D;

        // Fill it up.
    #ifdef _DEBUG_DUMP_LOOKUP_TEXTURE
        // Debug flag to dump out the texture to file.
        // Define this, if you want to write the lookup table
        // out as a file texture.
        static boolean firstTime = true;
    #else
        static boolean firstTime = false;
    #endif
        if (firstTime)
        {
            MImage image;

            image.create(lookup_texture_size, lookup_texture_size);
            unsigned char *ip  = image.pixels();

            float       diff, spec, phong;
            float       lt, vt;
            float       h;
            int         i, j;
            float       invRough = 1.0 / t_roughness;

            for (i=0; i < imgsize; i++)
            {
                // Compute sqrt( 1- (L dot T)*(L dot T) )
                lt = 1.0 - 2.0 * (float)i/(float)(imgsize-1);
                diff= sqrt( 1.0-lt*lt );

                for(j=0; j < imgsize; j++)
                {
                    vt= 2.0 * (float)j/(float)(imgsize-1) - 1.0;
                    spec= diff*sqrt( 1-vt*vt ) - lt*vt;

                    // Compute standard phong model....
                    // phong= Kd*diff + Ks*powf( spec< 0.0 ? 0.0 : spec, 1.0/roughness );
                    phong= t_ks*powf( spec < 0.0 ? 0.0 : spec, invRough );
                    float Kddiff = t_kd*diff;

                    h= (Kddiff+phong)*255.0 + 0.5;
                    *ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
                    h= (Kddiff+phong)*255.0 + 0.5;
                    *ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
                    h= (Kddiff+phong)*255.0 + 0.5;
                    *ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
                    *ip++ = 1.0;
                }
            }
            image.writeToFile("c:/aniso.iff");
            firstTime = false;
        }

        unsigned char *ip  = lookup_table;

        float       diff, spec, phong;
        float       lt, vt;
        float       h;
        int         i, j;
        float       invRough = 1.0 / t_roughness;

        for (i=0; i < imgsize; i++)
        {
            // Compute sqrt( 1- (L dot T)*(L dot T) )
            lt = 1.0 - 2.0 * (float)i/(float)(imgsize-1);
            diff= sqrt( 1.0-lt*lt );

            for(j=0; j < imgsize; j++)
            {
                vt= 2.0 * (float)j/(float)(imgsize-1) - 1.0;
                spec= diff*sqrt( 1-vt*vt ) - lt*vt;

                // Compute standard phong model....
                // phong= Kd*diff + Ks*powf( spec< 0.0 ? 0.0 : spec, 1.0/roughness );
                phong= t_ks*powf( spec < 0.0 ? 0.0 : spec, invRough );
                float Kddiff = t_kd*diff;

                h= (Kddiff+phong)*255.0 + 0.5;
                *ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
                h= (Kddiff+phong)*255.0 + 0.5;
                *ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
                h= (Kddiff+phong)*255.0 + 0.5;
                *ip++ = (h < 0.0) ? 0 : (h > 255.0 ? 255 : (int)h);
            }
        }

        fLookupTextureReprocessed = true;

        return;
    }

    void hwAnisotropicShader_NV20::bind_lookup_table()
    {
        // Update the material table if needed
        make_lookup_texture();

        // Bind the lookup table texture
        lookup_texture->bind();

        if (fLookupTextureReprocessed)
        {
            glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB8, lookup_texture_size, lookup_texture_size,
                0, GL_RGB, GL_UNSIGNED_BYTE, lookup_table);
            fLookupTextureReprocessed = false;
        }

        lookup_texture->parameter(GL_TEXTURE_MIN_FILTER, GL_NEAREST);
        lookup_texture->parameter(GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        lookup_texture->parameter(GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        lookup_texture->parameter(GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    }

    // Initialize the necessary OpenGL extensions
    //
    void hwAnisotropicShader_NV20::init_ext(const char * ext)
    {
        if(!glh_init_extension(ext))
        { cerr << "Failed to initialize " << ext << "!" << endl; exit(0); }
    }

    hwAnisotropicShader_NV20::hwAnisotropicShader_NV20()
    {
        // Get an reference to the singleton texture cache.
        m_pTextureCache = MTextureCache::instance();

        init_ext("GL_ARB_multitexture");
        //init_ext("GL_NV_register_combiners");
        init_ext("GL_NV_vertex_program");

        isDirectionalLight = true;  // light's rotation is connected to the lightRotation attr
        isNonAmbientLight = false; // Does not have decay

        // Set the lookup values to absurd values, so that the
        // look-up table automatically get recomputed during the first update.
        currentColor[0] = currentColor[1] = currentColor[2] = -1.0;
        currentRoughness = -1.0;
        currentKd = -1.0;
        currentKs = -1.0;

        lookup_texture = NULL;
        lookup_table   = NULL;
        fLookupTextureReprocessed = false;

        // Initialize callbacks.
        fBeforeNewCB = 0;
        fBeforeOpenCB = 0;
        fBeforeRemoveReferenceCB = 0;
        fMayaExitingCB = 0;
        attachSceneCallbacks();

        // Initialize the vertex program ids...
        fVertexProgramsLoaded = false;
        fVertexProgramDirectional = 0;
        fVertexProgramPointDecay = 0;
        fVertexProgramPointNoDecay = 0;
        // All vertex programs will get allocated and loaded
        // during the first refresh.
    }

    hwAnisotropicShader_NV20::~hwAnisotropicShader_NV20()
    {
        detachSceneCallbacks();
    }

    void releaseVertexProgram(GLuint* pVertexProgramId)
    {
        // If the vertex program id is set...
        if (*pVertexProgramId > 0)
        {
            // Unbind any vertex program...
            glBindProgramNV(GL_VERTEX_PROGRAM_NV, 0);

            glDeleteProgramsNV(1, pVertexProgramId);

            // For sanity, set the id to 0.
            *pVertexProgramId = 0;
        }
    }

    void hwAnisotropicShader_NV20::releaseEverything()
    {
        release_lookup_texture();

        // Release all loaded vertex programs.
        if (fVertexProgramsLoaded)
        {
            releaseVertexProgram(&fVertexProgramDirectional);
            releaseVertexProgram(&fVertexProgramPointDecay);
            releaseVertexProgram(&fVertexProgramPointNoDecay);
            fVertexProgramsLoaded = false;
        }

        // Release the texture cache through refcounting.
        m_pTextureCache->release();
        if(!MTextureCache::getReferenceCount())
        {
            m_pTextureCache = 0;
        }
    }

    void hwAnisotropicShader_NV20::attachSceneCallbacks()
    {
        fBeforeNewCB  = MSceneMessage::addCallback(MSceneMessage::kBeforeNew,  releaseCallback, this);
        fBeforeOpenCB = MSceneMessage::addCallback(MSceneMessage::kBeforeOpen, releaseCallback, this);
        fBeforeRemoveReferenceCB = MSceneMessage::addCallback(MSceneMessage::kBeforeRemoveReference,
                                                              releaseCallback, this);
        fMayaExitingCB = MSceneMessage::addCallback(MSceneMessage::kMayaExiting, releaseCallback, this);
    }

    /*static*/
    void hwAnisotropicShader_NV20::releaseCallback(void* clientData)
    {
        hwAnisotropicShader_NV20 *pThis = (hwAnisotropicShader_NV20*) clientData;
        pThis->releaseEverything();
    }

    void hwAnisotropicShader_NV20::detachSceneCallbacks()
    {
        if (fBeforeNewCB)
            MMessage::removeCallback(fBeforeNewCB);
        if (fBeforeOpenCB)
            MMessage::removeCallback(fBeforeOpenCB);
        if (fBeforeRemoveReferenceCB)
            MMessage::removeCallback(fBeforeRemoveReferenceCB);
        if (fMayaExitingCB)
            MMessage::removeCallback(fMayaExitingCB);

        fBeforeNewCB = 0;
        fBeforeOpenCB = 0;
        fBeforeRemoveReferenceCB = 0;
        fMayaExitingCB = 0;
    }

    MStatus initializePlugin( MObject obj )
    {
        MStatus   status;

        const MString UserClassify( "shader/surface/utility" );

        MFnPlugin plugin( obj, PLUGIN_COMPANY, "4.5", "Any");
        status = plugin.registerNode( "hwAnisotropicShader_NV20", hwAnisotropicShader_NV20::id,
                                      hwAnisotropicShader_NV20::creator, hwAnisotropicShader_NV20::initialize,
                                      MPxNode::kHwShaderNode, &UserClassify );
        if (!status) {
            status.perror("registerNode");
            return status;
        }

        return MS::kSuccess;
    }

    MStatus uninitializePlugin( MObject obj )
    {
        MStatus   status;

        MFnPlugin plugin( obj );

        plugin.deregisterNode( hwAnisotropicShader_NV20::id );
        if (!status) {
            status.perror("deregisterNode");
            return status;
        }

        return MS::kSuccess;
    }

    void * hwAnisotropicShader_NV20::creator()
    {
        return new hwAnisotropicShader_NV20();
    }

    // Initialize the plug-in. Called once when the plug-in is loaded.
    // This mostly involve creating attributes.
    MStatus hwAnisotropicShader_NV20::initialize()
    {
        MFnNumericAttribute nAttr;
        MStatus status;
        MFnTypedAttribute sAttr; // For string attributes

        // Material color
        colorR = nAttr.create( "colorR", "cr",MFnNumericData::kFloat);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setDefault(1.0f);

        colorG = nAttr.create( "colorG", "cg",MFnNumericData::kFloat);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setDefault(0.5f);

        colorB = nAttr.create( "colorB", "cb",MFnNumericData::kFloat);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setDefault(0.5f);

        color = nAttr.create( "color", "c", colorR, colorG, colorB);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setDefault(1.0f, 0.5f, 0.5f);
        nAttr.setUsedAsColor(true);

        // Material parameters. Set defaults to be "shiny"
        //
        kDiffuse = nAttr.create( "kDiffuse", "kd", MFnNumericData::kFloat);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setMin(0.0f);
        nAttr.setMax(1.0f);
        nAttr.setDefault(0.2f);

        kSpecular = nAttr.create( "kSpecular", "ks", MFnNumericData::kFloat);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setMin(0.0f);
        nAttr.setMax(1.0f);
        nAttr.setDefault(0.9f);

        roughness = nAttr.create( "roughness", "rn", MFnNumericData::kFloat);
        nAttr.setStorable(true);
        nAttr.setKeyable(true);
        nAttr.setMin(0.0f);
        nAttr.setMax(1.0f);
        nAttr.setDefault(0.025f);

        // Add the attributes here
        addAttribute(color);
        addAttribute(roughness);
        addAttribute(kDiffuse);
        addAttribute(kSpecular);
        attributeAffects (colorR, outColor);
        attributeAffects (colorG, outColor);
        attributeAffects (colorB, outColor);
        attributeAffects (color,  outColor);
        attributeAffects (roughness, outColor);
        attributeAffects (kDiffuse, outColor);
        attributeAffects (kSpecular, outColor);

        return MS::kSuccess;
    }

    // To get 3 float values from the node attribute
    //
    MStatus hwAnisotropicShader_NV20::getFloat3(MObject attr, float value[3])
    {
        MStatus status = MS::kSuccess;

        // Get the attr to use
        //
        MPlug   plug(thisMObject(), attr);

        MObject object;
        status = plug.getValue(object);
        if (!status)
        {
            status.perror("hwAnisotropicShader_NV20::getFloat3 plug.getValue.");
            return status;
        }

        MFnNumericData data(object, &status);
        if (!status)
        {
            status.perror("hwAnisotropicShader_NV20::getFloat3 construct data.");
            return status;
        }

        status = data.getData(value[0], value[1], value[2]);
        if (!status)
        {
            status.perror("hwAnisotropicShader_NV20::getFloat3 get values.");
            return status;
        }

        return status;
    }

    // To get a string value from the node attribute
    //
    MStatus hwAnisotropicShader_NV20::getString(MObject attr, MString &str)
    {
        MPlug   plug(thisMObject(), attr);
        MStatus status = plug.getValue( str );

        return status;
    }

    /* virtual */
    MStatus hwAnisotropicShader_NV20::bind(const MDrawRequest& request, M3dView& view)
    {
        MStatus status;

        // Get the material color
        float t_color[3];
        getFloat3(color, t_color);

        view.beginGL();
        {
            glPushAttrib( GL_ALL_ATTRIB_BITS );
            glPushClientAttrib(GL_CLIENT_VERTEX_ARRAY_BIT);

            // Set the material color
            glColor4f(t_color[0], t_color[1], t_color[2], 1.0f);

            // Bind anisotropic texture in texture unit 1
            glActiveTextureARB( GL_TEXTURE0_ARB );
            glEnable(GL_TEXTURE_2D);
            bind_lookup_table();

        }
        view.endGL();

        return MS::kSuccess;
    }

    /* virtual */
    MStatus hwAnisotropicShader_NV20::unbind(const MDrawRequest& request,
                   M3dView& view)
    {
        view.beginGL();
        glActiveTextureARB( GL_TEXTURE0_ARB );
        glDisable(GL_TEXTURE_2D);

        glPopClientAttrib();
        glPopAttrib();

        view.endGL();

        return MS::kSuccess;
    }

    /* virtual */
    MStatus hwAnisotropicShader_NV20::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)
    {
        // We assume triangles here.
        //
        if (prim != GL_TRIANGLES)
            return MS::kSuccess;        // [claforte August 9th 2001] Should this be MS::kFailure?

        view.beginGL();

        // Find out if we have a directional light before
        // loading the vertex program since we use a different
        // vertex program depending on whether the light is a directional
        // one or not
        //
        isDirectionalLight = true; // Assume is directional
        isNonAmbientLight = false;

        boolean useDefaultLight = false;

        unsigned int numLights;
        MDagPath lightPath;
        view.getLightCount( numLights );
        if (numLights)
        {
            M3dView::LightingMode mode;
            view.getLightingMode(mode);
            if (mode == M3dView::kLightDefault)
            {
                useDefaultLight = true;
                isDirectionalLight = true;
            }
            else
            {
                view.getLightPath( 0, lightPath );
                MObject lightObj = lightPath.node();

                isDirectionalLight = lightObj.hasFn( MFn::kDirectionalLight );
                isNonAmbientLight = lightObj.hasFn( MFn::kNonAmbientLight );
                if (isNonAmbientLight)
                {
                    MFnNonAmbientLight mNonAmbientLight(lightObj);
                    if (mNonAmbientLight.decayRate() == 0)
                        isNonAmbientLight = false;
                }
            }
        }

        // Load the appropriate vertex program
        loadVertexProgramGL();

        // Bind and enable the appropriate vertex program,
        // depending on light type.
        //
        if (isDirectionalLight)
            glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramDirectional);
        else if (isNonAmbientLight)
            glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramPointDecay);
        else
            glBindProgramNV(GL_VERTEX_PROGRAM_NV, fVertexProgramPointNoDecay);

        // Assert if an error occurs after binding the vertex programs.
        GLenum error = glGetError();
        assert(error == GL_NO_ERROR);

        // Enable the vertex program.
        glEnable(GL_VERTEX_PROGRAM_NV);

        // Get object's inverse matrix (ie: from world to object space.)
        MDagPath objPath = request.multiPath();
        MMatrix objMatrix = objPath.inclusiveMatrixInverse();

        // Get the light direction in object space.
        // This code assumes that there is a directional light in the scene,
        // and that it is the first light in DAG order.
        //
        if (numLights)
        {
            // Handle default lighting mode
            if (useDefaultLight )
            {
                glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0, 0, 1, 1); // Light dir = 0,0,1
                glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1.0, 1.0, 1.0, 1); // Light color = 1,1,1,1
            }

            // Handle other lighting modes
            else
            {
                view.getLightPath( 0, lightPath );
                MMatrix matrix = lightPath.inclusiveMatrix();

                isDirectionalLight = lightPath.node().hasFn( MFn::kDirectionalLight );

                // Get rotation of a directional light in object space
                if (isDirectionalLight)
                {
                    MVector lightDir(0,0,1);
                    lightDir *= matrix;

                    // Transform into object space
                    lightDir *= objMatrix;

                    // Provide the direction to the vertex program (constant 11)
                    // Need to send it over negated since Maya reverses light direction !!!
                    glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, -lightDir.x, -lightDir.y, -lightDir.z, 1);
                }
                // Get the position of a non-directional light in object space
                else
                {
                    MPoint lightPos(0,0,0); // origin
                    lightPos *= matrix;

                    // Transform into object space
                    lightPos *= objMatrix;

                    // Provide the position to the vertex program (constant 11)
                    // Need to send it over negated since Maya reverses light positions !!!
                    glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, -lightPos.x, -lightPos.y, -lightPos.z, 1);
                }

                MFnLight mLight(lightPath.node());

                // Set the light's color.
                MColor lightColor = mLight.color();
                float intensity = mLight.intensity();
                lightColor.r *= intensity;
                lightColor.g *= intensity;
                lightColor.b *= intensity;
                // This should NOT be clamped here, so the vertex program
                // does the correct computation ! Don't uncomment these
                // lines.
                //if (lightColor.r > 1.0) lightColor.r = 1.0;
                //if (lightColor.g > 1.0) lightColor.g = 1.0;
                //if (lightColor.b > 1.0) lightColor.b = 1.0;
                glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, lightColor.r, lightColor.g, lightColor.b, 1);
            }
        }
        else
        {
            // Set some default values
            glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 11, 0, 0, 1, 1);
            glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 9, 1.0, 1.0, 1.0, 1);
        }

        // Find the camera position in geometry's object space
        float cameraPos[4] = {0.0f, 0.0f, 0.0f, 0.0f};
        {
            MDagPath camDag;
            view.getCamera(camDag);

            MPoint cameraInObject(0,0,0);

            MMatrix cameraToWorldMatrix = camDag.inclusiveMatrix();

            cameraInObject *= cameraToWorldMatrix; // to world
            cameraInObject *= objMatrix;

            glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 12, cameraInObject.x, cameraInObject.y, cameraInObject.z, 1);
        }

        // VERTEX CONSTANTS:
        //  c0- 3  4x4 ModelView-Projection composite matrix
        //  c4    (1/2, 1, 0, 0) vector needed to compute (u,v)
        //
        glTrackMatrixNV(GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV);
        glProgramParameter4fNV(GL_VERTEX_PROGRAM_NV, 4, 0.5, 1.0, 0.0, 0.0);

        // VERTEX REGISTERS (Attributes):
        // 0 - coord
        // 2 - tangent
        glVertexAttribPointerNV( 0, 3, GL_FLOAT, 0, vertexArray );
        glVertexAttribPointerNV( 2, 3, GL_FLOAT, 0, normalArrays[TANGENT_INDEX] );

        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
        glEnableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );

        glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indexArray);

        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY0_NV );
        glDisableClientState( GL_VERTEX_ATTRIB_ARRAY2_NV );

        glDisable(GL_VERTEX_PROGRAM_NV);

        view.endGL();

        return MS::kSuccess;
    }

    /* virtual */
    int     hwAnisotropicShader_NV20::normalsPerVertex()
    {
        // Need the normal and tangents for this computation
        return 2;
    }

    /* virtual */
    int     hwAnisotropicShader_NV20::texCoordsPerVertex()
    {
        return 1;
    }

    // Release the lookup texture/image.
    void hwAnisotropicShader_NV20::release_lookup_texture()
    {
        if (lookup_table)
        {
            delete lookup_table;
            lookup_table = NULL;
        }

        if (lookup_texture)
        {
            delete lookup_texture;
            lookup_table = NULL;
        }
    }