ジオメトリのオーバーライド

MPxGeometryOverride は、ビューポート 2.0 でカスタム DAG オブジェクトの高レベルな描画 API に依存しないサポートを定義する API エントリ ポイントです。旧式のインタフェースには、対応するクラスはありません。ビューポート 2.0 は、保持モードの描画を主体として構成されています。可能な限り、すべてのジオメトリック データがグラフィックス カードにバッファとして保持されます。MPxGeometryOverride のすべての実装は、特定のタイプの DAG オブジェクトと関連付けられた「データ フィーダ」とみなすとよく理解できます。MPxGeometryOverride の実装では、データを提供することに加え、オブジェクトを描画するために必要な追加のレンダリング可能項目(レンダー項目)を定義することもできます。MPxGeometryOverride インタフェースは、ポリゴン メッシュ、NURBS サーフェスなどのネイティブ Maya オブジェクトをサポートするために使用される内部インタフェースと非常に似ています。したがって、このクラスを使用すると、プラグイン作成者は、統合、内部頂点データ共有など、すべての内部最適化を利用できます。また、MPxGeometryOverride の実装は、Maya の内部スクリーン スペース エフェクトおよびサポートされているすべてのシェーダ(プラグイン シェーダを含む)で機能します。

MPxGeometryOverride は、カスタム描画を実行するために設計されていないため、「draw」メソッドはなく、描画コンテキストにアクセスできません。プラグイン シェイプでもありません。これは、単に、ビューポート 2.0 での描画用にデータ バッファを作成する方法を記述するために、プラグイン シェイプに関連付けることができるクラスです。DAG オブジェクトの実際の描画は、各レンダー項目に関連付けられているシェーダによって処理されます。

MPxGeometryOverride の実装は、分類文字列を使用して MDrawRegistry に登録する必要があります。分類文字列は、システムで正しく認識されるように「drawdb/geometry」で始まる必要があります。分類文字列がオーバーライドの分類文字列を満たす DAG オブジェクトは、オーバーライドを使用して評価されます。Maya は、関連付けられている DAG オブジェクトごとに、登録されている MPxGeometryOverride のインスタンスを 1 つ作成します。

図 38: MPxGeometryOverride が分類文字列により特定の DAG オブジェクトに関連付けられています。内部サーフェス シェーダからのレンダー項目およびカスタム レンダー項目が、オーバーライドで処理するレンダー項目のセットの一部となっている構成の例を示してあります。

インタフェースは、複数のセクションまたはフェーズに分かれています。最初のフェーズは updateDG()であり、関連付けられた Maya DAG オブジェクトの全アトリビュート データをここで照会およびキャッシュする必要があります。最後の populateGeometry()フェーズでは、アトリビュート データを照会しようとしても無効で、この動作を行うと不安定な状態になることがあります。このように分離されていることにより、ディペンデンシー グラフの評価とグラフィックス デバイス アクセスの分離が維持されて、マルチスレッドでの更新が容易になります。

2 番目のフェーズは updateRenderItems() です。このフェーズ中に、事前に作成されたレンダー項目のリストがオーバーライドに提供されます。レンダー項目は、オブジェクトに割り当てられているサポートされているシェーダごとに 1 つです。オーバーライドでは、オブジェクトの表示を強化するためにこれらのレンダー項目を無効にすることや追加のレンダー項目を追加することができます。追加のレンダー項目は、MShaderInstance インタフェースを使用してシェーダと関連付ける必要があります。各レンダー項目からのシェーダによって、populateGeometry() ステージでオーバーライドが満たす必要のあるジオメトリ要件のセット全体が決まります。MPxGeometryOverride の追加のレンダー項目は、古い MDrawRequest インタフェースに似ています。

第 3 フェーズは、populateGeometry() です。このフェーズ中に、オーバーライドで満たす必要のあるジオメトリ要件のリストおよびこれらの要件を生成したレンダー項目のリストがオーバーライドに提供されます。オーバーライドでは、要件を満たすために、提供されている MGeometry 構造に頂点バッファ(MVertexBuffer)を入力する必要があります。各レンダー項目用のインデックス バッファ(MIndexBuffer)を作成する必要もあります。インデックス バッファは MGeometry オブジェクトに格納されている必要があり、MRenderItem::associateWithIndexBuffer() を使用して対応するレンダー項目に直接関連付けられている必要もあります。

