003.四角形の回転と移動(Dx12版)

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

 どちらのソリューションでも、実行結果は以下のような画面が出ます。単純な四角形が回転しながら左右に移動します。

 

図0003a

 


 動画は以下になります。

 

 

【共通解説】

 Dx12、Dx11両方に共通なのはシェーダーです。DxSharedプロジェクト内にシェーダファイルというフィルタがあり、そこに記述されてます。
 今回使用するシェーダは頂点シェーダとピクセルシェーダです。VertexPositionColor型の頂点を持ち、コンスタントバッファからの入力で、位置を変更させています。
 SimpleSample002と同じシェーダです。

 また更新処理も共通になります。SquareSprite::OnUpdate関数には、四角形が左右に移動しながら回転する記述がされています。

【Dx12版解説】

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

■初期化■

 初期化はSquareSprite::OnCreate関数から呼ばれます。
 ここではまず、頂点の配列を作成し、そこからメッシュを作成しています。今回のメッシュにはインデックスバッファが含まれます。前サンプルでは、描画は三角形を一つ描画しただけなので頂点を使って描画しましたが、ここからのサンプルでは、ほとんどのケースでインデックス描画をします。
 インデックス描画というのは頂点に番号を付けて、その番号を使って連続した三角形を定義し(これをインデックスといいます)、そのインデックスを使って描画します。頂点を使って直接描画するより効率がよくなります。
 そのため、ここでの初期化処理では、頂点を作成したあとインデックスを作成しています。
 頂点の数は4つです。その4つを三角形二つの描画に割り当てるのですが、頂点の順番を考えた場合0, 1, 2, 1, 3, 2という順番で3角形を二つ描画すると考えれば、頂点を使いまわししながら描画できるのがわかります。(つまり頂点の数が少なくすむ)。
 この0, 1, 2, 1, 3, 2がインデックスです。単純な番号の配列ですが、それをインデックスバッファという形で作成します。
 インデックスを含むメッシュの作成は簡単です。頂点の配列インデックスの配列を作成し
//メッシュの作成
m_SquareMesh = MeshResource::CreateMeshResource(vertices, indices, false);
 のように、MeshResource::CreateMeshResource関数に渡します。すると戻り値はメッシュ(のshared_ptr)になります。
 パラメータのfalseこの頂点は変更されるかどうかです。頂点バッファは動的に変更できます。しかし変更できる頂点は負荷がかかるので、作成時にどちらかに決めるという処理になっています。
 その後、SquareSprite::OnCreate関数では、Dx12リソースを順に初期化します。

■そのほかのリソースの初期化■

 このあと、ルートシグネチャ初期化、デスクプリタヒープ初期化、コンスタントバッファ初期化、パイプラインステート初期化、コマンドリスト初期化、コンスタントバッファの更新の順に呼ばれますが、これらは、SimpleSample002と同じです。詳細はSimpleSample002を参照ください。

■コンスタントバッファの更新■

 このサンプルではコンスタントバッファシェーダに渡す行列を更新するのに、前回のサンプルとは違う方法をとっています。
//コンスタントバッファ更新
void SquareSprite::UpdateConstantBuffer() {
    //行列の定義
    Mat4x4 World, Proj;
    //ワールド行列の決定
    World.affineTransformation2D(
        m_Scale,            //スケーリング
        Vec2(0, 0),     //回転の中心(重心)
        m_Rot,              //回転角度
        m_Pos               //位置
    );
    //射影行列の決定
    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;

    //中略

}
 このように、World行列をWorld.AffineTransformation2D関数を使ってスケール、回転、位置を反映した後、射影行列をかけてます。
 ここで反映させている射影行列
    //射影行列の決定
    float w = static_cast<float>(App::GetApp()->GetGameWidth());
    float h = static_cast<float>(App::GetApp()->GetGameHeight());
    Proj.OrthographicLH(w, h, -1.0, 1.0f);
    //行列の合成
    World *= Proj;
 は非常に重要です。このように射影行列を作ると、いわゆるピクセル座標系になります。頂点の作成時幅及び高さが1.0fの四角形を作成します。そしてスケーリングを128.0f, 128.0fで初期化しています。つまり128×128ピクセルで描画したいわけです。このようにしてスプライトの座標系をピクセル座標系にしておくと、最終的に上記のような射影行列を反映させたうえでシェーダに渡せば、ピクセル座標系での描画が可能になります。グラフィック担当者との画像作成時のサイズ等を決定するのに、このように描画すると決めておけば、スケーリングがピクセルになるので大変便利です。

■描画処理■

 描画処理SimpleSample002とほとんど同じです。違う部分は、頂点による描画ではなくインデックス描画を行うところです。その部分を以下に記します。
void SquareSprite::DrawObject() {
    //中略

    //描画方法のセット(三角形リスト)
    m_CommandList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    //頂点バッファのセット
    m_CommandList->IASetVertexBuffers(0, 1, &m_SquareMesh->GetVertexBufferView());
    //インデックスバッファをセット
    m_CommandList->IASetIndexBuffer(&m_SquareMesh->GetIndexBufferView());
    //インデックス描画
    m_CommandList->DrawIndexedInstanced(m_SquareMesh->GetNumIndicis(), 1, 0, 0, 0);

    //中略

}
 赤くなっているところがインデックス描画の部分です。頂点制作時にインデックスバッファもメッシュに入れているので、インデックスバッファビューを取り出すことができます。

 以上、Dx12側の説明は終わりです。

【まとめ】

 今回は、前回のSimpleSample002シェーダーや頂点型を変えずにインデックス描画をする方法をサンプル化しました。またスプライトの座標系についても少し触れました。スプライトの座標系は必ずしもこの方法がベストかどうかは議論も残るところですが、いずれにせよ一つのゲームでは一つの座標系で記述したほうが、グラフィッカーとのやり取りを考えてもよろしいかと思います。