001.三角形の描画(Dx11版)

 これから、シンプルバージョンのサンプルの説明を始めます。このサンプルはSimplSample001というディレクトリに含まれます。
 このドキュメントはDx11版のドキュメントです。Dx12版は目次より移行できます。BaseCrossDx11.slnというソリューションを開くとDx11版が起動します。
 実行結果は以下のような画面が出ます。単純な3角形の描画です。

 

図0001a

 


Dx11版解説

 BaseCrossDx11.slnを開くと、BaseCrossDx11というメインプロジェクトがあります。
 まずBaseCrossの根っこにある構造について説明します。

 BaseCrossにはGameStageGameObjectをそなえたフルバージョンがあります。ここには、プログラマの負担をできるだけ減らせるよう(とはいってもGUIがそろっているわけではありませんが)、オブジェクトの大きさや位置、あるいは衝突判定、あるいは物理計算などがコンポーネントという形で用意されてます。
 また、描画処理も描画コンポーネントを差し替えることで、比較的簡単に描画処理を変更したりできます。
 エフェクトもパーティクルエフェクトの基本クラスは用意されています。
 これらのフルバージョンの機能はシンプルバージョンという薄い座布団の上に乗っかている設計になってます。ですからフルバージョンであっても自分で描画処理を記述したり、あるいは自作の衝突判定を実装したりすることは可能です。
 しかし、フルバージョンの用意された機能に慣れてしまうと、ゲーム制作の根っこにある技術を知らずにゲームを作る癖がついてしまいます。結果として、なかなかゲーム制作会社が求める本当のゲームプログラムの技術を身につけるチャンスを逸してしまいます。そういう意味ではフルバージョン諸刃の剣と言えます。
 ですから、ゲーム制作に慣れるためにはフルバージョンもいいのですが、ある程度何本か作成した後は、シンプルバージョンによるゲーム制作を行うことによって本当のゲームプログラムの技術を身に着けられます。
 ぜひ挑戦することをお勧めします。

ゲームプログラミングで行わなければならないこと

 まずゲームプログラミングで行わなければならないことを以下に列挙します。
1、ゲームエンジンの初期化と描画バッファの初期化
2、サウンドや入力インターフェイスの初期化
3、タイマーによるターンの設定
4、リソースの構築
5、更新処理
6、描画処理
7、終了処理
 非常にざっくりとしますが、基本的に上記の内容を兼ね備える必要があります。
 まず、1、ゲームエンジンの初期化と描画バッファの初期化ですが、BaseCrossDx11版の場合はDirectX11の初期化にあたります。デバイスおよびデバイスコンテキストを初期化しゲーム側で描画できるように準備をします。これはBaseCrossではDeviceResourcesクラスが担当します。
 2、サウンドや入力インターフェイスの初期化BaseCrossDx11版の場合はAudioManagerクラスAudioResourceクラス、そしてMultiAudioObjectクラスで再生します。入力インターフェイスはキーボードやマウス、InputDeviceクラスで管理します。
 3、タイマーによるターンの設定は、BaseCrossDx11版の場合はStepTimerクラスが行います。
  このほかに、例外処理や、バイナリファイルの読み込み、CSVやXMLに関する処理もユーティリティとして用意されています。  4、リソースの構築は実際に描画されるメッシュであるメッシュリソーステクスチャリソースあるいはサウンドリソースの構築です。MeshResourceクラスTextureResourceクラス、そしてAudioResourceクラスもリソースです。
 また頂点定義を記した、VertexHelper.hもい用意されています。

 以上はシンプルバージョンであってもBaseCross側でクラスや関数で用意されてます。ですからフルバージョンのように簡単ではありませんが、多くの場合Appクラスにまとめられ、そのインスタンスを介して、いつでもアクセスできるようになってます。
 5、更新処理は準備したリソースを移動したり、あるいは回転させるなど処理です。フルバージョンでは更新用のコンポーネントとしてTransformRigidbody、そしてCollisionがあります。しかし、シンプルバージョンにはこれらは用意されてません。
 フルバージョンのようにコンポーネントの形では用意されてませんが、コンポーネントが使用する衝突判定クラスなどはTransHelper.hに用意されています。また、基本データ型であるVector2、Vector3、Vector4、Matrix4X4、Quaternionなどは実装されています。
 6、描画処理描画の仕組みは用意されてますが、実際の描画処理はゲーム側で行います。描画の方法についてもシェーダを自作しなければなりません。
 7、終了処理BaseCrossで用意されてます。Escキーまたはウインドウの閉じるで、終了します。

