BaseDx12(Dx12研究とフレームワーク)

【第1部】シンプルバージョン編

1108.シンプルな三角形の描画(8)

 前項に引き続きScene::OnInitAssets()関数です。
 今項ではメッシュを作成します。

BaseMeshクラス

 メッシュとは、画面に描画されるオブジェクトです。頂点をつなげたデータのかたまりで、多くは三角形を複数描画することにより、2Dあるいは3Dを表現します。
 メッシュの頂点はオブジェクトによってまちまちです。頂点のみの場合頂点とカラー情報のみの場合頂点と法線情報(ライティングに使います)の場合などなど、柔軟な構造が要求されます。
 このような柔軟な頂点構造のことを、FVF(フレキシブル頂点フォーマット)といいます。
 DirectXは伝統的にこのFVFに対応したエンジンとなっており、例えばBaseDx12のようにフレームワークを作成する場合、FVFへの対応は必須とも言えます。
 さてC++でこのような可変長のデータ構造に対応する手法としてテンプレート(template)という文法は、C++文法と求められる機能が一致する仕組みと考えられます。
 BaseDx12ではメッシュの表現方法として、テンプレートクラスである、BaseMeshクラスが実装されています。
 ここではBaseMeshクラスの基本的な使い方を説明しながらメッシュを作成したいと思います。
 Scene::OnInitAssets()における、メッシュ作成のブロックは以下です。
    // メッシュ
    {
        auto aspectRatio = baseDevice->GetAspectRatio();
        vector<VertexPositionColor> vertex =
        {
            { Float3(0.0f, 0.25f * aspectRatio, 0.0f), Float4(1.0f, 0.0f, 0.0f, 1.0f) },
            { Float3(0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 1.0f, 0.0f, 1.0f) },
            { Float3(-0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 0.0f, 1.0f, 1.0f) }
        };
        //メッシュ作成
        m_baseMesh = BaseMesh::CreateBaseMesh<VertexPositionColor>(vertex);
    }
 まず
        auto aspectRatio = baseDevice->GetAspectRatio();
 と画面のアスペクト比を取ってきて、頂点の位置の計算に使います。
 このメッシュはデバイス座標に直接描画されることを想定しています。
 デバイス座標とはGPUの座標系で、X軸方向に-1.0から1.0、Y軸方向に-1.0から1.0、Z軸方向に0から1.0の範囲を持ちます。
 3Dの描画をする場合、ワールド座標と呼ばれる空間から、カメラ位置を加味したうえで、最終的にデバイス座標系に変換して描画します。その変換処理を行うのが頂点シェーダとも言えます。
 このサンプルでは、頂点シェーダでは、その変換作業を行わないので、デバイス座標に合わせた形でメッシュを作成します。
        vector<VertexPositionColor> vertex =
        {
            { Float3(0.0f, 0.25f * aspectRatio, 0.0f), Float4(1.0f, 0.0f, 0.0f, 1.0f) },
            { Float3(0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 1.0f, 0.0f, 1.0f) },
            { Float3(-0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 0.0f, 1.0f, 1.0f) }
        };
 のVertexPositionColorは、位置情報と色情報を持った頂点です。
 ですので、各頂点は
            { Float3(0.0f, 0.25f * aspectRatio, 0.0f), Float4(1.0f, 0.0f, 0.0f, 1.0f) },
 のように指定します。3角形なので3つの頂点があります。
 これをSTLのvector(配列)として定義します。
 それでその配列をBaseMesh::CreateBaseMesh()テンプレート関数に渡せば、メッシュクラス(BaseMeshクラス)のインスタンスを返します。
 BaseMesh::CreateBaseMesh()テンプレート関数はこのほかにインデックスも渡すバージョンもあります。
 このサンプルではインデックス描画は行わないので頂点のみで作成するバージョンを使っています。
 m_baseMeshメンバ変数Sceneクラスパイプラインステートの変数と一緒に宣言されています。(前項を参照ください)

 Scene::OnInitAssets()関数はこれで終わり、呼び出し側のGameDevice::LoadAssets()関数に制御が戻ります。

コマンドリストクローズおよびキューの実行

 ここで、初期化処理はおおむね終了です。
 最後にコマンドリストクローズキューの実行を行います。具体的には以下の構文です。
        ThrowIfFailed(m_commandList->Close());
        ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
        m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
 まず、ThrowIfFailed(m_commandList->Close());をクローズします。
 そして
        ID3D12CommandList* ppCommandLists[] = { m_commandList.Get() };
 で、クローズ済みのコマンドリストの配列を作成します。ここでは、コマンドリストは1つなので、配列の要素数は1ですね。
 最後に
        m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
 で、それらを実行します。

同期オブジェクトおよびGPUの処理待ち

 ここで終了ではなくて、最後の最後に
        SyncAndWaitForGpu();
 と同期オブジェクトおよびGPUの処理待ちを行います。
 この関数はDirectX-Graphics-Samplesのものを移植したものです。内容的には
    void Dx12Device::SyncAndWaitForGpu() {
        ThrowIfFailed(GetID3D12Device()->CreateFence(
            m_fenceValues[m_frameIndex], D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence))
        );
        m_fenceValues[m_frameIndex]++;

        // Create an event handle to use for frame synchronization.
        m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
        if (m_fenceEvent == nullptr)
        {
            ThrowIfFailed(HRESULT_FROM_WIN32(GetLastError()));
        }
        //GPUの処理待ち
        WaitForGpu();
    }
 といったものです。ここは、DirectX-Graphics-Samplesそのままで問題ないでしょう。
 ここではさらにWaitForGpu()関数を呼び出しています。内容は以下です。
 ここもここは、DirectX-Graphics-Samplesそのままです。
    void Dx12Device::WaitForGpu()
    {
        // Schedule a Signal command in the queue.
        ThrowIfFailed(m_commandQueue->Signal(m_fence.Get(), m_fenceValues[m_frameIndex]));

        // Wait until the fence has been processed.
        ThrowIfFailed(
            m_fence->SetEventOnCompletion(m_fenceValues[m_frameIndex], m_fenceEvent)
        );
        WaitForSingleObjectEx(m_fenceEvent, INFINITE, FALSE);

        // Increment the fence value for the current frame.
        m_fenceValues[m_frameIndex]++;
    }

初期化の終了

 さてここまでで三角形を描画する、すべての準備が整いました。
 次項では更新処理そして描画処理を解説します。