Loading...

【DirextX12】第一回 DirextX12を使ったゲーム開発 〜基礎知識からポリゴン描画まで〜

C++
マイケル
マイケル
みなさんこんにちは!
マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜〜
マイケル
マイケル
突然ですが、今回から何回かに分けて
DirectX12 を触っていこうと思います!
エレキベア
エレキベア
ついに触る時が来たクマか・・・
マイケル
マイケル
というのも、描画周りの学習を進めていくにあたり
やはり描画パイプライン周りの知識は必要だな、と感じたためです。
エレキベア
エレキベア
前OpenGLで簡単なゲームも作ってたクマが、
拡張ライブラリやSDLも使用してたクマからね〜〜
マイケル
マイケル
あとはやっぱり一度は触ってみたかったからね!!
今回は下記のように画面の塗りつぶしと四角形のポリゴン表示を行うサンプルを作っていこうと思います!
20221119 10↑黄色で画面を塗りつぶして青色のポリゴンを表示している
エレキベア
エレキベア
こんなの余裕クマ〜〜〜
マイケル
マイケル
それでもこれだけで300行以上のコードを書くことになるんだよね・・・
しかし一つ一つ見ていけば絶対に理解できるので、諦めずにやっていきましょう!
エレキベア
エレキベア
これだけでそんなに書くクマか・・・
マイケル
マイケル
今回作ったサンプルについては下記GitHubのリポジトリに上げていますので
こちらもご参照ください!

GitHub – masarito617/cpp-dx12-sample-polygon

↑今回作ったサンプル

マイケル
マイケル
下記のように公式のサンプルやチュートリアルもあるのですが、正直知識0からこれを理解していくのはかなり苦難なのではないかと思いました・・・。

Durect3D 12 の基本的なコンポーネントの作成

実用的なサンプル – Win32 apps | Microsoft Learn

GitHub – microsoft / DirectX-Graphics-Samples

↑公式のサンプル、チュートリアル

エレキベア
エレキベア
ワケが分からないクマ〜〜〜
マイケル
マイケル
そのため今回は下記の参考書も参考にさせてもらいつつ、なるべくチュートリアルのコードと近くなるよう整理して作りました。
この記事と合わせて見ることで、公式のサンプルコードを理解する手助けになればと思います!

DirectX 12の魔導書 3Dレンダリングの基礎からMMDモデルを踊らせるまで

エレキベア
エレキベア
DirectX12の書籍があまりないから本当にありがたいクマね
マイケル
マイケル
それでは早速やっていこう!!
スポンサーリンク

DirectX12の基礎知識

マイケル
マイケル
ということでコードを書いていきたい・・・ところですが、
その前に前提知識としてDirectX12の基礎知識について知っておきましょう!
12に至るまでの簡単な歴史とパイプライン知っておくべき用語について説明します!
エレキベア
エレキベア
楽しみクマ〜〜〜

DirextXの歴史とパイプライン

マイケル
マイケル
まず、DirectXが12に至るまでの歴史についてはざっくりと下記のようになっています。
DirectX9から本格的にHLSL言語でシェーダプログラミングが出来るようになり、そこからシェーダステージの追加やパイプラインの変更が行われていることが分かります。

年代バージョン概要
1997年〜DirectX5Windows95の時代になってから普及し、PCで3Dゲームすることを広めた。
1998年〜DirectX6ラスタライズ処理やテクスチャ貼りつけ等のピクセル単位の処理のみ担当した。
1999年〜DirectX7頂点単位の座標変換、光源処理を担当できるようになった。
2000年〜DirectX8シェーダをプログラムできるプログラマブルシェーダをサポートした。(SM1.x
2002年〜DirectX9SM2.0、SM3.0と対応され、HLSL言語にも対応するようになる。
2006年〜DirectX10SM4.0に対応し、ジオメトリシェーダを含めたパイプラインに変更された。
2009年〜DirectX11SM5.0に対応し、新たに4つのシェーダステージが追加された。
(DirectX9、10と互換性有り)
2015年〜DirectX12SM5.1に対応し、Pipeline State Object (PSO)というパイプラインの仕組みに変更された。(DirectX11と互換性無し)

参考:

ゲーム制作者になるための3Dグラフィックス技術 改訂3版

エレキベア
エレキベア
DirectX12になるまでもいろんな歴史があったクマね
マイケル
マイケル
DirectX9〜11までのパイプラインの変更内容についてはざっくりと下記のようになっています。
DirectX9
UntitledImage
DirectX10
UntitledImage
DirectX11
UntitledImage
マイケル
マイケル
「頂点シェーダ」「ピクセルシェーダ」だけだったものが、10で「ジオメトリシェーダ」が追加されています。
また、11ではテッセレーションステージと呼ばれる「ハルシェーダ」「テッセレータ」「ドメインシェーダ」の3つのシェーダステージに加え、図にはありませんが「演算シェーダ(Compute Shader)」と呼ばれる汎用目的でGPUを使用するためのシェーダも追加されています。
エレキベア
エレキベア
こんなに種類があるのクマか・・・
マイケル
マイケル
バージョンアップに伴い、パイプラインもかなり複雑になっているね・・・
各シェーダの役割は下記のようになっています!
シェーダ名概要
頂点シェーダ頂点単位の陰影処理を行う。
ハルシェーダポリゴンの分割レベル、分割手法を決定する。
テッセレータ渡されたポリゴン分割計画によって分割を行う。(固定機能)
ドメインシェーダ仮想的に分割された各頂点を変異させる。
ジオメトリシェーダ頂点の増減を行う。
ピクセルシェーダピクセル単位の陰影処理を行う。
演算シェーダGPUで汎用計算を行う。
マイケル
マイケル
とはいえ今回は基本的な「頂点シェーダ」「ピクセルシェーダ」しか使わないので、概要だけ覚えておきましょう!

用語とイメージ

マイケル
マイケル
パイプラインの流れについてはざっくりとイメージが伝わったかと思いますが、DirectX12ではPipeline State Object (PSO)と呼ばれる仕組みが導入され、効率化のためにある程度まとめてGPUへ渡す方向になっています。
ScreenShot 2022 11 20 2 11 29↑PSOという単位でまとめてGPUへ渡すことで処理を効率化している
マイケル
マイケル
それに併せて様々な概念が導入され11より更に複雑になり、専門用語も増えています。
ポリゴン表示までに必要な用語を並べると下記のようになります。
用語概要
コマンドリストGPUへの命令を受け取る。
コマンドアロケータコマンドリストで受け取った命令を溜め込む。
コマンドキューコマンドアロケータに溜め込んだ命令を先頭から実行する。
ディスクリプタGPUに送るリソースの仕様について示したもの。
ディスクリプタヒープディスクリプタをまとめたもの。
ディスクリプタテーブルルートシグネチャ内で設定し、ディスクリプタヒープとシェーダーのレジスタを関連付けたもの。
ルートシグネチャディスクリプタテーブルをまとめたもの。
パイプラインステートオブジェクト表示までに必要なパイプラインのパラメータを1つのオブジェクトにまとめたもの。
フェンスGPUの処理が終わったかどうかを監視するためのもの。
バリアコマンド実行時にリソースが何の用途に使用されるのかをGPUに伝えるもの。
エレキベア
エレキベア
うおおうぅ・・・
ポリゴン表示するだけでこんなにクマか・・・
マイケル
マイケル
専門用語が多すぎておおぅ・・・となってしまいますが、これらも大方「まとめる」という思想が元になっていると考えられます。
例えば、下記のようなイメージです。

・GPUへの命令はコマンドリストを通してコマンドアロケータに格納し、コマンドキューから取り出して実行する

ScreenShot 2022 11 20 2 11 07

・ディスクリプタ(ビューやテクスチャ等の情報)はディスクリプタヒープにまとめてGPUへ渡す

ScreenShot 2022 11 20 2 11 19
エレキベア
エレキベア
なるほどクマ
用語はややこしいクマが、やっていることはそこまで難しくはないクマね
マイケル
マイケル
とりあえず最低限必要な知識は以上になります!
ここからは実際にコードを書いてイメージを掴んでいきましょう!!

Win32APIでのウィンドウ表示

マイケル
マイケル
それではサンプルを作っていきますが、改めてGitHubのコードは下記になります。
今回はVisualStudio2022を使用しました。

GitHub – masarito617/cpp-dx12-sample-polygon

↑今回作ったサンプル

マイケル
マイケル
まずはDirectXを触る前に、土台となるウィンドウを作成します。
こちらはWindowsAPI(Win32API)を使用して実装していますが、今回は簡単な説明に留めるので詳細は公式ドキュメントをご参照ください!

Windows API インデックス | Win32 apps | Microsoft Learn

エレキベア
エレキベア
ウィンドウ表示自体はDirectXは使用しないクマね

空のプロジェクト作成

マイケル
マイケル
まずは空のプロジェクトを作成します。
今回は cpp-dx12-sample-polygon という名前にしました。
20221119 01↑空のプロジェクトを選択
20221119 02↑プロジェクト名を入力
エレキベア
エレキベア
ここから始まるクマね

ウィンドウを表示する

マイケル
マイケル
ここからコードを書いていきますが、その前にWindowsAPIを使用するため
リンカー > システム > サブシステム をWindowsに指定しておきます。
20221119 03↑サブシステムをWindowsに指定する
マイケル
マイケル
そして
・Win32Application.h
・Win32Application.cpp

を作成して下記のように記述します。
#pragma once
#include <windows.h>
#include <tchar.h>

class Win32Application
{
public:
	static void Run(HINSTANCE hInstance);

private:
	static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
};
#include "Win32Application.h"

void Win32Application::Run(HINSTANCE hInstance)
{
	// ウィンドウクラス生成
	WNDCLASSEX windowClass = {};
	windowClass.cbSize = sizeof(WNDCLASSEX);
	windowClass.style = CS_HREDRAW | CS_VREDRAW;
	windowClass.lpfnWndProc = WindowProc;
	windowClass.hInstance = hInstance;
	windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	windowClass.lpszClassName = _T("DXSampleClass");
	RegisterClassEx(&windowClass);

	// ウィンドウサイズの調整
	RECT windowRect = { 0, 0, 1280, 720 };
	AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, false);

	// ウィンドウオブジェクトの生成
	HWND hwnd = CreateWindow(
		windowClass.lpszClassName,
		L"DX Sample",
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		windowRect.right - windowRect.left,
		windowRect.bottom - windowRect.top,
		nullptr,
		nullptr,
		hInstance,
		nullptr
	);

	// ウィンドウ表示
	ShowWindow(hwnd, SW_SHOW);

	// メインループ
	MSG msg = {};
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	// クラスを登録解除する
	UnregisterClass(windowClass.lpszClassName, windowClass.hInstance);
}

