001.三角形の描画(Dx11版)
 これから、
シンプルバージョンのサンプルの説明を始めます。このサンプルは
SimplSample001というディレクトリに含まれます。
 このドキュメントは
Dx11版のドキュメントです。
Dx12版は目次より移行できます。
BaseCrossDx11.slnというソリューションを開くと
Dx11版が起動します。
 実行結果は以下のような画面が出ます。単純な3角形の描画です。
 
 
図0001a
 
Dx11版解説
 BaseCrossDx11.slnを開くと、
BaseCrossDx11というメインプロジェクトがあります。
 まず
BaseCrossの根っこにある構造について説明します。
 
BaseCrossには
GameStageや
GameObjectをそなえた
フルバージョンがあります。ここには、プログラマの負担をできるだけ減らせるよう(とはいっても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、更新処理は準備したリソースを移動したり、あるいは回転させるなど処理です。
フルバージョンでは
更新用のコンポーネントとして
Transformや
Rigidbody、そして
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.hlslと
PSPCDirect.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表現のサンプルも出てきますが、フルバージョンのように、組み合わさった形でのサンプルはあまり用意しませんので、参考にする場合は、個別に参考にして自分のゲームに各自組み込んでください。
 以降、こんな感じでサンプルの解説を行っていきたいと思います。