チュートリアル: 頂点の網かけによるオブジェクトの不足
このチュートリアルでは、 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);
で指定された値に初期化されていることがわかります。この問題を解決するには、ローカル変数
m_marbleConstantBufferData.projection
の値を初期化する行の後にprojection
の値を設定するコード行を移動します。コードを修正したら、それをリビルドし、もう一度アプリを実行してレンダリングの問題が解決されたかどうかを確認します。