LRESULT CALLBACK Win32Application::WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
	switch (message)
	{
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hwnd, message, wparam, lparam);
	}
	return 0;
}
マイケル
マイケル
これはウィンドウ表示の処理になりますが、お決まりのような実装になるので覚えてしまいましょう!
あとはmain.cppを作成して下記のように呼び出せばウィンドウ表示は完了です!
#include "Win32Application.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
	Win32Application::Run(hInstance);
	return 0;
}
20221119 04↑空のウィンドウが表示される
エレキベア
エレキベア
ここまでは余裕クマね

画面を特定の色で塗りつぶす

マイケル
マイケル
ウィンドウの準備が出来たところで、DirectXを使っていきましょう!
ポリゴン表示をする・・・前に、まずは画面を特定の色で塗りつぶすというところまで実装していきます。
エレキベア
エレキベア
それくらいなら余裕クマよ
マイケル
マイケル
果たしてそんな簡単にいくかな・・・?
じっくり見ていきましょう!

d3dx12.hの導入

マイケル
マイケル
コードを書く前に今回はDirectXでよく使用する処理をヘルパー関数として整理してある d3dx12.h を導入します。
こちらはGitHubに公開されているのでダウンロードしてプロジェクト配下に格納しておきましょう。
この後、インクルードして使用していきます。

Direct3D 12 のヘルパー構造と関数 – Win32 apps | Microsoft Learn

GitHub – microsoft/DirectX-Headers/blob/main/include/directx/d3dx12.h

マイケル
マイケル
ヘルパー関数自体は、ほぼコード量を減らすために簡単にまとめただけのものとなっています。
内部でどのようなことを行っているか?も一緒に見ながら進めると理解しやすくなると思います。
エレキベア
エレキベア
これを導入しても量はかなり多いクマけどね・・・

大枠を実装

マイケル
マイケル
それではここから大枠を実装していきます。
・DXApplication.h
・DXApplication.cpp

として作成し、下記のように記述しましょう。
#pragma once

class DXApplication
{
public:
	DXApplication(unsigned int width, unsigned int height, std::wstring title);
	void OnInit(HWND hwnd);
	void OnUpdate();
	void OnRender();
	void OnDestroy();

	const WCHAR* GetTitle() const { return title_.c_str(); }
	unsigned int GetWindowWidth() const { return windowWidth_; }
	unsigned int GetWindowHeight() const { return windowHeight_; }

private:
	std::wstring title_;
	unsigned int windowWidth_;
	unsigned int windowHeight_;
};
#include "DXApplication.h"

DXApplication::DXApplication(unsigned int width, unsigned int height, std::wstring title)
	: title_(title)
	, windowWidth_(width)
	, windowHeight_(height)
{
}

// 初期化処理
void DXApplication::OnInit(HWND hwnd)
{
}

// 更新処理
void DXApplication::OnUpdate()
{
}

// 描画処理
void DXApplication::OnRender()
{
}

// 終了処理
void DXApplication::OnDestroy()
{
}
マイケル
マイケル
タイトル、ウィンドウサイズといった情報を受け取った後、

・初期化処理 (OnInit)
・更新処理 (OnUpdate)
・描画処理 (OnRender)
・終了処理 (OnDestroy)
といったゲームループの形で用意しています。
マイケル
マイケル
このクラスをWin32Applicationクラスからも参照、呼び出しするよう修正します。
#pragma once
#include <windows.h>
#include <tchar.h>
#include "DXApplication.h" // ★インクルードを追加

class Win32Application
{
public:
	static void Run(DXApplication* dxApp, HINSTANCE hInstance); // ★引数に追加

private:
	static LRESULT CALLBACK WindowProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam);
};
#include "Win32Application.h"

void Win32Application::Run(DXApplication* dxApp, HINSTANCE hInstance) // ★引数に追加
{
	// ウィンドウクラス生成
	WNDCLASSEX windowClass = {};
	windowClass.cbSize = sizeof(WNDCLASSEX);
	windowClass.style = CS_HREDRAW | CS_VREDRAW;
	windowClass.lpfnWndProc = WindowProc;
	windowClass.hInstance = hInstance;
	windowClass.hCursor = LoadCursor(NULL, IDC_ARROW);
	windowClass.lpszClassName = _T("DXSampleClass");
	RegisterClassEx(&windowClass);

	// ウィンドウサイズの調整
	RECT windowRect = { 0, 0, dxApp->GetWindowWidth(), dxApp->GetWindowHeight() }; // ★ウィンドウサイズを受け取る
	AdjustWindowRect(&windowRect, WS_OVERLAPPEDWINDOW, false);

	// ウィンドウオブジェクトの生成
	HWND hwnd = CreateWindow(
		windowClass.lpszClassName,
		dxApp->GetTitle(), // ★タイトルを受け取る
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		windowRect.right - windowRect.left,
		windowRect.bottom - windowRect.top,
		nullptr,
		nullptr,
		hInstance,
		nullptr
	);

	// アプリケーション初期化
	dxApp->OnInit(hwnd); // ★呼び出しを追加

	// ウィンドウ表示
	ShowWindow(hwnd, SW_SHOW);

	// メインループ
	MSG msg = {};
	while (msg.message != WM_QUIT)
	{
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}

		// アプリケーション更新
		dxApp->OnUpdate(); // ★呼び出しを追加
		dxApp->OnRender(); // ★呼び出しを追加
	}

	// アプリケーション終了
	dxApp->OnDestroy(); // ★呼び出しを追加

	// クラスを登録解除する
	UnregisterClass(windowClass.lpszClassName, windowClass.hInstance);
}
マイケル
マイケル
ウィンドウ処理のタイミングに合わせてゲームループの関数を呼び出していることが分かります。
あとはmain.cpp内で初期化して渡せば、大枠は完成です!
#include "Win32Application.h"
#include "DXApplication.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
	Win32Application::Run(hInstance);
	DXApplication dxApp(1280, 720, L"DX Sample");
	Win32Application::Run(&dxApp, hInstance);
	return 0;
}
マイケル
マイケル
この状態で実行してエラーが発生しないか確認してみましょう。
エレキベア
エレキベア
あとはDirectXの処理を実装していくのみクマね

