004.複数の四角形の描画(Dx11版)

 このサンプルはSimplSample004というディレクトリに含まれます。
 BaseCrossDx11.slnというソリューションを開くとDx11版が起動します。

 実行結果は以下のような画面が出ます。

 

図0004a

 


 動画は以下になります。

 

 

【共通解説】

 Dx12、Dx11両方に共通なのはシェーダーです。DxSharedプロジェクト内にシェーダファイルというフィルタがあり、そこに記述されてます。
 今回使用するシェーダは頂点シェーダとピクセルシェーダです。VertexPositionColor型の頂点を持ち、コンスタントバッファからの入力で、位置を変更させています。
 SimpleSample002と同じシェーダです。
 このサンプルは複数のオブジェクトをまとめて処理するサンプルです。
 ゲーム盤上にはさまざまなオブジェクトが配置されますが、スケーリング、回転、位置のみが違ってテクスチャや描画シェーダが同じもの(グループ)は、結構あります。このサンプルではそのようなオブジェクトグループを配置する場合のサンプルとなります。

【Dx11版解説】

 BaseCrossDx11.slnを開くと、BaseCrossDx11というメインプロジェクトがあります。この中のCharacter.h/cppが主な記述個所になります。

■初期化■

 Dx11版Dx12版同様、それぞれの四角形を定義するSquareSprite構造体とそれをまとめるSquareSpriteGroupで構成されます。Dx12版との違いは、ルートシグネチャやパイプラインステートといったDx12リソースのようなものはありません。
 ですから、初期化は非常にシンプルです。SquareSpriteGroup::OnCreate()関数で行ってますがメッシュリソースを作成した後は、各四角形のデータの初期化のみとなります。
void SquareSpriteGroup::OnCreate() {
    float HelfSize = 0.5f;
    //頂点配列
    m_BackupVertices = {
        { VertexPositionColor(Vec3(-HelfSize, HelfSize, 0), Col4(1.0f, 0.0f, 0.0f, 1.0f)) },
        { VertexPositionColor(Vec3(HelfSize, HelfSize, 0), Col4(0.0f, 1.0f, 0.0f, 1.0f)) },
        { VertexPositionColor(Vec3(-HelfSize, -HelfSize, 0), Col4(0.0f, 0.0f, 1.0f, 1.0f)) },
        { VertexPositionColor(Vec3(HelfSize, -HelfSize, 0), Col4(1.0f, 0.0f, 1.0f, 1.0f)) },
    };
    //インデックス配列
    vector<uint16_t> indices = { 0, 1, 2, 1, 3, 2 };
    //メッシュの作成
    //頂点変更できる
    m_SquareSpriteMesh = MeshResource::CreateMeshResource(m_BackupVertices, indices, true);
    //グループの配列の作成
    m_SquareSpriteVec.assign(100, SquareSprite());
    for (auto& v : m_SquareSpriteVec) {
        v.m_LocalRot = Util::RandZeroToOne(true);
        v.m_LocalRotVelocity = Util::RandZeroToOne(true) * 20.0f - 10.0f;
        v.m_LocalPos = Vec2(0, 0);
        v.m_LocalVelocity = Vec2(Util::RandZeroToOne(true) * 200.0f - 100.0f,
             100 + Util::RandZeroToOne(true) * 100.0f);
        v.m_LocalGravityVelocity = Vec2(0, 0);
    }
}
 これで初期化終了です。

■更新処理■

 更新処理SquareSpriteGroup::OnUpdate()関数で行います。ただ、ここでは以下のように別の関数を呼んでいます。
void SquareSpriteGroup::OnUpdate() {
    float ElapsedTime = App::GetApp()->GetElapsedTime();
    UpdateObjects(ElapsedTime);
}
 呼ばれたほうの関数は以下です。
