チュートリアル: 頂点の網かけによるオブジェクトの不足
このチュートリアルでは、Visual Studio のグラフィックス診断ツールを使用して、頂点シェーダー ステージの間に発生するエラーによって見つからないオブジェクトを調査する方法を示します。
このチュートリアルでは、次の作業について説明します。
[グラフィックス イベント一覧] を使用して、問題の原因となる可能性がある部分を検索します。
[グラフィックス パイプライン ステージ] ウィンドウを使用して、DrawIndexed Direct3D API 呼び出しの効果を確認します。
HLSL デバッガーを使用して、頂点シェーダーを調査します。
[グラフィックス イベント呼び出し履歴] を使用して、正しくない HLSL 定数の原因を突き止めることができます。
シナリオ
3-D アプリケーションのオブジェクトが欠落する一般的な原因の 1 つは、頂点シェーダーによる不適切なまたは予期しない方法によるオブジェクトの頂点の変換です。たとえば、オブジェクトが非常に小さいサイズに縮小される場合や、オブジェクトがカメラの前面にではなく、背後に表示される場合などがあります。
このシナリオでは、テストのアプリケーションを実行すると、背景は予期したとおりに表示されますが、オブジェクトの 1 つが表示されません。グラフィックス診断を使用すると、グラフィックス ログに問題をキャプチャして、アプリケーションのデバッグを実行できます。問題は、アプリケーションでは次のように見えます。
調査
グラフィックス診断ツールを使用すると、グラフィックス ログ ファイルを読み込んで、テスト中にキャプチャされたフレームを検査することができます。
グラフィックス ログのフレームを調査するには
Visual Studio で、オブジェクトの欠落を表すフレームを含むグラフィックス ログを読み込みます。新しいグラフィックス ログのタブが Visual Studio に表示されます。このタブの上部に、選択されたフレームのレンダー ターゲットが出力されます。下部の [フレーム一覧] には、キャプチャされた各フレームがサムネイル イメージとして表示されます。
[フレーム一覧] で、オブジェクトが表示されないことを示すフレームを選択します。レンダー ターゲットが更新され、選択したフレームが反映されます。このシナリオでは、グラフィックス ログのタブは次のように表示されます。
問題を示すフレームを選択したら、[グラフィックス イベント一覧] を使用して診断を開始できます。[グラフィックス イベント一覧] には、アクティブなフレームをレンダリングするために行われたすべての Direct3D API 呼び出し (たとえば、デバイスの状態の設定、バッファーの作成および更新、フレームに表示されるオブジェクトの描画を行う各 API 呼び出しなど) が含まれます。アプリが想定どおりに動作しているときは、さまざまな種類の呼び出し (描画、ディスパッチ、コピー、クリアなどの呼び出し) が関与します。(必ずではありませんが) 多くの場合、対応する変更がレンダー ターゲットで発生するからです。描画呼び出しは、それぞれがアプリでレンダリングされたジオメトリを表すので特に重要です (ディスパッチ呼び出しでもジオメトリをレンダリングできます)。
このケースでは、欠落しているオブジェクトはレンダー ターゲットへ描画されず、残りのシーンは予期したとおりに描画されることがわかっているため、グラフィックス パイプライン ステージ ツールと共に [グラフィックス イベント一覧] を使用して、どの描画呼び出しが欠落しているオブジェクトのジオメトリに対応しているかを確認できます。[グラフィックス パイプライン ステージ] ウィンドウには、各描画呼び出しに対して送信されたジオメトリが、レンダー ターゲットに与える影響には関係なく表示されます。描画呼び出し間を移動すると、選択されている呼び出しに関連付けられたジオメトリを表示するようにパイプライン ステージが更新され、その呼び出しが完了した後のレンダー ターゲットの状態を表示するようにレンダー ターゲットの出力が更新されます。
表示されないジオメトリの描画呼び出しを見つけるには
[グラフィックス イベント一覧] ウィンドウを開きます。グラフィックス診断ツール バーで、[イベント一覧] を選択します。
[グラフィックス パイプライン ステージ] ウィンドウを開きます。グラフィックス診断ツール バーで、[パイプライン ステージ] を選択します。
[グラフィックス イベント一覧] ウィンドウの各描画呼び出しを移動する中で、欠落しているオブジェクトがあるかどうか [グラフィックス パイプライン ステージ] ウィンドウで確認してください。この操作を簡単に行うには、[グラフィックス イベント一覧] ウィンドウの右上隅にある [検索] ボックスに「Draw」と入力します。これによって一覧がフィルター処理され、タイトルに "Draw" を含むイベントのみが一覧に表示されます。
[グラフィックス パイプライン ステージ] ウィンドウで、[入力アセンブラー] ステージには変換する前のオブジェクトのジオメトリが表示され、[頂点シェーダー] ステージには変換後の同じオブジェクトが表示されます。このシナリオでは、[入力アセンブラー] ステージにオブジェクトが表示されたときに欠落しているオブジェクトが見つかり、[頂点シェーダー] ステージには何も表示されないことがわかります。
[!メモ]
他のジオメトリ ステージ (ハル シェーダー、ドメイン シェーダー、ジオメトリ シェーダーなど) でオブジェクトが処理された場合、それが問題の原因である可能性があります。通常、結果が表示されない、または予期しない方法で表示される問題は、最も早いステージに関連しています。
欠落したオブジェクトに対応する描画呼び出しに達したら停止します。このシナリオでは、[グラフィックス パイプライン ステージ] ウィンドウに、ジオメトリが GPU に発行された (入力アセンブラー サムネイルによって示されます) にもかかわらず、頂点シェーダー ステージで何か問題が発生したためレンダー ターゲットに表示されなかった (頂点シェーダー サムネイルによって示されます) ことを示します。
アプリケーションによって描画呼び出しが発行され、問題が頂点シェーダー ステージの間に発生したことを確認したら、HLSL デバッガーを使用して頂点シェーダーを調べ、オブジェクトのジオメトリで何が起こったのかを確認できます。HLSL デバッガーを使用して、実行中の HLSL 変数の状態を調べ、HLSL コードを順を追って確認し、ブレークポイントを設定することによって、問題の診断に役立てることができます。
頂点シェーダーを調査するには
頂点シェーダー ステージのデバッグを開始します。[グラフィックス パイプライン ステージ] ウィンドウの [頂点シェーダー] ステージで、[デバッグ開始] ボタンをクリックします。
[入力アセンブラー] ステージから頂点シェーダーに渡されるデータは正常であり、[頂点シェーダー] ステージで出力が生成されないように見えるため、頂点シェーダーの出力構造体である output をチェックする必要があります。HLSL コードを順を追って確認する際に、output が変更されている部分の詳細を確認します。
最初に output が変更されている部分には、メンバー worldPos が書き込まれています。
値は適切であるように見えるため、output が変更される次の行までコードを順を追って確認します。
次に output が変更されている部分には、メンバー pos が書き込まれています。
今回、pos のメンバーの値はすべてゼロであるため、疑わしいように見えます。次に、output.pos の値がすべてゼロになった理由を確認します。
output.pos は temp という名前の変数から値を受け取ることがわかります。前の行から、temp の値は、前の値と projection という名前の定数を乗算した結果であることがわかります。temp の疑わしい値はこの乗算の結果であることが推察できます。projection にポインターを置くと、この値もすべてゼロであることがわかります。
このシナリオでは、temp の疑わしい値が projection との乗算によって生じている可能性が高く、projection は投影行列が含まれると見なされる定数であるため、含まれる値のすべてがゼロであってはならないことが調査によってわかりました。
アプリケーションによってシェーダーに渡される HLSL 定数 projection がこの問題の原因であることを確認したら、次の手順では、アプリケーションのソース コード内の定数バッファーが指定されている場所を検索します。この場所を検索するには、[グラフィックス イベント呼び出し履歴] を使用できます。
アプリケーションのソース コード内で定数が指定されている場所を検索するには
[グラフィックス イベント呼び出し履歴] ウィンドウを開きます。グラフィックス診断ツール バーで、[グラフィックス イベント呼び出し履歴] を選択します。
ご使用のアプリケーションのソース コードの呼び出し履歴の上へ移動します。[グラフィックス イベント呼び出し履歴] ウィンドウで、最上位の呼び出しを選択して定数バッファーがそこに指定されているかどうかを確認します。指定されていない場合、指定されている場所が見つかるまで呼び出し履歴の上へ検索を続けます。このシナリオでは、UpdateSubresource Direct3D API を使用して、定数バッファーが呼び出し履歴のさらに上にある MarbleMaze::Render という名前の関数に指定されていて、その値は m_marbleConstantBufferData という名前の定数バッファー オブジェクトから取得されたことがわかります。
ヒント 同時にアプリケーションのデバッグを行うと、この位置にブレークポイントが設定され、次のフレームが表示されるとヒットします。これで、定数バッファーが指定されたときに m_marbleConstantBufferData のメンバーを調べ、projection メンバーの値がすべてゼロに設定されていることを確認できます。
定数バッファーが指定されている場所を確認し、その値が変数 m_marbleConstantBufferData から取得されたことがわかったら、次の手順では、m_marbleConstantBufferData.projection メンバーがすべてゼロに設定されている場所を検索します。[すべての参照の検索] を使用して、m_marbleConstantBufferData.projection の値を変更するコードをすばやくスキャンできます。
アプリケーションのソース コード内で projection メンバーが指定されている場所を検索するには
m_marbleConstantBufferData.projection への参照を検索します。変数 m_marbleConstantBufferData のショートカット メニューを開き、[すべての参照の検索] をクリックします。
アプリケーションのソース コード上の projection メンバーが変更されている場所の行に移動するには、[シンボルの検索結果] ウィンドウで、その行を選択します。projection メンバーを変更する最初の結果が問題の原因ではない可能性があるため、アプリケーションのソース コード内の複数の領域を確認する必要があります。
m_marbleConstantBufferData.projection が指定されている場所が見つかったら、周囲のソース コードを確認し、正しくない値の発生元を特定できます。このシナリオでは、m_marbleConstantBufferData.projection の値が projection というローカル変数に指定され、その後、次の行にあるコード m_camera->GetProjection(&projection); で指定された値に初期化されていることがわかります。
この問題を解決するには、ローカル変数 projection の値を初期化する行の後に m_marbleConstantBufferData.projection の値を設定するコード行を移動します。
コードを修正したら、それを再度ビルドし、再度アプリケーションを実行してレンダリングの問題が解決されたかどうかを確認します。