DirextX12オブジェクトの定義

マイケル
マイケル
ここからDirectX12に関連するオブジェクトを初期化していきます。
まずはヘッダーファイルを下記のように修正しましょう。
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>
#include <wrl.h>
#include "d3dx12.h"
#include <stdexcept>
#include <vector>
#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib,"d3dcompiler.lib")
using Microsoft::WRL::ComPtr;
class DXApplication
{
public:
・・・略・・・
private:
	static const unsigned int kFrameCount = 2;
	
	std::wstring title_;
	unsigned int windowWidth_;
	unsigned int windowHeight_;
	// パイプラインオブジェクト
	ComPtr<ID3D12Device> device_;
	ComPtr<ID3D12CommandAllocator> commandAllocator_;
	ComPtr<ID3D12GraphicsCommandList> commandList_;
	ComPtr<ID3D12CommandQueue> commandQueue_;
	ComPtr<IDXGISwapChain4> swapchain_;
	ComPtr<ID3D12DescriptorHeap> rtvHeaps_;             // レンダーターゲットヒープ
	ComPtr<ID3D12Resource> renderTargets_[kFrameCount]; // バックバッファー
	// フェンス
	ComPtr<ID3D12Fence> fence_;
	UINT64 fenceValue_;
	HANDLE fenceEvent_;
	void LoadPipeline(HWND hwnd);
	void CreateD3D12Device(IDXGIFactory6* dxgiFactory, ID3D12Device** d3d12device);
	void ThrowIfFailed(HRESULT hr);
};
エレキベア
エレキベア
だいぶ増えたクマが冒頭で紹介していた用語のものがほとんどになるクマね
マイケル
マイケル
軽く内容を見てみよう!
必要なヘッダをインクルード
マイケル
マイケル
最初にインクルードしているのはDirectXに用意されているヘッダになります。
ライブラリのリンクも必要になるため、忘れずに記述しましょう。
#include <d3d12.h>
#include <dxgi1_6.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>

・・・略・・・

#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib,"d3dcompiler.lib")
ComPtrの使用
マイケル
マイケル
そしてDirectX関連の変数がComPtrというあまり見慣れない型で定義されていますが、
こちらはstd::shared_ptrと似た働きを持つスマートポインタになります。

ComPtr クラス | Microsoft Learn

ComPtr<ID3D12Device> device_;

↑ComPtr型で定義されている

マイケル
マイケル
異なるのは解放時の動きで、参照カウントが0になった際にはRelease関数が呼び出されます。
DirectX関連のオブジェクトはGPUメモリも確保している場合があるため、ComPtrを使用することで明示的にReleaseを記述する必要がなくなる、というわけです!
エレキベア
エレキベア
なるほどクマ
GPU使用を考慮したスマートポインタというわけクマね
マイケル
マイケル
Microsoft::WRL::ComPtrで使用することができますが、長いため下記のように定義しておくと扱いやすくなります。
#include <wrl.h>
・・・
using Microsoft::WRL::ComPtr;
必要なオブジェクトの定義
マイケル
マイケル
下記はDirectX関連のオブジェクトで、ほぼComPtrで定義しています。
こちらはこの後初期化処理を記述していきます。

private:
	static const unsigned int kFrameCount = 2;

・・・

	// パイプラインオブジェクト
	ComPtr<ID3D12Device> device_;
	ComPtr<ID3D12CommandAllocator> commandAllocator_;
	ComPtr<ID3D12GraphicsCommandList> commandList_;
	ComPtr<ID3D12CommandQueue> commandQueue_;
	ComPtr<IDXGISwapChain4> swapchain_;
	ComPtr<ID3D12DescriptorHeap> rtvHeaps_;             // レンダーターゲットヒープ
	ComPtr<ID3D12Resource> renderTargets_[kFrameCount]; // バックバッファー

	// フェンス
	ComPtr<ID3D12Fence> fence_;
	UINT64 fenceValue_;
	HANDLE fenceEvent_;
エレキベア
エレキベア
やっぱり多いクマね〜〜

DirextX12オブジェクトの初期化

マイケル
マイケル
それでは実際に初期化処理を記述します。
OnInit、LoadPipeline関数を下記のように実装しましょう!
DXApplication::DXApplication(unsigned int width, unsigned int height, std::wstring title)
	: title_(title)
	, windowWidth_(width)
	, windowHeight_(height)
	, fenceValue_(0)
	, fenceEvent_(nullptr)
{
}

・・・略・・・

// 初期化処理
void DXApplication::OnInit(HWND hwnd)
{
	LoadPipeline(hwnd);
}

void DXApplication::LoadPipeline(HWND hwnd)
{
	UINT dxgiFactoryFlags = 0;

#ifdef _DEBUG
	{
		// デバッグレイヤーを有効にする
		ComPtr<ID3D12Debug> debugLayer;
		if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugLayer)))) {
			debugLayer->EnableDebugLayer();
			dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
		}
	}
#endif

	// DXGIFactoryの初期化
	ComPtr<IDXGIFactory6> dxgiFactory;
	ThrowIfFailed(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&dxgiFactory)));

	// デバイスの初期化
	CreateD3D12Device(dxgiFactory.Get(), device_.ReleaseAndGetAddressOf());

	// コマンド関連の初期化
	{
		// コマンドキュー
		D3D12_COMMAND_QUEUE_DESC commandQueueDesc = {};
		commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // タイムアウト無し
		commandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; // コマンドリストと合わせる
		ThrowIfFailed(device_->CreateCommandQueue(&commandQueueDesc, IID_PPV_ARGS(commandQueue_.ReleaseAndGetAddressOf())));
		// コマンドアロケータ
		ThrowIfFailed(device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(commandAllocator_.ReleaseAndGetAddressOf())));
		// コマンドリスト
		ThrowIfFailed(device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator_.Get(), nullptr, IID_PPV_ARGS(commandList_.ReleaseAndGetAddressOf())));
		ThrowIfFailed(commandList_->Close());
	}

	// スワップチェーンの初期化
	{
		DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {};
		swapchainDesc.BufferCount = kFrameCount;
		swapchainDesc.Width = windowWidth_;
		swapchainDesc.Height = windowHeight_;
		swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
		swapchainDesc.SampleDesc.Count = 1;
		ThrowIfFailed(dxgiFactory->CreateSwapChainForHwnd(
			commandQueue_.Get(),
			hwnd,
			&swapchainDesc,
			nullptr,
			nullptr,
			(IDXGISwapChain1**)swapchain_.ReleaseAndGetAddressOf()));
	}

	// ディスクリプタヒープの初期化
	{
		D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
		rtvHeapDesc.NumDescriptors = kFrameCount;            //表裏の2つ
		rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;   //レンダーターゲットビュー
		rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; //指定なし
		ThrowIfFailed(device_->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(rtvHeaps_.ReleaseAndGetAddressOf())));
	}

	// スワップチェーンと関連付けてレンダーターゲットビューを生成
	{
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeaps_->GetCPUDescriptorHandleForHeapStart());
		for (UINT i = 0; i < kFrameCount; ++i)
		{
			ThrowIfFailed(swapchain_->GetBuffer(static_cast<UINT>(i), IID_PPV_ARGS(renderTargets_[i].ReleaseAndGetAddressOf())));
			device_->CreateRenderTargetView(renderTargets_[i].Get(), nullptr, rtvHandle);
			rtvHandle.Offset(1, device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
		}
	}

	// フェンスの生成
	{
		ThrowIfFailed(device_->CreateFence(fenceValue_, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence_.ReleaseAndGetAddressOf())));
		fenceEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	}
}

// 終了処理
void DXApplication::OnDestroy()
{
	CloseHandle(fenceEvent_);
}

・・・略・・・

