MPxShaderOverride は、ビューポート 2.0 でプラグイン サーフェス シェーダのすべてのシェーディングとライティングをオーバーライドする API エントリ ポイントです。MPxGeometryOverride と同様に、このクラスはシェーダの Maya ノードを定義しません。このクラスは描画全体をオーバーライドするため、テクスチャ、ライト、ジオメトリなどのリソースの定義とバインドはこのオーバーライドですべて行われます。MDrawContext (描画コンテキスト)のインスタンスが適切なポイントで提供されるので、デバイス情報にアクセスしてこれらのタスクを容易に実行できます。このクラスは未処理の描画コールが必要であるため、描画 API に依存します。両方の API へのサポートが必要な場合は、DirectX および OpenGL 用に個別のコード パスを作成する必要があります。Maya CgFX および dx11Shader プラグインでのビューポート 2.0 のサポートは、このインタフェースを使用して実装されます。
MPxShaderOverride の実装は、シェーディング ノードの特定のタイプと関連付ける必要があります。ほとんどの場合は、プラグインでシェーディング ノードを定義し、シェーダを使用するオブジェクト用の描画コードを提供するために別の MPxShaderOverride が作成されます。CgFX プラグインは MPxHwShaderNode インタフェースを使用して CgFX ノードを定義しますが、ビューポート 2.0 での描画をサポートする別の MPxShaderOverride が存在します。MPxShaderOverride の実装は、分類文字列を使用して MDrawRegistry に登録する必要があります。オーバーライドの分類を満たす分類文字列を使用するシェーダは、オーバーライドを使用して描画されます。分類文字列は、システムが認識できるように「drawdb/shader」で始まる必要があります。Maya では、シーンでアクティブに使用されている関連付けられたシェーダ タイプの各インスタンスに対して登録されたシェーダ オーバーライドの 1 つのインスタンスを作成します。
図 40
上位レベルでは、MPxShaderOverride に主なタスクが 2 つあります。描画する必要があるジオメトリ ストリームを指定し(ジオメトリ要件)、割り当て先のオブジェクトを描画することです。これは、新しいレンダリング モデルの producer-consumer (生成者-使用者)リレーションシップを示します。このクラスは、ジオメトリ要件(MGeometryRequirements)を生成します。この要件は、ジオメトリ ストリームを生成するためにジオメトリ システム(MPxGeometryOverride の内部クラスまたはプラグイン実装)で使用されます。その後、このクラスの描画メソッドがジオメトリ ストリームを使用して描画します。
図 41: 2 つの異なる DAG オブジェクトに割り当てられたシェーダ オーバーライドのサンプル構成です。一方の DAG オブジェクトは、ジオメトリ オーバーライドを使用しています。もう一方は、内部ジオメトリ アップデータを使用しています。この構成は、NURBS サーフェス アップデータの例になります。シェーダ オーバーライドは、各アップデータの要件を「生成」します。各オブジェクトは、レンダリングする新しいジオメトリを「生成」します。このジオメトリは、シェーダ オーバーライドによって「使用」されるまでは、関連付けられたレンダー項目の下位のパイプラインに渡されます。
オーバーライドで実装する必要のある 3 つの主なフェーズは、初期化、更新、および描画です。
関連付けられたシェーダ タイプのインスタンスがオブジェクトにバインドされている場合、またはシェーダ自体の入力アトリビュートが変更された場合は、MPxShaderOverride のフェーズがトリガされて実行されます。更新をトリガするために、特別なロジックを追加する必要はありません。コールされる更新メソッドは、発生した変更のタイプによって異なります。シェーダの新しい割り当てによって、完全なリビルドがトリガされます。同様に、rebuildAlways() が true を返した場合は、アトリビュートの変更によって完全なリビルドもトリガされます。その他の場合はすべて、初期化はスキップされ、更新のみが行われます。描画フェーズは、シェーダでオブジェクトを描画する必要があるすべての更新時に発生します。
初期化フェーズ中に、addGeometryRequirement() を使用してジオメトリ ストリームの要件を指定することができます。この要件では、特定のシェーディング エフェクトが割り当てられるオブジェクトから、必要なジオメトリ ストリームを指定します。要件を指定しない場合、1 つの位置ストリーム要件が使用されます。initialize() メソッドは、「シェーダ キー」を表す文字列を返す必要があります。MPxShaderOverride の複数のインスタンスが本質的に同じシェーダを表していても、それぞれ異なるシェーダ パラメータを使用していることがよくあります。シェーダ キーは、同じシェーダを表す MPxShaderOverride インスタンスを識別するために使用されます。レンダリングを最適化するために、レンダラは、MPxShaderOverride から返されるプロパティに基づいて、オブジェクトのレンダリングを統合しようとします。これには、シェーダ キーが含まれますが、その他の要素も含まれることがあります。統合およびジオメトリの処理の詳細については、下記の「統合に関する考慮事項」を参照してください。これにより、プラグインはシーケンス全体で 1 回セットアップを実行するだけで済むようになります。各プラグインが、同じシェーダを表す目的を決定します。
図 42: シェーダ オーバーライドによって提供されるシェーダ キーを 2 つのレンダー項目で同じにすることができます。この場合、統合によって、描画が行われる前にこれらの項目を 1 つのレンダー項目に「マージ」できます。
初期化中、現在の表示モードが非テクスチャ表示モードである場合は、指定されたシェーダ オーバーライドに関連付けられているシェーディング ノードを使用するすべてのレンダー項目で、内部的に定義された静的シェーダ インスタンスが使用されます。これは、追加のノード モニタを回避すると同時に、このシェーダ インスタンスを共有するレンダー項目を統合できるようにするための、パフォーマンス最適化です。
この動作をオーバーライドするには、既定の共有インスタンスの代わりにカスタム シェーダ インスタンスを戻すように MPxShaderOverride::nonTexturedShaderInstance() メソッドをオーバーライドします。ノードのアトリビュート変更時にこのシェーダ インスタンスを更新する必要があるかどうかを示すように、戻りパラメータを設定することができます。モニタが不要な場合は、Maya は非テクスチャ モードの間、更新フェーズをスキップしようとします。テクスチャ モード表示に使用されるシェーダで、更新が必要になることがあります。たとえば、MPxShaderOverride::rebuildAlways() が true を返した場合は、このメソッドで設定したオプションにかかわらず、更新フェーズが呼び出されます。
更新フェーズ中に、シェーディングに必要なすべてのデータ値が更新されます。インタフェースでは、ディペンデンシー グラフにアクセスできるポイント(updateDG())と、描画 API (OpenGL または DirectX)にアクセスできるポイント(updateDevice())が明確に分割されています。中間データは、endUpdate() が呼び出されるときにクリーンアップできます。たとえば、オーバーライドに対し、指定されたノードのアトリビュートからの入力が必要な場合があります。
オーバーライドが、シェーディングに半透明が含まれるどうかに関するヒントを提供することがあります。このヒントは、updateDevice() と endUpdate() の間で呼び出される isTransparent() メソッドをオーバーライドして提供することができます。
ディスプレイスメントなどの高度なシェーディング エフェクトによって、シェーディングされるオブジェクトのサイズが変更されることがあります。この場合は、誤ったタイミングでフラスタム カリングされないように、オブジェクトのバウンディング ボックスを調整することをお勧めします。これは、boundingBoxExtraScale() メソッドをオーバーライドして実現することができます。これによって、オーバーライドでオブジェクトの絶対値バウンディング ボックスを提供することはできなくなります。代わりに、オーバーライドは、計算されたバウンディング ボックスに適用されるスケール係数を提供します。これは、多数のシェーダが同じオブジェクトに影響を与える可能性があり、各シェーダは他のすべての要件を把握できないからです。
描画フェーズは、純粋な仮想 draw() メソッドによって実行されます。このメソッドは、正常に描画できる場合は true を返します。false を返す場合、描画はサポートされていないマテリアルで使用される既定のシェーダを使用して行われます。描画は意図的にデータ更新と混合されません。描画がコールされる時点で、すべての評価が完了している必要があります。更新フェーズと描画フェーズ間で受け渡す必要があるユーザ データがある場合は、オーバーライドでそのデータそのものをキャッシュする必要があります。描画中に Maya のディペンデンシー グラフにアクセスするとエラーになり、この操作によって不安定になることがあります。すべてのシェーダの設定とジオメトリの描画を処理する draw() メソッドの実装は可能ですが、draw() メソッドの使用が必要なのは、シェーダの設定に対してのみです。この後に、drawGeometry() が呼び出されて、Maya でジオメトリの描画を処理できるようになります。手動のジオメトリ バインドが必要な場合は、draw() メソッドに渡されるレンダー項目リスト内の各レンダー項目のジオメトリを使用して、ハードウェア リソース ハンドルを照会できます。
また、レンダー項目が別のシェーダ キーを使用して描画されるたびに、描画フェーズ内で activateKey() および terminateKey() メソッドも呼び出されます。activateKey() および terminateKey() メソッドを使用すると、同じシェーダ キーを共有している一連の draw() 呼び出しに対して一度だけレンダリング状態を構成することにより、レンダリングを最適化することができます。3 つのすべてのシェーダ オーバーライド(A、B、C)が同じシェーダ キーを返す場合の起動 シーケンスの例は、次のようになります。
shaderOverrideA->activateKey(...) shaderOverrideA->draw(...) shaderOverrideB->draw(...) shaderOverrideC->draw(...) shaderOverrideA->terminateKey(...)
注: terminateKey() コールバックは、activateKey() コールバックの起動に使用された MPxShaderOverride インスタンスで常に起動されます。
すべての描画メソッドは、MDrawContext パラメータを使用して描画コンテキストにアクセスできます。これは、テクスチャ マネージャとともに、可能な限り、状態およびテクスチャを管理するために使用する必要があります。これらのインタフェースを使用すると(未処理の描画 API コールを行うのとは対照的に)、パフォーマンスが向上し、デバイス状態の破損に関する問題を回避できます。
handlesDraw() メソッドは、activateKey()、draw()、および terminateKey() の前に呼び出されます。このメソッドを使用して、オーバーライドは、現在の描画コンテキストに基づいて描画を処理するかどうかを決定できます。たとえば、カラー パスの描画は処理しても、シャドウ マップ作成パスの描画は処理しないことを選択できます。このメソッドから false が返された場合、activateKey()、draw()、および terminateKey() は呼び出されません。
Maya SDK の例「hwPhongShader」は、MPxShaderOverride の簡単な使用例を示しています。プラグインは、MPxHwShaderNode を使用するノードを定義し、クラス「hwPhongShaderOverride」に MPxShaderOverride を実装してビューポート 2.0 のサポートを提供します。オーバーライドの draw() メソッドは、(コンパイル時の定数によってコントロールされる)両方の描画メソッドを示します。最初の例では、シェーダを設定してから drawGeometry() を呼び出して、描画を実行します。2 番目の例では、シェーダの設定後にすべてのジオメトリ バインドを手動で実行し、手動で OpenGL 呼び出し glDrawElements() を行って描画を実行します。
MPxShaderOverride の使用法の詳細な例については、CgFX プラグイン(cgFxShaderNode.h/.cpp)および dx11Shader プラグイン(dx11ShaderOverride.h/.cpp)を参照してください。dxShaderOverride には、handlesDraw() の使用例が含まれています。
オーバーライドでシェーダ インスタンスを使用している場合、オーバーライドごとに 1 つのシェーダ インスタンスを保持するのではなく、固有のグローバル セットを使用する必要があります。詳細については、「シェーダ インスタンス」の「統合に関する考慮事項」を参照してください。
統合が必要でない場合、次をオーバーライドすることによってこれを示すことができます。
MPxShaderOverride::handlesConsolidatedGeometry() 仮想メソッド
これを設定しない場合、項目を統合できるかどうかの判断に、シェーダ オプションが使用されます。
dx11Shader、glslShader、および hwPhongShader Developer Kit プラグインを参照してください。また、最初の 2 つの例では、セマンティックとして公開されています。「ビューポート 2.0 の dx11Shader および glslShader プラグインでサポートされるセマンティックと注釈」を参照してください。
シェーダ キーの独自性以外に、MRenderItem の独自性を決めるために使用される「カスタム データ」も、レンダー項目の統合に影響します。****つまり、異なるデータ ポインタを持つレンダー項目は、項目の統合が起こらないことを意味します。統合のサポートが必要な場合、これを考慮する必要があります。
シェーダでは、描画時に Maya オブジェクトごとの情報抽出が必要になることがあります。
これを実現する方法の 1 つは、シェーダを使用するレンダー項目が統合されないように強制することです。その結果、シェーダ セットアップと描画の呼び出しが、オブジェクト単位でセットになります。この動作は、シェーダ キー、カスタム データの使用、または MPxShaderOverride::handlesConsolidatedGeometry() オーバーライドに基づいて、統合されないものとして項目にマークを付けることで実現できます。
統合時に分離を強制する代わりの方法は、レンダー項目が統合されることは許可して、描画時にパスおよびジオメトリの情報を抽出するというものです。
シャドウ マップ パスのように、オブジェクトごとの抽出を必要としないパスでは、統合されたジオメトリ全体を描画する既定の動作は維持できます。
抽出を必要としないパスでは、まず MRenderItem::isConsolidated() メソッドを調べて、統合されたジオメトリがレンダー項目に含まれているかどうかを確認する必要があります。含まれている場合は、MRenderItem::sourceIndexMapping() を使用して、Maya DAG パスのリスト、および関連するインデックス範囲を取得できます。インデックス範囲は、描画時に使用できる、統合されたジオメトリ データ バッファ内の、パスごとのジオメトリがある場所を示します。
MRenderItem::sourceDagPath() メソッドは 1 つのパスしか返すことができないので、統合されたジオメトリ パスの抽出には適していません。
hwPhongShader プラグインからの次のサンプル コードでは、基本的なロジックを示しています。
const MHWRender::MRenderItem* renderItem = renderItemList.itemAt(renderItemIdx); const MHWRender::MGeometry* geometry = renderItem->geometry(); MHWRender::MGeometryIndexMapping geometryIndexMapping; if (renderItem->isConsolidated()) { renderItem->sourceIndexMapping(geometryIndexMapping); // Extract information if the geometry was consolidated for (int i=0; i<geometryIndexMapping.geometryCount(); i++) { // Pull out the dag path, any component, and the start and end // indexing into the geometry buffers. MDagPath path = geometryIndexMapping.dagPath(i); MObject comp = geometryIndexMapping.component(i); int indexStart = geometryIndexMapping.indexStart(i); int indexLength = geometryIndexMapping.indexLength(i); } } else { // Have unconsolidated geometry. The indexing thus spans the entire // range of the buffers returned. MDagPath sourceDagPath = renderItem->sourceDagPath(); }
具体的な例として、pPlaneShape1 および pPlaneShape2 の 2 つのオブジェクトがあるシーンを考えます。これらには同じプラグイン シェーダ(hwPhong)が割り当てられています。次のイメージでは、各オブジェクトのトポロジ(頂点およびフェースの ID)が示されています。三角形分割は点線で示されています。
オブジェクトごとのジオメトリ データにアクセスするには、最初にインデックス情報を抽出する必要があります。インデックス情報は、描画時に返されるレンダー項目(MRenderItem)のジオメトリ(MGeometry)を調べることにより返されます。
MRenderItem renderItem; // Passed at draw time. // Get the geometry const MHWRender::MGeometry* geometry = renderItem->geometry(); // Get the index type MHWRender::MGeometry::Primitive indexPrimType = renderItem->primitive(); MString indexPrimTypeName = MHWRender::MGeometry::primitiveString(indexPrimType); const MHWRender::MIndexBuffer* buffer = geometry->indexBuffer(0); // Get the data type MString dataType = MHWRender::MGeometry::dataTypeString(buffer->dataType()); // Get the size of the index buffer int indexCount = geometry->indexBufferCount(); // Dump the indexing for debugging purposes. Note that when geometry is consolidated, the // index can be unsigned short. MHWRender::MIndexBuffer* nonConstIB = const_cast<MHWRender::MIndexBuffer*>(buffer); if (buffer->dataType() == MHWRender::MGeometry::kUnsignedInt32) { const unsigned int *ptr = (const unsigned int*)nonConstIB->map(); for (unsigned int i=0; i<indexBufferCount; i++) { const unsigned int index = ptr[i]; fprintf(stderr, "-- index[%d] = %d\n", i, index); } nonConstIB->unmap(); } else { const unsigned short *ptr = (const unsigned short*)nonConstIB->map(); for (unsigned int i=0; i<indexBufferCount; i++) { const unsigned short index = ptr[i]; fprintf(stderr, "-- index[%d] = %d\n", i, index); } nonConstIB->unmap(); }
2 つのオブジェクトの間で統合が可能な場合、返される情報は次のようになります。プレーン テキストおよび太字は、インデックスが元のオブジェクトとどのように関係しているかを示すために使用されています。太字は pPlaneShape1 を示すために使用されています。
Indexing Primitive Type = Triangles,
Index type = Unsigned Int 16,
Index count = 18
Index array:
index[0] = 0
index[1] = 1
index[2] = 3
index[3] = 3
index[4] = 1
index[5] = 2
index[6] = 4
index[7] = 5
index[8] = 7
index[9] = 7
index[10] = 5
index[11] = 6
index[12] = 8
index[13] = 9
index[14] = 11
index[15] = 11
index[16] = 9
index[17] = 10
ジオメトリについて提供される統合情報は次のとおりです。
最初のジオメトリでは、パス = |pPlane2|pPlaneShape2、開始インデックス = 0、インデックスの長さ = 12 となっています。このジオメトリには 2 つの四角形(または 4 つの三角形)があるため、こちらのインデックスの方が長くなっています。
2 番目のジオメトリでは、パス = |pPlane1|pPlaneShape1、開始インデックス = 12、インデックスの長さ = 6 となっています。このインデックスは、1 つの四角形(2 つの三角形)に対するものです。
注: ジオメトリが統合されている場合は、32 ビット インデックスでなく、16 ビット インデックス(符号なしの short)が返されることがあります。
次に示すのは、頂点およびフェースの識別子情報のダンプ結果です。
vertexid[0] = 0.000000 |
faceid[0] = 0.000000 |
vertexid[1] = 1.000000 | faceid[1] = 0.000000 |
vertexid[2] = 4.000000 | faceid[2] = 0.000000 |
vertexid[3] = 3.000000 | faceid[3] = 0.000000 |
vertexid[4] = 1.000000 | faceid[4] = 1.000000 |
vertexid[5] = 2.000000 | faceid[5] = 1.000000 |
vertexid[6] = 5.000000 | faceid[6] = 1.000000 |
vertexid[7] = 4.000000 | faceid[7] = 1.000000 |
vertexid[8] = 0.000000 | faceid[8] = 0.000000 |
vertexid[9] = 1.000000 | faceid[9] = 0.000000 |
vertexid[10] = 3.000000 | faceid[10] = 0.000000 |
vertexid[11] = 2.000000 | faceid[11] = 0.000000 |
各データ バッファの最初の 8 つのエントリは pPlaneShape2 に関するものであり、各データ バッファの最後の 4 つのエントリは pPlaneShape1 に関するものです。
データ バッファの検索用に提供されたインデックスをたどることにより、次のような三角形ごとの頂点 ID が得られます。
[0,1,3], [3,1,4], [1,2,4], [4,2,5], [0,1,2], [2,1,3]
最初の 4 つの三角形は pPlaneShape2 のものであり、最後の 2 つの三角形は pPlaneShape1 のものです。
対応するフェース ID の検索からは、次のように結果が得られます。
[0,0,0],[0,0,0],[1,1,1],[1,1,1], [0,0,0], [0,0,0]
最初の 2 つの三角形(pPlaneShape2)はフェース 0 に属し、次の 2 つの三角形(pPlaneShape2)はフェース 1 に属し、最後の 2 つの三角形は pPlaneShape1 のフェース 0 に属しています。
注: フェースおよび頂点の識別子情報は、シェーダの初期化中に適切な vertexid および faceid セマンティックを使用して頂点ストリームを照会することにより生成されます。
ビューポート 1 では、ジオメトリの統合と同等のものがありませんが、各シェーダの描画のために一連のジオメトリを送り返そうとすることがあります。これは、常にシェーダをバインドおよびバインド解除することを避けるための最適化です。
MPxHwShaderNode インタフェースの呼び出しパターンは、次のようになります。
一方、ビューポート 2.0 の MPxShaderOverride インタフェースの呼び出しパターンは、次のようになります。
ビューポート 2.0 では、すべての情報について描画は 1 回だけなので、次のようになります。
すべてのシェーダに共通するフレームごとの作業を存在させることが可能です。この場合、いくつかのオプションが使用できます。
MPxShaderOverride のコンテキスト内で、「フレーム スタンプ」を取得するための呼び出しを使用できます()。MDrawContext::getFrameStamp()たとえば、シェーダを複数回呼び出すときの、コストがかかるライティングの再計算を避けるために、glslShader および dx11Shader プラグインでは、フレーム スタンプの変更時のみ計算を実行します。
ビューポート 2.0 の通知コールバックは、グローバル レベルで使用できます。(MRenderer::addNotification(MHWRender::MPassContext::kBeginRenderSemantic)).
ビューポートごとの MUiMessage コールバック(add3dViewPreRenderMsgCallback())は、引き続きビューポート 2.0 でグローバル レベルの使用が可能です。
フレームと描画コンテキストの詳細については、「フレームと描画のコンテキスト」を参照してください。