hwPhongShader ノード プラグインは、Maya API 開発キットで提供されています。このサンプルでは、立方体環境マップを使用してピクセルごとの Phong シェーディングを行います。ライトの方向は現在視点の位置に固定されています。
プラグインを初期化する手順は簡単です。初期化では、スウォッチ分類文字列が作成され、ノードとドラッグ & ドロップ動作のクラスが登録されます。ドラッグ & ドロップ動作には固有の MTypeId は必要ないことに注意してください。この動作を示す簡単なテキスト文字列で十分です。
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; }
プラグインの初期化を解除すると、Phong マテリアル ノードとドラッグ & ドロップ動作の登録が解除されます。
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; }
ディペンデンシー グラフ(Dependency Graph)で使用されるハードウェア シェーダ プラグインの initialize() メソッドのアトリビュートは事前に設定されています。標準的なカラー アトリビュートは、その機能に応じたプロパティを使用して追加され、フラグされます。最初にアトリビュートが作成され、次にアトリビュートが追加され、その後アトリビュート間のリレーションシップが設定されます。次の例では、パフォーマンスを向上させるためにアトリビュートを内部的にキャッシュするように設定します。
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() の簡単な使用例について説明します。このメソッドを設定するのは、outColor アトリビュートを処理する場合だけです。このメソッドに他のアトリビュートを渡すと MS::kUnknownParameter が返されます。この場合、Maya がこれらのアトリビュートを処理します。それ以外の場合には、データ ブロックからアトリビュート aDiffuseColor がアクセスされ、この値を使って出力カラーが設定されます。
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; }
このメソッドを実行すると、ハードウェア シェーディング ノード プラグインがソフトウェア レンダーで可視になります。ソフトウェア レンダーが必要ない場合、このメソッドを実行する必要はありません。
bind() メソッドと glBind() メソッドは、同じ方法を使用します。必要なアトリビュートが変更されるか、Phong テクスチャが設定されると、Phong テクスチャが初期化されます。
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; }
このサンプルの unbind() メソッドと glUnbind() メソッドは、MS::kSuccess だけを返します。これは、リソースの解放に関する代替方法が記述されているためです。hwPhongShader の例では、API を使用してメッセージが確認され、新規ファイルの作成、ファイルのオープン、ファイルの参照などのイベントの前にリソースが解放されます。詳細については、完全なサンプル コードを参照してください。
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; }
geometry() と glGeometry() メソッドは同じように設定されています。これらは、それぞれインタフェースに依存しない draw() メソッドをコールして、描画コードの重複を回避します。
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; }
draw() メソッドのパラメータは、情報を画面にレンダーする OpenGL に情報を渡す場合に使用します。他の配列から情報にアクセスする場合には、indexArray を使うことに注意してください。
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; }
スウォッチを描画する場合には、renderSwatchImage() 仮想メソッドを実行し、MHardwareRenderer クラスを MGeometryData と OpenGL と組み合わせて使用してイメージを描画します。renderSwatchImage() に渡される MImage には、求められる出力の幅と高さに関する情報が含まれています。
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; }
drawTheSwatch() メソッドは、 renderSwatchImage() メソッドからコールされます。このメソッドは、イメージに対して OpenGL 描画を実行します。
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 ); } }