// D3D12Deviceの生成
void DXApplication::CreateD3D12Device(IDXGIFactory6* dxgiFactory, ID3D12Device** d3d12device)
{
	ID3D12Device* tmpDevice = nullptr;

	// グラフィックスボードの選択
	std::vector <IDXGIAdapter*> adapters;
	IDXGIAdapter* tmpAdapter = nullptr;
	for (int i = 0; SUCCEEDED(dxgiFactory->EnumAdapters(i, &tmpAdapter)); ++i)
	{
		adapters.push_back(tmpAdapter);
	}
	for (auto adapter : adapters)
	{
		DXGI_ADAPTER_DESC adapterDesc;
		adapter->GetDesc(&adapterDesc);
		// AMDを含むアダプターオブジェクトを探して格納(見つからなければnullptrでデフォルト)
		// 製品版の場合は、オプション画面から選択させて設定する必要がある
		std::wstring strAdapter = adapterDesc.Description;
		if (strAdapter.find(L"AMD") != std::string::npos)
		{
			tmpAdapter = adapter;
			break;
		}
	}

	// Direct3Dデバイスの初期化
	D3D_FEATURE_LEVEL levels[] = {
		D3D_FEATURE_LEVEL_12_1,
		D3D_FEATURE_LEVEL_12_0,
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
	};
	for (auto level : levels) {
		// 生成可能なバージョンが見つかったらループを打ち切り
		if (SUCCEEDED(D3D12CreateDevice(tmpAdapter, level, IID_PPV_ARGS(&tmpDevice)))) {
			break;
		}
	}
	*d3d12device = tmpDevice;
}
・・・略・・・
エレキベア
エレキベア
おおおぅ・・・・
マイケル
マイケル
ヘルパー関数を使っているのにこの量です・・・
こちらも一つ一つ見ていきましょう!
初期化時のエラーハンドリング
マイケル
マイケル
DirectX関連のオブジェクトの結果はHRESULTという形式で返ってきます。
こちらの結果チェックとしてThrowIfFailed関数を用意しました。
公式サンプルのコードを真似たもので、エラーの場合は問答無用でthrowしています。
void DXApplication::ThrowIfFailed(HRESULT hr)
{
	if (FAILED(hr))
	{
		// hrのエラー内容をthrowする
		char s_str[64] = {};
		sprintf_s(s_str, "HRESULT of 0x%08X", static_cast<UINT>(hr));
		std::string errMessage = std::string(s_str);
		throw std::runtime_error(errMessage);
	}
}
エレキベア
エレキベア
ThrowIfFailedで囲んでいる処理はHRESULT形式で返ってくる処理なのクマね
デバイスの初期化
マイケル
マイケル
まずは基本オブジェクトとなるデバイス(ID3D12Device)オブジェクトを生成します。
流れとしては
・生成に必要となるIDXGIFactory6オブジェクトを生成
・EnumAdapters関数で使用できるアダプタを探す
・生成可能なFEATURE_LEVELを探しつつID3D12Deviceオブジェクトを初期化する
といった流れになっています。
・・・略・・・

void DXApplication::LoadPipeline(HWND hwnd)
{
	UINT dxgiFactoryFlags = 0;

#ifdef _DEBUG
	{
		// デバッグレイヤーを有効にする
		ComPtr<ID3D12Debug> debugLayer;
		if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugLayer)))) {
			debugLayer->EnableDebugLayer();
			dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
		}
	}
#endif

	// DXGIFactoryの初期化
	ComPtr<IDXGIFactory6> dxgiFactory;
	ThrowIfFailed(CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&dxgiFactory)));

	// デバイスの初期化
	CreateD3D12Device(dxgiFactory.Get(), device_.ReleaseAndGetAddressOf());

・・・略・・・

}

・・・略・・・

// D3D12Deviceの生成
void DXApplication::CreateD3D12Device(IDXGIFactory6* dxgiFactory, ID3D12Device** d3d12device)
{
	ID3D12Device* tmpDevice = nullptr;

	// グラフィックスボードの選択
	std::vector <IDXGIAdapter*> adapters;
	IDXGIAdapter* tmpAdapter = nullptr;
	for (int i = 0; SUCCEEDED(dxgiFactory->EnumAdapters(i, &tmpAdapter)); ++i)
	{
		adapters.push_back(tmpAdapter);
	}
	for (auto adapter : adapters)
	{
		DXGI_ADAPTER_DESC adapterDesc;
		adapter->GetDesc(&adapterDesc);
		// AMDを含むアダプターオブジェクトを探して格納(見つからなければnullptrでデフォルト)
		// 製品版の場合は、オプション画面から選択させて設定する必要がある
		std::wstring strAdapter = adapterDesc.Description;
		if (strAdapter.find(L"AMD") != std::string::npos)
		{
			tmpAdapter = adapter;
			break;
		}
	}

	// Direct3Dデバイスの初期化
	D3D_FEATURE_LEVEL levels[] = {
		D3D_FEATURE_LEVEL_12_1,
		D3D_FEATURE_LEVEL_12_0,
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
	};
	for (auto level : levels) {
		// 生成可能なバージョンが見つかったらループを打ち切り
		if (SUCCEEDED(D3D12CreateDevice(tmpAdapter, level, IID_PPV_ARGS(&tmpDevice)))) {
			break;
		}
	}
	*d3d12device = tmpDevice;
}
マイケル
マイケル
頭でデバッグレイヤーも有効にしていますが、こちらは有効にすることでエラーの詳細まで確認できるようになります。
マイケル
マイケル
strAdapter.find(L”AMD”)の部分に関しては、自分の環境でAMDのGPUを指定していたために名称からfindしています。
リリースする際には使用できるアダプタを一覧表示して設定画面で選ばせるような対処が必要になるため、ご注意ください。
エレキベア
エレキベア
環境によって複数GPUが搭載されている場合もあるクマね
コマンド関連のオブジェクト初期化
マイケル
マイケル
そして次にコマンド関連のオブジェクトを生成しています。
こちらは冒頭で紹介した通り

・コマンドリスト
・コマンドアロケータ
・コマンドキュー

の3つが対処になります。
    // コマンド関連の初期化
	{
		// コマンドキュー
		D3D12_COMMAND_QUEUE_DESC commandQueueDesc = {};
		commandQueueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; // タイムアウト無し
		commandQueueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; // コマンドリストと合わせる
		ThrowIfFailed(device_->CreateCommandQueue(&commandQueueDesc, IID_PPV_ARGS(commandQueue_.ReleaseAndGetAddressOf())));
		// コマンドアロケータ
		ThrowIfFailed(device_->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(commandAllocator_.ReleaseAndGetAddressOf())));
		// コマンドリスト
		ThrowIfFailed(device_->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, commandAllocator_.Get(), nullptr, IID_PPV_ARGS(commandList_.ReleaseAndGetAddressOf())));
		ThrowIfFailed(commandList_->Close());
	}
エレキベア
エレキベア

どれもデバイスオブジェクトの関数を呼び出して生成しているクマね

スワップチェーンを生成してディスクリプタヒープと紐づける
マイケル
マイケル
そして次に画面の色を変更するためにスワップチェーンを生成します。
こちらはダブルバッファを行うために必要なもので、
・スワップチェーンオブジェクトを生成
・ディスクリプタヒープを生成
・ディスクリプタヒープとスワップチェーンを関連付けてレンダーターゲットビューを生成
といった流れで実装しています。
	// スワップチェーンの初期化
	{
		DXGI_SWAP_CHAIN_DESC1 swapchainDesc = {};
		swapchainDesc.BufferCount = kFrameCount;
		swapchainDesc.Width = windowWidth_;
		swapchainDesc.Height = windowHeight_;
		swapchainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		swapchainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
		swapchainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
		swapchainDesc.SampleDesc.Count = 1;
		ThrowIfFailed(dxgiFactory->CreateSwapChainForHwnd(
			commandQueue_.Get(),
			hwnd,
			&swapchainDesc,
			nullptr,
			nullptr,
			(IDXGISwapChain1**)swapchain_.ReleaseAndGetAddressOf()));
	}

	// ディスクリプタヒープの初期化
	{
		D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
		rtvHeapDesc.NumDescriptors = kFrameCount;            //表裏の2つ
		rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;   //レンダーターゲットビュー
		rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; //指定なし
		ThrowIfFailed(device_->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(rtvHeaps_.ReleaseAndGetAddressOf())));
	}

	// スワップチェーンと関連付けてレンダーターゲットビューを生成
	{
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeaps_->GetCPUDescriptorHandleForHeapStart());
		for (UINT i = 0; i < kFrameCount; ++i)
		{
			ThrowIfFailed(swapchain_->GetBuffer(static_cast<UINT>(i), IID_PPV_ARGS(renderTargets_[i].ReleaseAndGetAddressOf())));
			device_->CreateRenderTargetView(renderTargets_[i].Get(), nullptr, rtvHandle);
			rtvHandle.Offset(1, device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
		}
	}
