チュートリアル: 頂点の網かけによるオブジェクトの不足

このチュートリアルでは、 Visual Studio のグラフィックス診断ツールを使用して、頂点シェーダー ステージの間に発生するエラーが原因で欠落しているオブジェクトを調査する方法を示します。

このチュートリアルでは、次の作業について説明します。

  • [グラフィックス イベント一覧] を使用して、問題の原因となる可能性がある部分を検索します。

  • [グラフィックス パイプライン ステージ] ウィンドウを使用して、 DrawIndexed Direct3D API 呼び出しの影響を確認します。

  • HLSL デバッガー を使用して、頂点シェーダーを調査します。

  • [グラフィックス イベント呼び出し履歴] を使用して、正しくない HLSL 定数の原因を突き止めることができます。

シナリオ

3-D アプリのオブジェクトが欠落する一般的な原因の 1 つは、頂点シェーダーによるオブジェクトの頂点の変換が正しくない方法や予期しない方法で行われることです。たとえば、オブジェクトが非常に小さいサイズに縮小される場合や、オブジェクトがカメラの前面にではなく、背後に表示されるように変換される場合などがあります。

このシナリオでは、テストのためにアプリを実行すると、背景は期待どおりにレンダリングされますが、オブジェクトの 1 つが表示されません。 グラフィックス診断を使うと、問題点をグラフィックス ログにキャプチャし、アプリのデバッグを実行できます。 問題は、アプリケーションでは次のように見えます。

オブジェクトを表示できません。

調査

グラフィックス診断ツールを使うと、グラフィックス ログ ファイルを読み込んで、テスト中にキャプチャされたフレームを検査できます。

グラフィックス ログのフレームを調査するには

  1. Visual Studioで、オブジェクトの欠落を表すフレームを含むグラフィックス ログを読み込みます。 新しいグラフィックス ログのタブが Visual Studioに表示されます。 このタブの上部に、選択されたフレームのレンダー ターゲットが出力されます。 下部の [フレーム一覧] には、キャプチャされた各フレームの縮小版が表示されます。

  2. [フレーム一覧] で、オブジェクトが表示されないことを示すフレームを選択します。 レンダー ターゲットが更新され、選択したフレームが反映されます。 このシナリオでは、グラフィックス ログのタブは次のように表示されます。

    Visual Studio のグラフィックス ログ ドキュメント

    問題を示しているフレームを選んだら、 [グラフィックス イベント一覧] を使用して診断を開始できます。 [グラフィックス イベント一覧] には、アクティブなフレームをレンダリングするために実行されたすべての Direct3D API 呼び出しが含まれています。たとえば、デバイスの状態の設定、バッファーの作成と更新、フレームに表示するオブジェクトの描画などを行う API 呼び出しです。 アプリが想定どおりに動作しているときは、描画、ディスパッチ、コピー、クリア呼び出しなどのさまざまな種類の呼び出しが関与します。(必ずではありませんが) 多くの場合、対応する変更がレンダー ターゲットで発生するからです。 描画呼び出しは、それぞれがアプリでレンダリングされたジオメトリを表すため、特に重要です (ディスパッチ呼び出しでもジオメトリをレンダリングできます)。

    このケースでは、欠落しているオブジェクトはレンダー ターゲットへ描画されず、残りのシーンは予期したとおりに描画されることがわかっているため、 [グラフィックス パイプライン ステージ] ツールと共に [グラフィックス イベント一覧] を使用して、欠落しているオブジェクトのジオメトリにどの描画呼び出しが対応しているかを判別できます。 [グラフィックス パイプライン ステージ] ウィンドウには、レンダー ターゲットに対する影響とは無関係に、各描画呼び出しに送られたジオメトリが表示されます。 描画呼び出し間を移動すると、選択されている呼び出しに関連付けられたジオメトリを表示するようにパイプライン ステージが更新され、その呼び出しが完了した後のレンダー ターゲットの状態を表示するようにレンダー ターゲットの出力が更新されます。

表示されないジオメトリに対応する描画呼び出しを見つけるには

  1. [グラフィックス イベント一覧] ウィンドウを開きます。 [グラフィックス診断] ツール バーで、 [イベント一覧] を選択します。

  2. [グラフィックス パイプライン ステージ] ウィンドウを開きます。 [グラフィックス診断] ツール バーで、 [パイプライン ステージ] を選択します。

  3. [グラフィックス イベント一覧] ウィンドウで各描画呼び出し間を移動しながら、 [グラフィックス パイプライン ステージ] ウィンドウで、表示されないオブジェクトを探します。 この操作を簡単に行うには、 [グラフィックス イベント一覧] ウィンドウの右上隅にある [検索] ボックスに「Draw」と入力します。 これによって一覧がフィルター処理され、タイトルに "Draw" を含むイベントのみが一覧に表示されます。

    [グラフィックス パイプライン ステージ] ウィンドウで、 [入力アセンブラー] ステージは変換される前のオブジェクトのジオメトリを示し、 [頂点シェーダー] ステージは変換された後の同じオブジェクトを示します。 このシナリオでは、 [入力アセンブラー] ステージにオブジェクトが表示されたときに欠落しているオブジェクトが見つかり、 [頂点シェーダー] ステージには何も表示されないことがわかります。

    注意

    他のジオメトリ ステージ (ハル シェーダー、ドメイン シェーダー、ジオメトリ シェーダーなど) でオブジェクトが処理された場合、それが問題の原因である可能性があります。 多くの場合、結果が表示されない、または予期しない表示になる最も早いステージが、問題に関連しています。

  4. 表示されないオブジェクトに対応する描画呼び出しに到達したら、停止します。 このシナリオで、 [グラフィックス パイプライン ステージ] ウィンドウは、ジオメトリが GPU に発行された (入力アセンブラー サムネイルによって示されます) にもかかわらず、頂点シェーダー ステージで何か問題が発生したためレンダー ターゲットに表示されなかった (頂点シェーダー サムネイルによって示されます) ことを示します。

    DrawIndexed イベントとパイプラインに対するその効果

    表示されないオブジェクトのジオメトリについてアプリによって描画呼び出しが発行され、問題が頂点シェーダー ステージの間に発生したことを確認したら、HLSL デバッガーを使用して頂点シェーダーを調べ、オブジェクトのジオメトリで何が起こったのかを確認できます。 HLSL デバッガーを使用して、実行中の HLSL 変数の状態を調べ、HLSL コードをステップスルーし、ブレークポイントを設定することによって、問題の診断に役立てることができます。

