【第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の処理待ち
ここで終了ではなくて、最後の最後に
と
同期オブジェクトおよび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]++;
}
初期化の終了
さてここまでで
三角形を描画する、すべての準備が整いました。
次項では
更新処理そして
描画処理を解説します。