マイケル
マイケル
スワップチェーンを行うため、フレーム数(2)分ループしながらレンダーターゲットビュー(RTV)を生成しています。
また冒頭で紹介した通り、ディスクリプタヒープにディスクリプタ(RTV)をまとめる形式になっていることが分かります。
ScreenShot 2022 11 20 2 11 19
エレキベア
エレキベア
少し複雑クマがじっくり見ていけば分かるクマね
フェンスの作成
マイケル
マイケル
そして最後にGPU処理を待機するためのフェンスオブジェクトを生成しています。
こちらはDestroy時にCloseする必要があるため、そちらも忘れないようにしましょう。
・・・略・・・
void DXApplication::LoadPipeline(HWND hwnd)
{

・・・略・・・

	// フェンスの生成
	{
		ThrowIfFailed(device_->CreateFence(fenceValue_, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(fence_.ReleaseAndGetAddressOf())));
		fenceEvent_ = CreateEvent(nullptr, FALSE, FALSE, nullptr);
	}
}

// 終了処理
void DXApplication::OnDestroy()
{
	CloseHandle(fenceEvent_);
}
エレキベア
エレキベア
これで初期化は完了クマ〜〜〜

描画処理

マイケル
マイケル
次は実際の描画処理・・・ということで、OnRender関数に処理を記述していきます。
最終的に下記のようになります!
// 描画処理
void DXApplication::OnRender()
{
	// コマンドリストのリセット
	{
		ThrowIfFailed(commandAllocator_->Reset());
		ThrowIfFailed(commandList_->Reset(commandAllocator_.Get(), nullptr));
	}

	// コマンドリストの生成
	{
		// バックバッファのインデックスを取得
		auto frameIndex = swapchain_->GetCurrentBackBufferIndex();

		// リソースバリアの設定 (PRESENT -> RENDER_TARGET)
		auto startResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets_[frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
		commandList_->ResourceBarrier(1, &startResourceBarrier);

		// レンダーターゲットの設定
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeaps_->GetCPUDescriptorHandleForHeapStart(), frameIndex, device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
		commandList_->OMSetRenderTargets(1, &rtvHandle, true, nullptr);

		// 画面クリア
		float clearColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };  // 黄色
		commandList_->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

		// TODO ここで描画処理を行う

		// リソースバリアの設定 (RENDER_TARGET -> PRESENT)
		auto endResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets_[frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
		commandList_->ResourceBarrier(1, &endResourceBarrier);

		// 命令のクローズ
		commandList_->Close();
	}

	// コマンドリストの実行
	{
		ID3D12CommandList* commandLists[] = { commandList_.Get() };
		commandQueue_->ExecuteCommandLists(1, commandLists);
		// 画面のスワップ
		ThrowIfFailed(swapchain_->Present(1, 0));
	}

	// GPU処理の終了を待機
	{
		ThrowIfFailed(commandQueue_->Signal(fence_.Get(), ++fenceValue_));
		if (fence_->GetCompletedValue() < fenceValue_) {
			ThrowIfFailed(fence_->SetEventOnCompletion(fenceValue_, fenceEvent_));
			WaitForSingleObject(fenceEvent_, INFINITE);
		}
	}
}
エレキベア
エレキベア
これはパッと見で大体分かりそうクマね
マイケル
マイケル
最初に図で載せた下記のようなイメージになるね!
一応これも一つ一つ見ていこう!
ScreenShot 2022 11 20 2 11 07↑コマンド実行のイメージ
コマンドリストのリセット
マイケル
マイケル
まずはループの初めにコマンドリストをリセットします。
これは特に説明不要ですね!
	// コマンドリストのリセット
	{
		ThrowIfFailed(commandAllocator_->Reset());
		ThrowIfFailed(commandList_->Reset(commandAllocator_.Get(), nullptr));
	}
コマンドリストの生成
マイケル
マイケル
そして次は実際にGPUへ送る命令を記述しています。
冒頭で「バリア」という用語を「コマンド実行時にリソースが何の用途に使用されるのかをGPUに伝えるもの」と紹介しましたが、それが「リソースバリアの設定」の部分になります。
今回だとPRESENT状態からRENDER_TAGET状態に移行する、と伝えてコマンドを格納した後にPRESENT状態に戻しています。
	// コマンドリストの生成
	{
		// バックバッファのインデックスを取得
		auto frameIndex = swapchain_->GetCurrentBackBufferIndex();

		// リソースバリアの設定 (PRESENT -> RENDER_TARGET)
		auto startResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets_[frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
		commandList_->ResourceBarrier(1, &startResourceBarrier);

		// レンダーターゲットの設定
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeaps_->GetCPUDescriptorHandleForHeapStart(), frameIndex, device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
		commandList_->OMSetRenderTargets(1, &rtvHandle, true, nullptr);

		// 画面クリア
		float clearColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };  // 黄色
		commandList_->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

		// TODO ここで描画処理を行う

		// リソースバリアの設定 (RENDER_TARGET -> PRESENT)
		auto endResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets_[frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
		commandList_->ResourceBarrier(1, &endResourceBarrier);

		// 命令のクローズ
		commandList_->Close();
	}
エレキベア
エレキベア
リソースの使用用途を伝えるのはそういうことだったクマね
マイケル
マイケル
状態を切り替えたら、ディスクリプタヒープから情報を取り出してレンダーターゲットを設定し、画面の色も変更(今回は黄色単色)します。
一通りの命令を格納したらクローズするのも忘れないようにしましょう!
コマンドリストの実行
マイケル
マイケル
あとはコマンドリストを実行するのみ!
実行後はスワップチェーンオブジェクトから画面をスワップしています。
	// コマンドリストの実行
	{
		ID3D12CommandList* commandLists[] = { commandList_.Get() };
		commandQueue_->ExecuteCommandLists(1, commandLists);
		// 画面のスワップ
		ThrowIfFailed(swapchain_->Present(1, 0));
	}
エレキベア
エレキベア
ここで溜めた処理が実行されるクマね
GPU処理の終了を待機
マイケル
マイケル
最後に、生成したフェンスオブジェクトを使用してGPU処理の終了を待機します。
	// GPU処理の終了を待機
	{
		ThrowIfFailed(commandQueue_->Signal(fence_.Get(), ++fenceValue_));
		if (fence_->GetCompletedValue() < fenceValue_) {
			ThrowIfFailed(fence_->SetEventOnCompletion(fenceValue_, fenceEvent_));
			WaitForSingleObject(fenceEvent_, INFINITE);
		}
	}
エレキベア
エレキベア
これで実装は完了クマ〜〜〜!!

実行!

マイケル
マイケル
この状態で実行し、下記のように真っ黄色の画面が表示されれば成功です!!
20221119 05
エレキベア
エレキベア
やったクマ〜〜〜〜
マイケル
マイケル
ループを確かめたい場合は、フレームごとに色を少しずつ変えたり等すれば変化することも確認できるはずです!

ポリゴンを表示する

マイケル
マイケル
さあここまで来たらあと一息!
ポリゴンを表示してみましょう!!
エレキベア
エレキベア
そういえばまだポリゴンすら表示できてなかったクマ・・・

頂点シェーダーとピクセル(フラグメント)シェーダー

マイケル
マイケル
DirectXの場合は2Dオブジェクトを表示するだけでもシェーダーを書く必要があるため、
頂点シェーダーピクセルシェーダーを実際に書いていきます。
シェーダについては冒頭でも軽く紹介しましたが、下記記事でも紹介しているため、よければそちらもご参考ください!

【C++】第四回 C++を使ったゲーム開発 〜3Dゲーム開発基礎 fbx読込とシェーダ編〜

ScreenShot 2021 10 09 21 59 57
エレキベア
エレキベア
懐かしい記事クマ〜〜〜

シェーダーファイルの作成

マイケル
マイケル
それでは早速作成していきましょう!
まずは頂点シェーダーからですが、ファイルの追加から「頂点シェーダーファイル」を選択して雛形を作成することができます。
今回は「BasicVertexShader.hlsl」というファイル名で作成しました。
20221119 06↑シェーダーファイルの作成
マイケル
マイケル
作成後、下記の内容に修正します。
頂点位置をそのまま返すだけのシンプルなものになります。
float4 BasicVS(float4 pos : POSITION) : SV_POSITION
{
    // 頂点位置をそのまま返す
	return pos;
}
↑頂点シェーダー処理の記述
マイケル
マイケル
後はシェーダーファイルを右クリックし、プロパティからエントリポイント名、シェーダーモデルを設定すれば作成完了です!!
20221119 07↑今回はSM5.0で設定した
マイケル
マイケル
同様にピクセルシェーダーも作成します。
20221119 08↑シェーダーファイルの作成
float4 BasicPS() : SV_TARGET
{
    // 青色で返す
	return float4(0.0f, 0.0f, 1.0f, 1.0f);
}
↑処理の記述(青色単色で返す)
20221119 09↑エントリポイント名、シェーダーモデルを設定
マイケル
マイケル
これでシェーダーの準備は完了です!
エレキベア
エレキベア
あとはこれを読み込んで表示クマ〜〜〜!!

頂点座標とインデックス座標

マイケル
マイケル
実装していく前に前提知識になりますが、頂点座標に加えてインデックス座標の概念も知っておく必要があります。
こちらは下記記事でも簡単に解説していますので、よければご参照ください!

【C++】第三回 C++を使ったゲーム開発 〜3Dゲーム開発基礎 OpenGLと座標変換編〜

エレキベア
エレキベア
これも懐かしいクマね〜〜
マイケル
マイケル
簡単にいうと、インデックス座標は頂点にインデックス値を与えることで複数回参照する処理を効率化する仕組みです。
例として下記のように4つの頂点があった場合、0〜3のインデックスを振ってあげることで「0,1,2」「0,2,3」といった座標で参照することができます。
ScreenShot 2021 10 09 21 58 11 1
↑頂点座標
ScreenShot 2021 10 09 21 58 11 2
↑インデックス座標
エレキベア
エレキベア
今回も四角形を描画するために三角ポリゴンを2つ描画するクマね

シェーダーファイルの読み込みとポリゴン描画

マイケル
マイケル
それではシェーダーファイルを読み込んでポリゴンを描画させていきます。
最終的には下記のようになります!
private:
	static const unsigned int kFrameCount = 2;

	std::wstring title_;
	unsigned int windowWidth_;
	unsigned int windowHeight_;

	// ★ポリゴン描画のため追加
	CD3DX12_VIEWPORT viewport_; // ビューポート
	CD3DX12_RECT scissorrect_;  // シザー短形

	// パイプラインオブジェクト
	ComPtr<ID3D12Device> device_;
	ComPtr<ID3D12CommandAllocator> commandAllocator_;
	ComPtr<ID3D12GraphicsCommandList> commandList_;
	ComPtr<ID3D12CommandQueue> commandQueue_;
	ComPtr<IDXGISwapChain4> swapchain_;
	ComPtr<ID3D12DescriptorHeap> rtvHeaps_;             // レンダーターゲットヒープ
	ComPtr<ID3D12Resource> renderTargets_[kFrameCount]; // バックバッファー

	// ★ポリゴン描画のため追加
	ComPtr<ID3D12PipelineState> pipelinestate_;         // パイプラインステート
	ComPtr<ID3D12RootSignature> rootsignature_;         // ルートシグネチャ

	// ★ポリゴン描画のため追加
	// リソース
	ComPtr<ID3D12Resource> vertexBuffer_;
	D3D12_VERTEX_BUFFER_VIEW vertexBufferView_;
	ComPtr<ID3D12Resource> indexBuffer_;
	D3D12_INDEX_BUFFER_VIEW indexBufferView_;

	// フェンス
	ComPtr<ID3D12Fence> fence_;
	UINT64 fenceValue_;
	HANDLE fenceEvent_;

	void LoadPipeline(HWND hwnd);
	void LoadAssets(); // ★ポリゴン描画のため追加

	void CreateD3D12Device(IDXGIFactory6* dxgiFactory, ID3D12Device** d3d12device);
	void ThrowIfFailed(HRESULT hr);
#include "DXApplication.h"

DXApplication::DXApplication(unsigned int width, unsigned int height, std::wstring title)
	: title_(title)
	, windowWidth_(width)
	, windowHeight_(height)
	
	// ★ポリゴン描画のため追加
	, viewport_(0.0f, 0.0f, static_cast<float>(windowWidth_), static_cast<float>(windowHeight_))
	, scissorrect_(0, 0, static_cast<LONG>(windowWidth_), static_cast<LONG>(windowHeight_))
	, vertexBufferView_({})
	, indexBufferView_({})
	
	, fenceValue_(0)
	, fenceEvent_(nullptr)
{
}

// 初期化処理
void DXApplication::OnInit(HWND hwnd)
{
	LoadPipeline(hwnd);
	LoadAssets(); // ★ポリゴン描画のため追加
}

・・・略・・・

// ★ポリゴン描画のため追加
void DXApplication::LoadAssets()
{
	// ルートシグネチャの生成
	{
		D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
		rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
		ComPtr<ID3DBlob> rootSignatureBlob = nullptr;
		ComPtr<ID3DBlob> errorBlob = nullptr;
		ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &rootSignatureBlob, &errorBlob));
		ThrowIfFailed(device_->CreateRootSignature(0, rootSignatureBlob->GetBufferPointer(), rootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(rootsignature_.ReleaseAndGetAddressOf())));
	}

	// パイプラインステートの生成
	{
		// シェーダーオブジェクトの生成
#if defined(_DEBUG)
		UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
		UINT compileFlags = 0;
#endif
		ComPtr<ID3DBlob> vsBlob;
		ComPtr<ID3DBlob> psBlob;
		D3DCompileFromFile(L"BasicVertexShader.hlsl", nullptr, nullptr, "BasicVS", "vs_5_0", compileFlags, 0, &vsBlob, nullptr);
		D3DCompileFromFile(L"BasicPixelShader.hlsl", nullptr, nullptr, "BasicPS", "ps_5_0", compileFlags, 0, &psBlob, nullptr);

		// 頂点レイアウトの生成
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		};

		// パイプラインステートオブジェクト(PSO)を生成
		D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
		psoDesc.pRootSignature = rootsignature_.Get();
		psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; // 入力レイアウトの設定
		psoDesc.VS = CD3DX12_SHADER_BYTECODE(vsBlob.Get());                       // 頂点シェーダ
		psoDesc.PS = CD3DX12_SHADER_BYTECODE(psBlob.Get());                       // ピクセルシェーダ
		psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);         // ラスタライザーステート
		psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);                   // ブレンドステート
		psoDesc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK;                           // サンプルマスクの設定
		psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;   // トポロジタイプ
		psoDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED;    // ストリップ時のカット設定
		psoDesc.NumRenderTargets = 1;                                             // レンダーターゲット数
		psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;                       // レンダーターゲットフォーマット
		psoDesc.SampleDesc.Count = 1;                                             // マルチサンプリングの設定
		ThrowIfFailed(device_->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(pipelinestate_.ReleaseAndGetAddressOf())));
	}

	// 頂点バッファビューの生成
	{
		// 頂点定義
		DirectX::XMFLOAT3 vertices[] = {
			{-0.4f,-0.7f, 0.0f} , //左下
			{-0.4f, 0.7f, 0.0f} , //左上
			{ 0.4f,-0.7f, 0.0f} , //右下
			{ 0.4f, 0.7f, 0.0f} , //右上
		};
		const UINT vertexBufferSize = sizeof(vertices);
		auto vertexHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
		auto vertexResDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
		// 頂点バッファーの生成
		ThrowIfFailed(device_->CreateCommittedResource(
			&vertexHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&vertexResDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(vertexBuffer_.ReleaseAndGetAddressOf())));
		// 頂点情報のコピー
		DirectX::XMFLOAT3* vertexMap = nullptr;
		ThrowIfFailed(vertexBuffer_->Map(0, nullptr, (void**)&vertexMap));
		std::copy(std::begin(vertices), std::end(vertices), vertexMap);
		vertexBuffer_->Unmap(0, nullptr);
		// 頂点バッファービューの生成
		vertexBufferView_.BufferLocation = vertexBuffer_->GetGPUVirtualAddress();
		vertexBufferView_.SizeInBytes = vertexBufferSize;
		vertexBufferView_.StrideInBytes = sizeof(vertices[0]);
	}


	// インデックスバッファビューの生成
	{
		// インデックス定義
		unsigned short indices[] = {
			0, 1, 2,
			2, 1, 3
		};
		const UINT indexBufferSize = sizeof(indices);
		auto indexHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
		auto indexResDesc = CD3DX12_RESOURCE_DESC::Buffer(indexBufferSize);
		// インデックスバッファの生成
		ThrowIfFailed(device_->CreateCommittedResource(
			&indexHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&indexResDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(indexBuffer_.ReleaseAndGetAddressOf())));
		// インデックス情報のコピー
		unsigned short* indexMap = nullptr;
		indexBuffer_->Map(0, nullptr, (void**)&indexMap);
		std::copy(std::begin(indices), std::end(indices), indexMap);
		indexBuffer_->Unmap(0, nullptr);
		// インデックスバッファビューの生成
		indexBufferView_.BufferLocation = indexBuffer_->GetGPUVirtualAddress();
		indexBufferView_.SizeInBytes = indexBufferSize;
		indexBufferView_.Format = DXGI_FORMAT_R16_UINT;
	}
}