図 39: 3 つのフェーズは、MPxGeometryOverride の更新に関係するメイン コンストラクト間の接続ラベルに使用されます。最初のフェーズ(1.)は、関連付けられた DAG オブジェクトへのアクセスに関連します。2 番目のフェーズ(2.)では、レンダー項目を更新し、ジオメトリの要件を決定します。3 番目のフェーズ(3.)では、頂点とインデックス バッファのデータおよび各レンダー項目に対するインデックスの関連付けを更新します。この例では、関連付けられているサーフェス シェーダ用の 1 つと、オーバーライドで描画するカスタム レンダー項目用の 2 つで構成される 3 つのレンダー項目から更新要件が決まります。

MPxGeometryOverride のフェーズは、複数の異なる状況でトリガされます。MPxGeometryOverride に関連付けられた DAG オブジェクトが初めて作成されると、オーバーライドのすべてのフェーズが実行されます。シーンの再描画では、関連付けられている DAG オブジェクトが変更されていない限り、オーバーライドの更新プロセスはトリガされません。DAG オブジェクトで発生する可能性がある変更のタイプは、トポロジの変更とデータの変更の 2 つです。データの変更では updateRenderItems() の呼び出しはトリガされません。この場合は、updateDG() および populateGeometry() のみが呼び出されます。トポロジの変更は、完全なリビルドを表し、オーバーライドのすべてのフェーズが呼び出されます。最初に実行されたときにオーバーライドが updateRenderItems() に追加のレンダー項目を追加すると、これらのレンダー項目はトポロジの更新をまたがって保持されます。したがって、それらを再度追加する必要はありません。プラグイン DAG オブジェクトでは、スタティック メソッド MRenderer::setGeometryDrawDirty() を呼び出すことにより、変更されたことをビューポート 2.0 の変更マネージャに通知できます。メソッドには、トポロジの変更なのかデータの変更なのかを示すパラメータがあります。

Maya SDK のサンプル apiMeshShape では、MPxGeometryOverride を使用しています。これは、シェーディング描画のためのジオメトリ ストリームとインデックスを装備している新しいクラス apiMeshGeometryOverride を定義します。ワイヤフレーム モードのために使用され、オブジェクトが選択されている場合に、シェーディング オブジェクトの上に選択ワイヤフレームを表示するために使用される、ワイヤフレーム描画の追加のレンダー項目も定義します。

