ゲーム開発
Unity
UnrealEngine
C++
Blender
ゲーム数学
ゲームAI
グラフィックス
サウンド
アニメーション
GBDK
制作日記
IT関連
ツール開発
フロントエンド関連
サーバサイド関連
WordPress関連
ソフトウェア設計
おすすめ技術書
音楽
DTM
楽器・機材
ピアノ
ラーメン日記
四コマ漫画
その他
おすすめアイテム
おもしろコラム
  • ゲーム開発
    • Unity
    • UnrealEngine
    • C++
    • Blender
    • ゲーム数学
    • ゲームAI
    • グラフィックス
    • サウンド
    • アニメーション
    • GBDK
    • 制作日記
  • IT関連
    • ツール開発
    • フロントエンド関連
    • サーバサイド関連
    • WordPress関連
    • ソフトウェア設計
    • おすすめ技術書
  • 音楽
    • DTM
    • 楽器・機材
    • ピアノ
  • ラーメン日記
    • 四コマ漫画
      • その他
        • おすすめアイテム
        • おもしろコラム
      1. ホーム
      2. 20221119_01

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

      C++グラフィックスDirectX12
      2022-11-20

      マイケル
      マイケル
      みなさんこんにちは!
      マイケルです!
      エレキベア
      エレキベア
      こんにちクマ〜〜〜
      マイケル
      マイケル
      突然ですが、今回から何回かに分けて
      DirectX12 を触っていこうと思います!
      エレキベア
      エレキベア
      ついに触る時が来たクマか・・・
      マイケル
      マイケル
      というのも、描画周りの学習を進めていくにあたり
      やはり描画パイプライン周りの知識は必要だな、と感じたためです。
      エレキベア
      エレキベア
      前OpenGLで簡単なゲームも作ってたクマが、
      拡張ライブラリやSDLも使用してたクマからね〜〜
      マイケル
      マイケル
      あとはやっぱり一度は触ってみたかったからね!!
      今回は下記のように画面の塗りつぶしと四角形のポリゴン表示を行うサンプルを作っていこうと思います!
      ↑黄色で画面を塗りつぶして青色のポリゴンを表示している
      エレキベア
      エレキベア
      こんなの余裕クマ〜〜〜
      マイケル
      マイケル
      それでもこれだけで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年〜 DirectX5 Windows95の時代になってから普及し、PCで3Dゲームすることを広めた。
      1998年〜 DirectX6 ラスタライズ処理やテクスチャ貼りつけ等のピクセル単位の処理のみ担当した。
      1999年〜 DirectX7 頂点単位の座標変換、光源処理を担当できるようになった。
      2000年〜 DirectX8 シェーダをプログラムできるプログラマブルシェーダをサポートした。(SM1.x
      2002年〜 DirectX9 SM2.0、SM3.0と対応され、HLSL言語にも対応するようになる。
      2006年〜 DirectX10 SM4.0に対応し、ジオメトリシェーダを含めたパイプラインに変更された。
      2009年〜 DirectX11 SM5.0に対応し、新たに4つのシェーダステージが追加された。
      (DirectX9、10と互換性有り)
      2015年〜 DirectX12 SM5.1に対応し、Pipeline State Object (PSO)というパイプラインの仕組みに変更された。(DirectX11と互換性無し)

      参考:

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

      エレキベア
      エレキベア
      DirectX12になるまでもいろんな歴史があったクマね
      マイケル
      マイケル
      DirectX9〜11までのパイプラインの変更内容についてはざっくりと下記のようになっています。
      DirectX9

      DirectX10

      DirectX11

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

      用語とイメージ

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

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

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

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

      Win32APIでのウィンドウ表示

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

      GitHub – masarito617/cpp-dx12-sample-polygon

      ↑今回作ったサンプル

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

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

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

      空のプロジェクト作成

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

      ウィンドウを表示する

      マイケル
      マイケル
      ここからコードを書いていきますが、その前にWindowsAPIを使用するため
      リンカー > システム > サブシステム をWindowsに指定しておきます。
      ↑サブシステムを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;
      }
      
      ↑空のウィンドウが表示される
      エレキベア
      エレキベア
      ここまでは余裕クマね

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

      マイケル
      マイケル
      ウィンドウの準備が出来たところで、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)をまとめる形式になっていることが分かります。
      エレキベア
      エレキベア
      少し複雑クマがじっくり見ていけば分かるクマね
      フェンスの作成
      マイケル
      マイケル
      そして最後に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);
      		}
      	}
      }
      
      エレキベア
      エレキベア
      これはパッと見で大体分かりそうクマね
      マイケル
      マイケル
      最初に図で載せた下記のようなイメージになるね!
      一応これも一つ一つ見ていこう!
      ↑コマンド実行のイメージ
      コマンドリストのリセット
      マイケル
      マイケル
      まずはループの初めにコマンドリストをリセットします。
      これは特に説明不要ですね!
      	// コマンドリストのリセット
      	{
      		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);
      		}
      	}
      
      エレキベア
      エレキベア
      これで実装は完了クマ〜〜〜!!

      実行!

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

      ポリゴンを表示する

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

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

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

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


      エレキベア
      エレキベア
      懐かしい記事クマ〜〜〜

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

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

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

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

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

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

      ↑頂点座標

      ↑インデックス座標
      エレキベア
      エレキベア
      今回も四角形を描画するために三角ポリゴンを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)になりますね。
      	// パイプラインステートの生成
      	{
      		// シェーダーオブジェクトの生成
      #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を設定しています。
      これにて一通りの処理の実装は完了です!!
      エレキベア
      エレキベア
      ついにクマ・・・・

      実行!

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

      おわりに

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

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

      ※次回の記事はこちら!

      C++グラフィックスDirectX12
      2022-11-20

      関連記事
      【書籍紹介】「コンピュータグラフィックス」に出てくる用語をまとめる【CGエンジニア検定】
      2024-07-13
      【UE5】Niagara SimulationStageによるシミュレーション環境構築
      2024-05-30
      【Unity】Boidsアルゴリズムを用いて魚の群集シミュレーションを実装する
      2024-05-28
      【UE5】第三回 ミニゲーム制作で学ぶUnrealC++ 〜UI・仕上げ実装 編〜
      2024-05-18
      【UE5】第二回 ミニゲーム制作で学ぶUnrealC++ 〜キャラクター・ゲーム実装 編〜
      2024-05-18
      【UE5】第一回 ミニゲーム制作で学ぶUnrealC++ 〜UnrealC++の概要 編〜
      2024-05-18
      【JUCE】DTMプラグインを作ってみる 〜ディストーション編〜【VST/AU】
      2024-03-22
      【Unity】第二回 シェーダーライティング入門 〜テクスチャマップを使用したライティング〜(法線マップ、スペキュラマップ、AOマップ)【シェーダー】
      2023-03-14