// 更新処理
void DXApplication::OnUpdate()
{
}

// 描画処理
void DXApplication::OnRender()
{
	// コマンドリストのリセット
	{
		ThrowIfFailed(commandAllocator_->Reset());
		ThrowIfFailed(commandList_->Reset(commandAllocator_.Get(), pipelinestate_.Get())); // ★パイプラインステートを設定
	}

	// コマンドリストの生成
	{
		// バックバッファのインデックスを取得
		auto frameIndex = swapchain_->GetCurrentBackBufferIndex();

		// リソースバリアの設定 (PRESENT -> RENDER_TARGET)
		auto startResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets_[frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET);
		commandList_->ResourceBarrier(1, &startResourceBarrier);

        // ★ポリゴン描画のため追加
		// パイプラインステートと必要なオブジェクトを設定
		commandList_->SetPipelineState(pipelinestate_.Get());         // パイプラインステート
		commandList_->SetGraphicsRootSignature(rootsignature_.Get()); // ルートシグネチャ
		commandList_->RSSetViewports(1, &viewport_);                  // ビューポート
		commandList_->RSSetScissorRects(1, &scissorrect_);            // シザー短形

		// レンダーターゲットの設定
		CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(rtvHeaps_->GetCPUDescriptorHandleForHeapStart(), frameIndex, device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV));
		commandList_->OMSetRenderTargets(1, &rtvHandle, true, nullptr);

		// 画面クリア
		float clearColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };  // 黄色
		commandList_->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);

        // ★ポリゴン描画のため追加
		// 描画処理の設定
		commandList_->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // プリミティブトポロジの設定 (三角ポリゴン)
		commandList_->IASetVertexBuffers(0, 1, &vertexBufferView_);                // 頂点バッファ
		commandList_->IASetIndexBuffer(&indexBufferView_);                         // インデックスバッファ
		commandList_->DrawIndexedInstanced(6, 1, 0, 0, 0);                         // 描画

		// リソースバリアの設定 (RENDER_TARGET -> PRESENT)
		auto endResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(renderTargets_[frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);
		commandList_->ResourceBarrier(1, &endResourceBarrier);

		// 命令のクローズ
		commandList_->Close();
	}

	// コマンドリストの実行
	{
		ID3D12CommandList* commandLists[] = { commandList_.Get() };
		commandQueue_->ExecuteCommandLists(1, commandLists);
		// 画面のスワップ
		ThrowIfFailed(swapchain_->Present(1, 0));
	}

	// GPU処理の終了を待機
	{
		ThrowIfFailed(commandQueue_->Signal(fence_.Get(), ++fenceValue_));
		if (fence_->GetCompletedValue() < fenceValue_) {
			ThrowIfFailed(fence_->SetEventOnCompletion(fenceValue_, fenceEvent_));
			WaitForSingleObject(fenceEvent_, INFINITE);
		}
	}
}

