39.Dx12版サンプルを全面書き換え(3)

今回は残りのDx12版サンプルを追加し、GitHub修正しました。
追加したのはSimpleSample013から0015です。Dx12版ライブラリ修正もあります。

この記事は、
コミットSimpleSample008(Dx12版)構造修正。
から、
コミットSimpleSample013から015(Dx12版)追加。ライブラリ修正あり。
の間の作業です。
GitHubサイト

https://github.com/WiZFramework/BaseCross

を参照して下さい。

Dx12に関する情報は、マイクロソフト社のDirectX-Graphics-Samplesが主な情報源となります。

マイクロソフト社のDirectX-Graphics-SamplesのGitHubサイトは以下になります。

https://github.com/Microsoft/DirectX-Graphics-Samples

ぜひ、興味ある方はダウンロードしてみてDx12研究を始めるといいと思います。

今回の追加のポイント

今回追加したサンプルのうち、特筆すべきはSimpleSample014です。
このサンプルはインスタンス描画を実装したもので、この処理により、動作が劇的に速くなります。
同じように複数のオブジェクト(グループ化したオブジェクト)を表示したサンプルはSimpleSample004ですが(こちらはスプライトですが構造を考える分にはわかると思います)、こちらがそれぞれ独立したコンスタントバッファを持ってるのに対して、インスタンス描画の場合はコンスタントバッファは一つです。それぞれのインスタンスの描画は頂点データの一部としてワールド行列をインプットします。
ですから、描画命令も1度です。1度の描画命令で、サンプルでは500個のインスタンスを描画しています。
実行イメージは以下になります。

2016090401

SimpleSample014について

SimpleSample014では、CubeObjectという構造体で1つ1つの立方体を表現し、CubeObjectGroupクラスでそれをグループ化(配列化)して管理します。Character.h/cppに記述があります。
SimpleSample004の場合、1つ1つの要素にコンスタントバッファを実装していましたが、今回のサンプルでは、すべてCubeObjectGroupクラスで管理します。ですから、構造上は今回のほうが単純と言えます。
コードでは、CubeObjectGroupクラスに、いつものようにルートシグネチャデスクプリタヒープなどのDx12リソースを保持して、それらを初期化します。いつもの1つのインスタンス描画と違うところは、メッシュリソースを頂点データのほかに行列データをメッシュリソースとして作成します。
実際のコードはCubeObjectGroup::CreateBuffers関数の下のほうにあります。


void CubeObjectGroup::CreateBuffers() {
    //中略
    //インスタンス行列バッファの作成
    //Max値で作成する
    vector<Matrix4X4> matrices(m_MaxInstance);
    for (auto& m : matrices) {
        m = Matrix4X4();
    }
    //インスタンス描画用の行列のメッシュ(内容変更できる)
    m_InstanceMatrixMesh = MeshResource::CreateMeshResource(matrices, true);
}

このようにMatrix4X4の配列を使ってメッシュリソースを作成します。
このテクニックはMeshResource::CreateMeshResource関数がテンプレート関数になっているから可能で、好みの型をもとに、頂点バッファを作成できるのです。頂点というとVertexPositionNormalTextureなどの頂点データを想像しますが、ほとんどのケースではそのようなバッファを作成するのですが、内部的には(Dx12から見れば)、行列型であってもパイプラインにインプットできる型として認識するので、このような実装が可能です。
このようにして作成したm_InstanceMatrixMeshm_MaxInstanceの大きさを持ちます。(ここでは2000個です)。このサイズを超えなければ、バッファ内に行列を設定して、好みの数だけインスタンス描画できるようになります。
続いてシェーダですが、シェーダが通常の描画と違うのは頂点シェーダだけです。シェーダはDx11版と兼用できるので、DxSharedプロジェクトに入ってますが、VSPNTInstance.hlslが実体です。ここでは、VertexPositionNormalTextureMatrixという、頂点データにワールド行列を足した頂点インプットデータとして扱います。
この設定をしているのはパイプラインステート作成のところです。


///パイプラインステート作成
void CubeObjectGroup::CreatePipelineState() {
    //パイプラインステートの作成
    D3D12_GRAPHICS_PIPELINE_STATE_DESC PineLineDesc;
    PipelineState::CreateDefault3D<VertexPositionNormalTextureMatrix, VSPNTInstance, PSPNTStatic>(m_RootSignature, PineLineDesc);
    PineLineDesc.RasterizerState.FillMode = D3D12_FILL_MODE::D3D12_FILL_MODE_SOLID;
    PineLineDesc.RasterizerState.CullMode = D3D12_CULL_MODE::D3D12_CULL_MODE_BACK;
    m_PipelineState = PipelineState::CreateDirect(PineLineDesc);
}