シンプルバージョンで行わなければならないこと

 では、上記の列挙に対してゲームプログラマが実装しないことを挙げながら、今回のサンプルを解説します、

配置オブジェクトクラスの定義

 表示されている三角形はTriangleObjectクラスです。ObjectInterfaceおよびShapeInterfaceの多重継承オブジェクトです。
 Character.h/cppが記述個所になります。
 以下は、TriangleObject::OnCreate()関数です。
void TriangleObject::OnCreate() {
    //頂点を作成するための配列
    vector<VertexPositionColor> vertices = {
        { Vec3(0.0f, 0.5f , 0.0f),Col4(1.0f, 0.0f, 0.0f, 1.0f) },
        { Vec3(0.5f, -0.5f, 0.5f),Col4(0.0f, 1.0f, 0.0f, 1.0f) },
        { Vec3(-0.5f, -0.5f, 0.5f),Col4(0.0f, 0.0f, 1.0f, 1.0f) }
    };
    m_TriangleMesh = MeshResource::CreateMeshResource(vertices, false);
}
 ObjectInterfaceおよびShapeInterfaceの継承クラスはOnCreate()仮想関数を記述する必要があります。この仮想関数はObjectFactory::Create()テンプレート関数を呼び出した際に、呼び出されます。
 ここでは上記のようにメッシュを作成しています。

 シンプルバージョンにはGameStageがありません。しかしSceneはあります。ステージ、あるいはそれに準ずる仕組みをどのように設計するかは各自考えます。
 ObjectInterfaceおよびShapeInterfaceの継承クラスとして作ったTriangleObjectクラスOnUpdate()、OnDraw()といった仮想関数を持ってます。しかし、フルバージョンのように自動的に呼び出されるわけではありません。
 一般的にはScene::OnUpdate()各オブジェクトのOnUpdate()を呼び出し、Scene::OnDraw()各オブジェクトのOnDraw()を呼び出します。
 注意したいのはScene::OnDraw()で、ビューの準備をしているところです
void Scene::OnDraw() {
    //描画デバイスの取得
    auto Dev = App::GetApp()->GetDeviceResources();
    Dev->ClearDefaultViews(Col4(0, 0, 0, 1.0f));
    //デフォルト描画の開始
    Dev->StartDefaultDraw();
    m_TriangleObject->OnDraw();
    //デフォルト描画の終了
    Dev->EndDefaultDraw();
}
 このように。デフォルト描画の開始デフォルト描画の終了で各オブジェクトの描画を挟みます。ここでは影の描画もエフェクトの描画もスプライトの描画も全くしてません。これらの特殊な配置オブジェクトの描画も各自行う必要があります。

 実際の3角形の描画は以下になります。