// 終了処理
void DXApplication::OnDestroy()
{
	CloseHandle(fenceEvent_);
}

// D3D12Deviceの生成
void DXApplication::CreateD3D12Device(IDXGIFactory6* dxgiFactory, ID3D12Device** d3d12device)
{
	ID3D12Device* tmpDevice = nullptr;

	// グラフィックスボードの選択
	std::vector <IDXGIAdapter*> adapters;
	IDXGIAdapter* tmpAdapter = nullptr;
	for (int i = 0; SUCCEEDED(dxgiFactory->EnumAdapters(i, &tmpAdapter)); ++i)
	{
		adapters.push_back(tmpAdapter);
	}
	for (auto adapter : adapters)
	{
		DXGI_ADAPTER_DESC adapterDesc;
		adapter->GetDesc(&adapterDesc);
		// AMDを含むアダプターオブジェクトを探して格納(見つからなければnullptrでデフォルト)
		// 製品版の場合は、オプション画面から選択させて設定する必要がある
		std::wstring strAdapter = adapterDesc.Description;
		if (strAdapter.find(L"AMD") != std::string::npos)
		{
			tmpAdapter = adapter;
			break;
		}
	}

	// Direct3Dデバイスの初期化
	D3D_FEATURE_LEVEL levels[] = {
		D3D_FEATURE_LEVEL_12_1,
		D3D_FEATURE_LEVEL_12_0,
		D3D_FEATURE_LEVEL_11_1,
		D3D_FEATURE_LEVEL_11_0,
	};
	for (auto level : levels) {
		// 生成可能なバージョンが見つかったらループを打ち切り
		if (SUCCEEDED(D3D12CreateDevice(tmpAdapter, level, IID_PPV_ARGS(&tmpDevice)))) {
			break;
		}
	}
	*d3d12device = tmpDevice;
}

void DXApplication::ThrowIfFailed(HRESULT hr)
{
	if (FAILED(hr))
	{
		// hrのエラー内容をthrowする
		char s_str[64] = {};
		sprintf_s(s_str, "HRESULT of 0x%08X", static_cast<UINT>(hr));
		std::string errMessage = std::string(s_str);
		throw std::runtime_error(errMessage);
	}
}
マイケル
マイケル
「// ★~~」で記述したコメントが追加箇所になるのですが、それなりに量もあるので一つ一つ見ていきましょう!
エレキベア
エレキベア
だいぶコードも多くなったクマね〜〜〜
必要なオブジェクトを定義
マイケル
マイケル
新たに必要となるオブジェクト、関数は下記になります。
パイプラインステート、ルートシグネチャ、シェーダ関連のオブジェクトに加えて、ビューポート、シザー短形といった表示領域を設定するオブジェクトも必要になります。

	CD3DX12_VIEWPORT viewport_; // ビューポート
	CD3DX12_RECT scissorrect_;  // シザー短形

・・・略・・・

	ComPtr<ID3D12PipelineState> pipelinestate_;         // パイプラインステート
	ComPtr<ID3D12RootSignature> rootsignature_;         // ルートシグネチャ

	// リソース
	ComPtr<ID3D12Resource> vertexBuffer_;
	D3D12_VERTEX_BUFFER_VIEW vertexBufferView_;
	ComPtr<ID3D12Resource> indexBuffer_;
	D3D12_INDEX_BUFFER_VIEW indexBufferView_;

・・・略・・・

	void LoadAssets();

・・・略・・・
マイケル
マイケル
今回はビューポート、シザー短形については単純にウィンドウの幅を設定するだけで問題ありません。
あとはLoadPipelineの後にLoadAssets関数を定義し、ここでシェーダの読み込みや設定を行うようにしていきます。

DXApplication::DXApplication(unsigned int width, unsigned int height, std::wstring title)
・・・略・・・
	, viewport_(0.0f, 0.0f, static_cast<float>(windowWidth_), static_cast<float>(windowHeight_))
	, scissorrect_(0, 0, static_cast<LONG>(windowWidth_), static_cast<LONG>(windowHeight_))
	, vertexBufferView_({})
	, indexBufferView_({})
・・・略・・・
{
}

// 初期化処理
void DXApplication::OnInit(HWND hwnd)
{
	LoadPipeline(hwnd);
	LoadAssets();
}

・・・略・・・

void DXApplication::LoadAssets()
{
・・・略・・・
}

・・・略・・・
エレキベア
エレキベア
LoadAssetsに処理を書いていくクマね
ルートシグネチャの生成
マイケル
マイケル
まずはルートシグネチャの生成です。
これは冒頭の概要で軽く触れた通り、ディスクリプタテーブル(ディスクリプタヒープとシェーダーのレジスタを関連付けたもの)をまとめるものになりますが、今回は座標を定義して渡すだけなので特に設定するものはありません
しかし、パイプラインステートの生成で必要になるため初期化のみ行なっています。
	// ルートシグネチャの生成
	{
		D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = {};
		rootSignatureDesc.Flags = D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT;
		ComPtr<ID3DBlob> rootSignatureBlob = nullptr;
		ComPtr<ID3DBlob> errorBlob = nullptr;
		ThrowIfFailed(D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &rootSignatureBlob, &errorBlob));
		ThrowIfFailed(device_->CreateRootSignature(0, rootSignatureBlob->GetBufferPointer(), rootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(rootsignature_.ReleaseAndGetAddressOf())));
	}