このようにVertexPositionNormalTextureMatrix型の頂点データを入力用として登録しています。
あと、動的に各立方体の情報から500個ぶんの行列を作成し、描画時にパイプラインに渡すわけですが、その処理はCubeObjectGroup::DrawObject関数にあります。


///描画処理
void CubeObjectGroup::DrawObject() {

    //中略
    m_InstanceMatrixMesh->UpdateResources<Matrix4X4>(m_CommandList);

    //中略

    //インスタンス描画は、頂点バッファを複数登録する
    const D3D12_VERTEX_BUFFER_VIEW Buf[2] = { 
        m_CubeMesh->GetVertexBufferView(),
        m_InstanceMatrixMesh->GetVertexBufferView() 
    };
    m_CommandList->IASetVertexBuffers(0, 2, Buf);
    m_CommandList->DrawIndexedInstanced(m_CubeMesh->GetNumIndicis(), m_CubeObjectVec.size(), 0, 0, 0);

    //中略
}

このようにインスタンス描画に関係する部分以外は中略してますがm_CommandList->IASetVertexBuffers関数呼び出しのパラメータに、通常のメッシュとインスタンス描画用の行列のバッファを渡します。
そのうえでm_CommandList->DrawIndexedInstanced関数呼び出しで、m_CubeObjectVec.size()の数だけ描画するわけです。意外と単純です。
実は、Dx11からDx12になりDrawIndexed関数という、1つのオブジェクトを描画するという命令文はなくなりました。つまり、通常の描画がインスタンス描画なのです。1つの描画1つのインスタンス描画と同じ意味になります。ですから、このインスタンス描画については、実装の手間は、Dx11版より楽かもしれません(もちろん、リソース作成の手間など前準備はDx11に比べ物にならないくらい複雑ですが)。このインスタンス描画だけを考えれば、ということです。

まとめ

ここまでのBaceCrossの開発で、ひとまず大きな節目を迎えました。
シンプルバージョンのサンプル作成は、これでひと段落として、次回に向けてはフルバージョンの実装をしていきたいと思います。
フルバージョンDxBass2016や2015を実装した人はわかると思いますが、ステージという概念が加わり、各プロパティやオブジェクトの性能を決めるためにコンポーネント方式を採用しています。
とりあえずはDxBase2016と同じ機能をBaseCrossにも実装する作業になるかと思います。また一部のアルゴリズム(自動衝突判定など)は、修正が入る予定です。
DxBase2016Dx11版オンリーなのでBaseCrossDx11フルバージョンについては比較的早く実装できると思います。Dx12版は始めて実装する部分も多いので、それなりに開発時間がかかるとは思いますが、そうはいっても、これまでシンプルバージョンのサンプルの作成で、結構いろんな描画方法に挑戦してきたので、ある程度の材料はできてるかなと感じています。

これまでDx12版の開発を通して実験と実装を行ってきたわけですが、一通り実装してみて、GPUという大変高性能なハードウェアを扱うのには、かなりな部分の人間からの歩み寄りが必要なのだなあ、とつくづく感じました。
そしてまたGPUは、その描画性能を最大限に生かすためにしっかりとした前準備を必要とし、さらには一回設定したものはなるべく変化させないといった、ハードウェアならではの特徴を持ちます(抽象的な表現ですみません)
とはいえ、ゲームで扱うのは頂点データのかたまりではなくプレイヤー敵キャラといった、バーチャルではあるけど生きたオブジェクトです。
そういう意味でガチガチのGPUにたいして生きたキャラクターの間を取り持つのが、BaseCross皆さんが作成しているであろうライブラリ、そしてUnityなど、のミドルウェアになるわけです。
Dx12の時代になり、GPUもどんどん進化しています。そういう意味でも、ミドルウェアに求められる機能は、ますます大きなものになっていくのだなあ、と感じる次第です。

そんなわけで、今後も是非、アクセスいただけましたら幸いです。
次回もよろしくお願いします。

カテゴリー

ピックアップ記事

  1. 2016092201
    今回は前回のサンプルを少し機能を追加しまして、いろんなオブジェクトを追加しています。FullTuto…
  2. 2016092001
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル003をアップしました。内…
  3. eyecatch
    前回更新から時間がたってしまいましたが、今回はフルバージョンチュートリアル002で懸案となっていまし…
PAGE TOP