サンプルの動作を例示する、apiMeshGeometryOverride から抽出したコードを次に示します。

  1. initializePlugin() での適切な分類への登録
    MFnPlugin plugin(...);
    // Define some drawDb classification string starting with drawdb/geometry
    MString apiMesh::drawDbClassification("drawdb/geometry/apiMesh");
    
    // Register a shape, with a drawdb classification
    plugin.registerShape( "apiMesh", apiMesh::id,
        &apiMesh::creator,
        &apiMesh::initialize,
        &apiMeshUI::creator,
        &apiMesh::drawDbClassification )
        
    // Register a geometry override with the same drawDB classification
    MHWRender::MDrawRegistry::registerGeometryOverrideCreator(
        apiMesh::drawDbClassification,
        apiMesh::drawRegistrantId,
        apiMeshGeometryOverride::Creator);
  2. ワイヤフレームを表示するための「カスタム」レンダー項目を作成する updateRenderItems() メソッド
    // Add a wire frame item if it's not there
    MHWRender::MRenderItem* dormantItem = NULL;
    
    int index = list.indexOf(
            "apiMeshWire",
            MHWRender::MGeometry::kLines,
            MHWRender::MGeometry::kWireframe);
            
    if (index < 0)
    {
        MHWRender::MRenderItem* wireItem = new MHWRender::MRenderItem(
                "apiMeshWire",
                MHWRender::MGeometry::kLines,
                MHWRender::MGeometry::kWireframe,
                false);
        list.append(wireItem);
    
        // Use a shader which will provide drawing using a solid color.
        MHWRender::MShaderInstance* shader = shaderMgr->getStockShader(
            MHWRender::MShaderManager::k3dSolidShader, NULL, NULL );
        if (shader)
        {
            // Set the color on the shader instance using the parameter interface
            static const float theColor[] = {1.0f, 0.0f, 0.0f, 1.0f};
            shader->setParameter("solidColor", theColor);
    
            // Assign the shader to the custom render item
            wireItem->setShader(shader);
        }
    }
  3. 頂点とインデックス バッファの更新、および populateGeometry() でのレンダー項目とインデックス バッファの関連付けの実行
    unsigned int totalVerts = N;
    
    // Set up three data buffers. One for position, one for normals and one for texture
    // coordinates.
    MHWRender::MVertexBuffer* positionBuffer = NULL;
    float* positions = NULL;
    MHWRender::MVertexBuffer* normalBuffer = NULL;
    float* normals = NULL;
    MHWRender::MVertexBuffer* uvBuffer = NULL;
    float* uvs = NULL;
    int numUVs = fMeshGeom->uvcoords.uvcount();
    
    // Scan through the requirements (descriptor list) one and a time. For each
    // descriptor check the semantic. Based on the semantic, update
    // the appropriate buffers that this override supports (position, normals and
    // texture coordinates).
    //
    const MHWRender::MVertexBufferDescriptorList& descList = requirements.vertexRequirements();
    int numVertexReqs = descList.length();
    MHWRender::MVertexBufferDescriptor desc;
    for (int reqNum=0; reqNum<numVertexReqs; reqNum++)
    {
        if (!descList.getDescriptor(reqNum, desc))
        {
            continue;
        }
    
        switch (desc.semantic())
        {
            case MHWRender::MGeometry::kPosition: // Handle position requirement
            {
                if (!positionBuffer)
                {
                    positionBuffer = data.createVertexBuffer(desc);
                    if (positionBuffer)
                    {
                        positions = (float*)positionBuffer->acquire(totalVerts);
                    }
                }
            }
            break;
            case MHWRender::MGeometry::kNormal: // Handle normal requirement
            {
                if (!normalBuffer)
                {
                    normalBuffer = data.createVertexBuffer(desc);
                    if (normalBuffer)
                    {
                        normals = (float*)normalBuffer->acquire(totalVerts);
                    }
                }
            }
            break;
            case MHWRender::MGeometry::kTexture: // Handle texture coordinate requirement
            {
                if (!uvBuffer)
                {
                    uvBuffer = data.createVertexBuffer(desc);
                    if (uvBuffer)
                    {
                        uvs = (float*)uvBuffer->acquire(totalVerts);
                    }
                }
            }
            break;
            default:
                // do nothing for stuff we don't understand
            break;
        }
    }
    
    // Copy data from shape into data buffers
    for (int i=0; i<fMeshGeom->faceCount; i++)
    {
        // ignore degenerate faces
        int numVerts = fMeshGeom->face_counts[i];
        if (numVerts > 2)
        {
            for (int j=0; j<numVerts; j++)
            {
                if (positions)
                {
                    // Fill in position data…
                }
                if (normals)
                {
                    // Fill in normal data…
                }
    
                if (uvs)
                {
                    // Fill in uv data…
                }
    
                vid++;
            }
        }
    }
    
    // Commit the updated buffers 
    if (positions)
        positionBuffer->commit(positions);
    if (normals)
        normalBuffer->commit(normals);
    if (uvs)
        uvBuffer->commit(uvs);
    
    // Fill in indexing data for the custom wireframe render item
    //
    MHWRender::MIndexBuffer* wireIndexBuffer = NULL; 
    int numItems = renderItems.length();
    for (int i=0; i<numItems; i++)
    {
        const MHWRender::MRenderItem* item = renderItems.itemAt(i);
        if (!item) continue;
    
        // Check for the wireframe render item created updateRenderItems()
        if (item->name() == "apiMeshWire") 
        {
            // Allocate a wireframe buffer
            if (!wireIndexBuffer)
            {
                wireIndexBuffer = data.createIndexBuffer(MHWRender::MGeometry::kUnsignedInt32);
                if (wireIndexBuffer)
                {
                    unsigned int* buffer = (unsigned int*)wireIndexBuffer->acquire(2*totalVerts);
                    if (buffer)
                    {
                        // Fill in the buffer here…
    
                        // Commit the buffer
                        wireIndexBuffer->commit(buffer);
                    }
                }
            }
    
            // Associate same index buffer with either render item
            if (wireIndexBuffer)
            {
                item->associateWithIndexBuffer(wireIndexBuffer);
            }    
            
        }
    
        // Handle other render items…
    }