エレキベア
エレキベア
今回は役目無しクマか・・・
パイプラインステートの生成
マイケル
マイケル
そして次はパイプラインステートの生成になります。
これが冒頭で紹介したPipelineStateObject(PSO)になりますね。
ScreenShot 2022 11 20 2 11 29
	// パイプラインステートの生成
	{
		// シェーダーオブジェクトの生成
#if defined(_DEBUG)
		UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#else
		UINT compileFlags = 0;
#endif
		ComPtr<ID3DBlob> vsBlob;
		ComPtr<ID3DBlob> psBlob;
		D3DCompileFromFile(L"BasicVertexShader.hlsl", nullptr, nullptr, "BasicVS", "vs_5_0", compileFlags, 0, &vsBlob, nullptr);
		D3DCompileFromFile(L"BasicPixelShader.hlsl", nullptr, nullptr, "BasicPS", "ps_5_0", compileFlags, 0, &psBlob, nullptr);

		// 頂点レイアウトの生成
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
		};

		// パイプラインステートオブジェクト(PSO)を生成
		D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};
		psoDesc.pRootSignature = rootsignature_.Get();
		psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; // 入力レイアウトの設定
		psoDesc.VS = CD3DX12_SHADER_BYTECODE(vsBlob.Get());                       // 頂点シェーダ
		psoDesc.PS = CD3DX12_SHADER_BYTECODE(psBlob.Get());                       // ピクセルシェーダ
		psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);         // ラスタライザーステート
		psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);                   // ブレンドステート
		psoDesc.SampleMask = D3D12_DEFAULT_SAMPLE_MASK;                           // サンプルマスクの設定
		psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;   // トポロジタイプ
		psoDesc.IBStripCutValue = D3D12_INDEX_BUFFER_STRIP_CUT_VALUE_DISABLED;    // ストリップ時のカット設定
		psoDesc.NumRenderTargets = 1;                                             // レンダーターゲット数
		psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;                       // レンダーターゲットフォーマット
		psoDesc.SampleDesc.Count = 1;                                             // マルチサンプリングの設定
		ThrowIfFailed(device_->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(pipelinestate_.ReleaseAndGetAddressOf())));
	}
マイケル
マイケル
ここでシェーダーファイルを実際に読みこんで設定しています。
この時頂点レイアウトを生成することで、シェーダーに渡すデータを定義することができます。
エレキベア
エレキベア
いろいろ設定項目はあるクマが、
まあ今回は頂点シェーダ、ピクセルシェーダを設定しているだけクマね
頂点バッファビュー、インデックスバッファビューの生成
マイケル
マイケル
次に頂点座標、インデックス座標を直接定義して、バッファビューとして生成します。
バッファビューというのは、座標を定義したバッファ(メモリ領域)を渡すための設定情報というようなイメージです。
	// 頂点バッファビューの生成
	{
		// 頂点定義
		DirectX::XMFLOAT3 vertices[] = {
			{-0.4f,-0.7f, 0.0f} , //左下
			{-0.4f, 0.7f, 0.0f} , //左上
			{ 0.4f,-0.7f, 0.0f} , //右下
			{ 0.4f, 0.7f, 0.0f} , //右上
		};
		const UINT vertexBufferSize = sizeof(vertices);
		auto vertexHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
		auto vertexResDesc = CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize);
		// 頂点バッファーの生成
		ThrowIfFailed(device_->CreateCommittedResource(
			&vertexHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&vertexResDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(vertexBuffer_.ReleaseAndGetAddressOf())));
		// 頂点情報のコピー
		DirectX::XMFLOAT3* vertexMap = nullptr;
		ThrowIfFailed(vertexBuffer_->Map(0, nullptr, (void**)&vertexMap));
		std::copy(std::begin(vertices), std::end(vertices), vertexMap);
		vertexBuffer_->Unmap(0, nullptr);
		// 頂点バッファービューの生成
		vertexBufferView_.BufferLocation = vertexBuffer_->GetGPUVirtualAddress();
		vertexBufferView_.SizeInBytes = vertexBufferSize;
		vertexBufferView_.StrideInBytes = sizeof(vertices[0]);
	}


	// インデックスバッファビューの生成
	{
		// インデックス定義
		unsigned short indices[] = {
			0, 1, 2,
			2, 1, 3
		};
		const UINT indexBufferSize = sizeof(indices);
		auto indexHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
		auto indexResDesc = CD3DX12_RESOURCE_DESC::Buffer(indexBufferSize);
		// インデックスバッファの生成
		ThrowIfFailed(device_->CreateCommittedResource(
			&indexHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&indexResDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(indexBuffer_.ReleaseAndGetAddressOf())));
		// インデックス情報のコピー
		unsigned short* indexMap = nullptr;
		indexBuffer_->Map(0, nullptr, (void**)&indexMap);
		std::copy(std::begin(indices), std::end(indices), indexMap);
		indexBuffer_->Unmap(0, nullptr);
		// インデックスバッファビューの生成
		indexBufferView_.BufferLocation = indexBuffer_->GetGPUVirtualAddress();
		indexBufferView_.SizeInBytes = indexBufferSize;
		indexBufferView_.Format = DXGI_FORMAT_R16_UINT;
	}
エレキベア
エレキベア
最終的にはバッファビューの形式にしてGPUに渡せるようにするクマね
コマンド命令の追加
マイケル
マイケル
最後に、これらを用いてコマンドリストに描画命令を追加します。
パイプラインステート、ルートシグネチャといった必要なオブジェクトを設定し、
バッファビューの設定とともに描画処理を追加します。
	// コマンドリストの生成
	{

・・・略・・・

		// パイプラインステートと必要なオブジェクトを設定
		commandList_->SetPipelineState(pipelinestate_.Get());         // パイプラインステート
		commandList_->SetGraphicsRootSignature(rootsignature_.Get()); // ルートシグネチャ
		commandList_->RSSetViewports(1, &viewport_);                  // ビューポート
		commandList_->RSSetScissorRects(1, &scissorrect_);            // シザー短形

・・・略・・・

		// 描画処理の設定
		commandList_->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); // プリミティブトポロジの設定 (三角ポリゴン)
		commandList_->IASetVertexBuffers(0, 1, &vertexBufferView_);                // 頂点バッファ
		commandList_->IASetIndexBuffer(&indexBufferView_);                         // インデックスバッファ
		commandList_->DrawIndexedInstanced(6, 1, 0, 0, 0);                         // 描画

・・・略・・・

	}
マイケル
マイケル
DrawIndexedInstancedには、インデックス座標の数である6を設定しています。
これにて一通りの処理の実装は完了です!!
エレキベア
エレキベア
ついにクマ・・・・

実行!

マイケル
マイケル
実装が完了したところで実行・・・!
これで下記のように青色の四角形が描画されれば成功です!!
20221119 10
エレキベア
エレキベア
やったクマ〜〜〜!!
長かったクマ〜〜〜〜

おわりに

マイケル
マイケル
というわけで今回はDirectX12を用いたポリゴン描画でした!
どうだったかな??
エレキベア
エレキベア
噂に聞いていた通り大変だったクマ・・・
でも一つ一つ見ていけば、特段難しいことをやっている感じでもなかったクマね
マイケル
マイケル
実際に書いてみて、ハードルが高いと言われている理由は
・前提知識(シェーダーや座標周り)が必要
・公式チュートリアルの情報が最小限かつ基本は英語
・専門用語が多く、パイプラインも複雑
・生成が必要なオブジェクト、設定量が多い
・今はゲームエンジンで簡単に作れるのでモチベーションが出にくい
といったところになるのかなと思ったよ。
エレキベア
エレキベア
初っ端から手を出すのはかなり厳しそうクマね
マイケル
マイケル
この記事も量がかなり多くなってしまった・・・
今後もとりあえずFBXモデル表示くらいまでは作ってみようと思うからお楽しみに!
それでは今日はこの辺でアデューー!!
エレキベア
エレキベア
クマ〜〜〜〜〜〜

【DirextX12】第一回 DirextX12を使ったゲーム開発 〜基礎知識からポリゴン描画まで〜 〜完〜

※次回の記事はこちら!

コメント