//各オブジェクトの位置等の変更
void SquareSpriteGroup::UpdateObjects(float ElapsedTime) {
    float h = static_cast<float>(App::GetApp()->GetGameHeight());
    h /= 2.0f;
    for (auto& v : m_SquareSpriteVec) {
        if (v.m_LocalPos.y < -h) {
            v.m_LocalRot = Util::RandZeroToOne(true);
            v.m_LocalRotVelocity = Util::RandZeroToOne(true) * 20.0f - 10.0f;
            v.m_LocalPos = Vec2(0, 0);
            v.m_LocalVelocity = Vec2(Util::RandZeroToOne(true) * 200.0f - 100.0f,
                 100 + Util::RandZeroToOne(true) * 100.0f);
            v.m_LocalGravityVelocity = Vec2(0, 0);
        }
        else {
            v.m_LocalRot += v.m_LocalRotVelocity * ElapsedTime;
            v.m_LocalPos += v.m_LocalVelocity * ElapsedTime;
            v.m_LocalGravityVelocity += Vec2(0, -98.0f) * ElapsedTime;
            v.m_LocalPos += v.m_LocalGravityVelocity * ElapsedTime;
        }
    }
}
 ここでは各オブジェクトのそれぞれの位置や回転などを、ターン時間に合わせて変化させながら、設定しています。範囲外に出てしまったオブジェクトは、パラメータを初期化して再登場させています。

■描画処理■

 描画処理Dx11版コンスタントバッファシングルトンとして実装されているので、各四角形に持たせる必要はありません。以下は描画処理です。
void SquareSpriteGroup::OnDraw() {
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pD3D11DeviceContext = Dev->GetD3DDeviceContext();
    auto RenderState = Dev->GetRenderState();


    //中略

    //グループをまとめて描画
    for (auto& v : m_SquareSpriteVec) {
        //行列の定義
        Mat4x4 World, Proj;
        //ワールド行列の決定
        World.affineTransformation2D(
            v.m_LocalScale,         //スケーリング
            Vec2(0, 0),     //回転の中心(重心)
            v.m_LocalRot,               //回転角度
            v.m_LocalPos                //位置
        );
        //射影行列の決定
        float w = static_cast<float>(App::GetApp()->GetGameWidth());
        float h = static_cast<float>(App::GetApp()->GetGameHeight());
        Proj = XMMatrixOrthographicLH(w, h, -1.0, 1.0f);
        //行列の合成
        World *= Proj;

        //コンスタントバッファの準備
        SpriteConstantBuffer sb;
        //エミッシブ加算は行わない。
        sb.Emissive = Col4(0, 0, 0, 0);
        //行列の設定
        sb.World = World;
        //コンスタントバッファの更新
        pD3D11DeviceContext->UpdateSubresource(CBSprite::GetPtr()->GetBuffer(), 0, nullptr, &sb, 0, 0);
        //コンスタントバッファの設定
        ID3D11Buffer* pConstantBuffer = CBSprite::GetPtr()->GetBuffer();
        ID3D11Buffer* pNullConstantBuffer = nullptr;
        //頂点シェーダに渡す
        pD3D11DeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
        //ピクセルシェーダに渡す
        pD3D11DeviceContext->PSSetConstantBuffers(0, 1, &pConstantBuffer);
        //描画
        pD3D11DeviceContext->DrawIndexed(m_SquareSpriteMesh->GetNumIndicis(), 0, 0);
    }
    //後始末
    Dev->InitializeStates();
}
 赤くなっているところが、各四角形の描画を行っているところです。コンスタントバッファ関連CBSprite::GetPtr()->GetBuffer()のようにシングルトンのポインタを利用することができます。
        //コンスタントバッファの準備
        SpriteConstantBuffer sb;
        //エミッシブ加算は行わない。
        sb.Emissive = Col4(0, 0, 0, 0);
        //行列の設定
        sb.World = World;
        //コンスタントバッファの更新
        pD3D11DeviceContext->UpdateSubresource(CBSprite::GetPtr()->GetBuffer(), 0, nullptr, &sb, 0, 0);
        //コンスタントバッファの設定
        ID3D11Buffer* pConstantBuffer = CBSprite::GetPtr()->GetBuffer();
        ID3D11Buffer* pNullConstantBuffer = nullptr;
        //頂点シェーダに渡す
        pD3D11DeviceContext->VSSetConstantBuffers(0, 1, &pConstantBuffer);
        //ピクセルシェーダに渡す
        pD3D11DeviceContext->PSSetConstantBuffers(0, 1, &pConstantBuffer);
 以上で描画処理は終了です。

【まとめ】

 今回は複数の同じオブジェクトの描画を行いました。ただ今回のように、ワールド行列のみ違うオブジェクトの複数描画であればSimplSample014で紹介するインスタンス描画のほうが効率が良いかもしれません。
 いろんなケースに応じて今回のサンプルのような方法をとるか、インスタンス描画をとるかを検討してみるといいと思います。