【第1部】シンプルバージョン編
1101.シンプルな三角形の描画(1)
サンプルの実行
BaseDx12には
シンプルバージョンと
フルバージョンという2つのバージョンがあります。
前者は
Dx12の機能を実装するための
サポート的な内容となります。ですので
物理エンジンや
補間処理などの機能は持ってません。(簡単な
衝突判定は持っています)。
後者は
Dx12の知識はなくても
C++の知識があれば、コンテンツをある程度実装できると思います。こちらには物理エンジン、補間処理、ユーティリティ的な行動などが実装されます。また、
モデルの描画や
モデルのアニメーションなども、比較的簡単に実装できます。
【第1部】シンプルバージョン編では、前者の解説です。フルバージョンを試してみたい方は
【第2部】フルバージョン編を参照してください。
SimpleSample101というディレクトリ内の
BaseDx12.slnを
VS2019で開いてください。
リビルドして実行すると、以下の画面が出てきます。
図1101a
シンプルバージョンについて
このサンプルの説明を始める前に、
BaseDx12フレームワークの
シンプルバージョンについて少し説明します。
もしよければ、本家、マイクロソフト社のサンプル、
DirectX-Graphics-Samplesをダウンロードし(GitHub公開されています。検索ですぐに見つかります)、その中の
D3D12HelloWorld内の
HelloTriangleと比較しながら見ていただくと良いと思います。
Dx12でGPUにレンダリングするには、様々な設定が必要です。
主なものを以下にあげます。
*Dx12デバイス
*コマンドキュー
*スワップチェーン
*デスクプリタヒープ
*レンダーターゲットビュー
*コマンドアロケーター
*ルートシグネチャー
*コマンドリスト
*パイプラインステート
*シェーダ(最低限、頂点とピクセル)
*メッシュ
*テクスチャ(必要に応じて)とシェーダリソースビュー
*サンプラー(必要に応じて)
これらのオブジェクト(リソース)を、初期化し、レンダリング時にそれぞれを組み合わせながら、GPUのパイプラインに描画命令(コマンド)を発行します。
HelloTriangleのソースを見るとわかりますが、たった一つの三角形を描画するだけなのに、実に多くの準備や命令が必要なのがわかります。
BaseDx12シンプルバージョンでは、プログラマの自由度を最大限守りながら、どうしたら効率よく、リソースの作成やコマンド発行できるか、を考えて作成したつもりです。
ライブラリを見る
まず最初に
ソリューション内にある
Libsというフィルタを見てください。
閉じている場合はクリックすると、以下の様な内容になっています。
図1101b
シンプルとはいえ、結構な量のファイル構成になっています。
それぞれ、意味があって記述しているので、皆重要は重要なのですが、リソース構築時は
BaseDevice.h/cppと
BasePipeline.h/cppが重要な役割をはたします。
ウインドウの作成とメッセージループ
まず、ライブラリ側ではなく
BaseDx12プロジェクト内にある、
WinMain.cppを見てください。
ここでは、ウインドウを作成して、メッセージループを起動します。その際、
シーンというオブジェクトを作成します。その記述が、
MainLoop()関数内にあります。以下が実体です。
Dx12にかかわる部分は、赤くなっています。
int MainLoop(HINSTANCE hInstance, HWND hWnd, int nCmdShow, int iClientWidth, int iClientHeight) {
//終了コード
int retCode = 0;
//ウインドウ情報。メッセージボックス表示チェックに使用
WINDOWINFO winInfo;
ZeroMemory(&winInfo, sizeof(winInfo));
//例外処理開始
try {
//COMの初期化
//サウンドなどで使用する
if (FAILED(::CoInitialize(nullptr))) {
// 初期化失敗
throw exception("Com初期化に失敗しました。");
}
basedx12::Scene scene;
basedx12::App::Init(hWnd, &scene, hInstance, nCmdShow, iClientWidth, iClientHeight);
//メッセージループ
MSG msg = { 0 };
while (WM_QUIT != msg.message) {
//キー状態が何もなければウインドウメッセージを得る
if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
//更新描画処理
basedx12::App::UpdateDraw();
}
//msg.wParamには終了コードが入っている
basedx12::App::Destroy();
retCode = (int)msg.wParam;
}
catch (exception & e) {
//STLエラー
//マルチバイトバージョンのメッセージボックスを呼ぶ
if (GetWindowInfo(hWnd, &winInfo)) {
MessageBoxA(hWnd, e.what(), "エラー", MB_OK);
}
else {
MessageBoxA(nullptr, e.what(), "エラー", MB_OK);
}
retCode = 1;
}
catch (...) {
//原因不明失敗した
if (GetWindowInfo(hWnd, &winInfo)) {
MessageBox(hWnd, L"原因不明のエラーです", L"エラー", MB_OK);
}
else {
MessageBox(nullptr, L"原因不明のエラーです", L"エラー", MB_OK);
}
retCode = 1;
}
//例外処理終了
//COMのリリース
::CoUninitialize();
return retCode;
}
アプリケーションクラス
ここで、構築部を見てみましょう
basedx12::Scene scene;
basedx12::App::Init(hWnd, &scene, hInstance, nCmdShow, iClientWidth, iClientHeight);
という記述は
Sceneクラスの
sceneというインスタンスを定義しています。
basedx12というのは
BaseDx12全体のネームスペースです。ライブラリ及びコンテンツは、すべてこのネームスペースに含まれます。
しかし
WinMain.cpp内の関数は
グローバル領域に置かれますので、
basedx12::Scene scene;という記述が必要になります。
sceneインスタンスを定義したら、そのポインタを渡して
basedx12::App::Init()関数を呼び出します。
Appクラスはいわゆる
アプリケーションクラスなのですが、
BaseDx12では、クラスの実体は作らずに
App::Init()のように、
すべてスタティック呼び出しの関数が含まれます。そういう意味では
クラスではなく
ネームスペースでもよいのですが、ネームスペースですとヘッダファイルへの記述が、ちょっとややこしくなるので、
クラスにしてあります。
basedx12::App::Init()関数内では
BaseDx12プロジェクト内の
Scene.h/cppに記述がある、
Scene::OnInit()関数を呼び出します。
今後、プロジェクト側を
コンテンツ側と称します。同様、フレームワーク側は
ライブラリ側と称します。
Sceneクラス
Sceneクラスは、以下のような宣言になっています。
class Scene :public SceneBase {
ComPtr<ID3D12PipelineState> m_pipelineState;
shared_ptr<BaseMesh> m_baseMesh;
public:
Scene() :SceneBase() {}
virtual ~Scene() {}
virtual void OnInit()override;
virtual void OnInitAssets()override;
virtual void OnUpdate()override;
virtual void OnDraw()override;
virtual void OnDestroy()override;
};
このように
SceneBaseクラスを継承して作成されています。
SceneBaseクラスは、ライブラリ部の
SceneBase.h/cppに記述があります。
ゲームシーンを表現すると思ってもらえればよいです。
この中で
virtual void OnInit()override;
という仮想関数があります。
この関数が
basedx12::App::Init()関数から呼び出されます。
Scene::OnInit()関数の記述は以下になります。
void Scene::OnInit() {
ResetActiveBaseDevice<GameDevice>(2);
}
このように
GameDeviceクラスをテンプレート引数に持った、
テンプレート関数呼び出しとなります。
この関数は、指定のクラス(ここではGameDeviceクラス)のインスタンスを作成して
Appクラスに登録します。こうすることにより
Appクラス経由で
GameDeviceクラスのインスタンスにアクセスできる形になります。
ResetActiveBaseDevice()関数では、そのあと
GameDevice::OnInit()関数を呼び出します。
この関数こそが、
Dx12のリソース群を構築すべき関数となります。
GameDeviceクラスは
コンテンツ部にある
GameDevice.h/cppです。次項では、その中身を詳しく見てみましょう。