void TriangleObject::OnDraw() {
    //描画
    //デバイスの取得
    auto Dev = App::GetApp()->GetDeviceResources();
    auto pDx11Device = Dev->GetD3DDevice();
    auto pID3D11DeviceContext = Dev->GetD3DDeviceContext();
    //ステータスのポインタ
    auto RenderStatePtr = Dev->GetRenderState();

    //ストライドとオフセット
    UINT stride = sizeof(VertexPositionColor);
    UINT offset = 0;
    //頂点バッファの設定
    pID3D11DeviceContext->IASetVertexBuffers(0, 1, 
        m_TriangleMesh->GetVertexBuffer().GetAddressOf(), &stride, &offset);
    //描画方法(3角形)
    pID3D11DeviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
    //nullテクスチャを設定
    ID3D11ShaderResourceView* pNull[1] = { 0 };
    ID3D11SamplerState* pNullSR[1] = { 0 };
    pID3D11DeviceContext->PSSetShaderResources(0, 1, pNull);
    //nullサンプラーを設定
    pID3D11DeviceContext->PSSetSamplers(0, 1, pNullSR);
    pID3D11DeviceContext->OMSetDepthStencilState(RenderStatePtr->GetDepthNone(), 0);
    //シェーダの設定
    //頂点シェーダの設定
    pID3D11DeviceContext->VSSetShader(VSPCDirect::GetPtr()->GetShader(), nullptr, 0);
    //ピクセルシェーダの設定
    pID3D11DeviceContext->PSSetShader(PSPCDirect::GetPtr()->GetShader(), nullptr, 0);
    //インプットレイアウトの設定
    pID3D11DeviceContext->IASetInputLayout(VSPCDirect::GetPtr()->GetInputLayout());

    //塗りつぶし
    pID3D11DeviceContext->OMSetBlendState(RenderStatePtr->GetOpaque(), nullptr, 0xffffffff);
    //レンダリングステート
    pID3D11DeviceContext->RSSetState(RenderStatePtr->GetCullBack());
    //描画
    pID3D11DeviceContext->Draw(3, 0);
    //後始末
    Dev->InitializeStates();

}
 記述内容は多いですが、一つ一つが理にかなっている(というか読めばわかる)操作かと思います。
 個別の解説はコメントでわかると思います。

 さてこうしたうえで、シーンでのこのオブジェクトの準備をします。
void Scene::OnCreate() {
    //立方体の作成
    m_TriangleObject = ObjectFactory::Create<TriangleObject>();
}
 このようにm_TriangleObjectに作成したオブジェクトを代入します。使いまわしするメッシュなどであればリソース化する方法もあります。

シェーダの記述

 シンプルバージョンではシェーダも記述しなければなりません。DxSharedにあるVSPCDirect.hlslPSPCDirect.hlslがシェーダです。INCStructs.hlsliはシェーダのインクルードファイルになります。
 シェーダはコンパイルして使用します。ビルドの設定でそれらのシェーダのビルド方法を指定しておきます。コンパイルされたシェーダはmedia/Shadersディレクトリに保存されます。
 シェーダの使用はProjectShader.h
DECLARE_DX11_VERTEX_SHADER(VSPCDirect, VertexPositionColor)
DECLARE_DX11_PIXEL_SHADER(PSPCDirect)
 そしてProjectShader.cpp
//PCDirect
IMPLEMENT_DX11_VERTEX_SHADER(VSPCDirect, App::GetApp()->GetShadersPath() + L"VSPCDirect.cso")
IMPLEMENT_DX11_PIXEL_SHADER(PSPCDirect, App::GetApp()->GetShadersPath() + L"PSPCDirect.cso")
 と記述することで、OnDraw()関数でそのシェーダを使用できるようになります。以下はOnDraw()関数内でのシェーダ関連の記述です。
    //シェーダの設定
    //頂点シェーダの設定
    pID3D11DeviceContext->VSSetShader(VSPCDirect::GetPtr()->GetShader(), nullptr, 0);
    //ピクセルシェーダの設定
    pID3D11DeviceContext->PSSetShader(PSPCDirect::GetPtr()->GetShader(), nullptr, 0);
    //インプットレイアウトの設定
    pID3D11DeviceContext->IASetInputLayout(VSPCDirect::GetPtr()->GetInputLayout());
 これで、作成したシェーダでの描画ができます。

まとめ

 この項ではシンプルバージョンにおける、制作の流れを説明しました。基本的にこの手順は変わりません。もっと複雑なオブジェクトを作成して描画するためには、これらのオブジェクトを自作し、シーンに配置する必要があります。
 また、シンプルバージョンのサンプルは、どうしても個別の紹介になります。この後スプライト3D表現のサンプルも出てきますが、フルバージョンのように、組み合わさった形でのサンプルはあまり用意しませんので、参考にする場合は、個別に参考にして自分のゲームに各自組み込んでください。

 以降、こんな感じでサンプルの解説を行っていきたいと思います。