頂点シェーダーを調査するには

  1. 頂点シェーダー ステージのデバッグを開始します。 [グラフィックス パイプライン ステージ] ウィンドウの [頂点シェーダー] ステージで、 [デバッグ開始] ボタンを選びます。

  2. [入力アセンブラー] ステージから頂点シェーダーに渡されるデータは正常であり、 [頂点シェーダー] ステージで出力が生成されないように見えるため、頂点シェーダーの出力構造体である outputを調べる必要があります。 HLSL コードをステップスルーする際に、 output が変更されている部分の詳細を確認します。

  3. 最初に output が変更されている部分には、メンバー worldPos が書き込まれています。

    "output.worldPos" の値は妥当です

    値は適切であるように見えるため、 outputが変更される次の行までコードをステップスルーします。

  4. 次に output が変更されている部分には、メンバー pos が書き込まれています。

    "output.pos" の値がゼロに設定されています

    今回、 pos のメンバーの値はすべてゼロであるため、疑わしいように見えます。 次に、 output.pos の値がすべてゼロになった理由を確認します。

  5. output.postempという名前の変数から値を受け取ることがわかります。 前の行から、 temp の値は、前の値と projectionという名前の定数を乗算した結果であることがわかります。 tempの疑わしい値はこの乗算の結果であることが推察できます。 projectionにポインターを置くと、この値もすべてゼロであることがわかります。

    射影行列には不適切な変換が含まれています

    このシナリオでは、 tempの疑わしい値が projectionとの乗算によって生じている可能性が高く、 projection は投影行列が含まれると見なされる定数であるため、含まれる値のすべてがゼロであってはならないことが調査によってわかりました。

    アプリによってシェーダーに渡される HLSL 定数 projectionがこの問題の原因である可能性があることを確認したら、次の手順では、アプリのソース コード内の定数バッファーが指定されている場所を検索します。 この場所を検索するには、 [グラフィックス イベント呼び出し履歴] を使用できます。

アプリのソース コード内で定数が設定されている場所を検索するには

  1. [グラフィックス イベント呼び出し履歴] ウィンドウを開きます。 [グラフィックス診断] ツール バーで、 [グラフィックス イベント呼び出し履歴] を選びます。

  2. アプリのソース コードの呼び出し履歴の上へ移動します。 [グラフィックス イベント呼び出し履歴] ウィンドウで、最上位の呼び出しを選んで定数バッファーがそこに指定されているかどうかを確認します。 指定されていない場合、指定されている場所が見つかるまで呼び出し履歴の上へ検索を続けます。 このシナリオでは、 UpdateSubresource Direct3D API を使用して、定数バッファーが呼び出し履歴のさらに上にある MarbleMaze::Renderという名前の関数に指定されていて、その値は m_marbleConstantBufferDataという名前の定数バッファー オブジェクトから取得されたことがわかります。

    オブジェクトの定数バッファーを設定するコード

    ヒント

    同時にアプリのデバッグを行っている場合、この位置にブレークポイントを設定して次のフレームが表示されるとヒットするようにできます。 これで、定数バッファーが指定されたときに m_marbleConstantBufferData のメンバーを調べ、 projection メンバーの値がすべてゼロに設定されていることを確認できます。

    定数バッファーが指定されている場所を確認し、その値が変数 m_marbleConstantBufferData から取得されたことがわかったら、次の手順では、m_marbleConstantBufferData.projection メンバーがすべてゼロに設定されている場所を検索します。 [すべての参照の検索] を使用して、 m_marbleConstantBufferData.projectionの値を変更するコードをすばやくスキャンできます。

アプリのソース コード内で projection メンバーが設定されている場所を検索するには

  1. m_marbleConstantBufferData.projectionへの参照を検索します。 変数 m_marbleConstantBufferDataのショートカット メニューを開き、 [すべての参照の検索] を選びます。

  2. アプリのソース コード上の projection メンバーが変更されている場所の行に移動するには、 [シンボルの検索結果] ウィンドウで、その行を選びます。 projection メンバーを変更する最初の結果が問題の原因ではない可能性があるため、アプリのソース コード内の複数の領域を確認する必要があります。

    m_marbleConstantBufferData.projection が設定されている場所が見つかったら、周囲のソース コードを確認し、正しくない値の発生元を特定できます。 このシナリオでは、 m_marbleConstantBufferData.projection の値が projection というローカル変数に設定され、その後、次の行にあるコード m_camera->GetProjection(&projection); で指定された値に初期化されていることがわかります。

    マーブル射影を初期化前に設定します

    この問題を解決するには、ローカル変数 m_marbleConstantBufferData.projection の値を初期化する行の後に projectionの値を設定するコード行を移動します。

    修正された C++ ソース コード

    コードを修正したら、それをリビルドし、もう一度アプリを実行してレンダリングの問題が解決されたかどうかを確認します。

    現在表示されているオブジェクト。