Loading...

【DirextX12】第二回 DirextX12を使ったゲーム開発 〜DirectXTexを用いたテクスチャ描画〜

C++
マイケル
マイケル
みなさんこんにちは!
マイケルです!
エレキベア
エレキベア
こんにちクマ〜〜〜
マイケル
マイケル
今日は前回から引き続き、DirectX12を触っていきます!
前回はポリゴン表示まで行ったので、続きとしてDirectXTexというライブラリを使用してテクスチャ描画するところまで進めていこうと思います。
エレキベア
エレキベア
ポリゴンにテクスチャを貼り付けるクマね
マイケル
マイケル
実装方法については公式のサンプルと下記の書籍を参考にさせていただきました!
この記事を読むことで、サンプルのコードの内容も大体理解できるようになると思います。

GitHub – microsoft / DirectX-Graphics-Samples (HelloTexture)
↑公式のサンプル

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

エレキベア
エレキベア
日本語の書籍はありがたいクマ〜〜〜
マイケル
マイケル
なお、今回作ったサンプルについてはGitHubに上げていますので、
全体の実装についてはこちらをご参照ください!

GitHub – masarito617/cpp-dx12-sample-texture
↑今回作ったサンプル

エレキベア
エレキベア
前回作ったのをベースに実装を加えたものクマね
スポンサーリンク

シェーダーにUV値を渡す

マイケル
マイケル
それではコードを書いていきますが、前回作ったポリゴン描画のコードを修正する形で進めていきます。
DirectX12の基本についても含めて、前回の記事をご参照下さい!
エレキベア
エレキベア
ポリゴン描画しただけの状態からテクスチャ描画まで進めていくクマね
マイケル
マイケル
テクスチャを描画する前に、まずはUV値を渡せるようシェーダー周りを修正していきましょう!

シェーダーの修正

マイケル
マイケル
頂点シェーダー、ピクセルシェーダーで受け渡すパラメータとして構造体を使用するため、hlsliファイルを追加します。
今回はBasicType.hlsliとして作成し、頂点座標とUV値をまとめたものを構造体として定義しました。
20221123 01
//頂点シェーダ→ピクセルシェーダへのやり取りに使用する
//構造体
struct BasicType {
	float4 position : SV_POSITION; // 頂点座標
	float2 uv : TEXCOORD;          // UV値
};
エレキベア
エレキベア
この情報をシェーダー間で受け渡しするクマね
マイケル
マイケル
頂点シェーダー、ピクセルシェーダーは下記のように修正します。
ピクセルシェーダーに関しては、一旦UV値をそのまま色として出力するようにしています。
#include"BasicType.hlsli"
BasicType BasicVS(float4 pos : POSITION, float2 uv : TEXCOORD)
{
	BasicType output;
	output.position = pos;
	output.uv = uv;
	return output;
}
#include"BasicType.hlsli"
float4 BasicPS(BasicType input) : SV_TARGET
{
	return float4(input.uv, 1.0f, 1.0f);
}
↑確認のためUV値を色としてそのまま出力
エレキベア
エレキベア
最終的に渡したUV値が色として出力されたら成功クマね

シェーダーにUV値を渡す

マイケル
マイケル
それでは実際にUV値を渡してみます。
C++側も頂点シェーダーへ渡す型定義としてVertex構造体を定義します。
・・・
	// リソース
	ComPtr<ID3D12Resource> vertexBuffer_;
	D3D12_VERTEX_BUFFER_VIEW vertexBufferView_;
	ComPtr<ID3D12Resource> indexBuffer_;
	D3D12_INDEX_BUFFER_VIEW indexBufferView_;

	struct Vertex
	{
		DirectX::XMFLOAT3 pos;
		DirectX::XMFLOAT2 uv;
	};
・・・

↑頂点シェーダーに渡す構造体を定義

マイケル
マイケル
そして頂点座標を渡している箇所にUV値も追加します。
元々XMFLOAT3型として渡していた箇所をVertex型へ変更するのも忘れないようにしましょう。
・・・
	// 頂点バッファビューの生成
	{
		// 頂点定義
		Vertex vertices[] = {
			{{-0.4f,-0.7f, 0.0f}, {0.0f, 1.0f}} , //左下
			{{-0.4f, 0.7f, 0.0f}, {0.0f, 0.0f}} , //左上
			{{ 0.4f,-0.7f, 0.0f}, {1.0f, 1.0f}} , //右下
			{{ 0.4f, 0.7f, 0.0f}, {1.0f, 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())));
		// 頂点情報のコピー
		Vertex* 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(Vertex);
	}
・・・

↑UV値を渡すよう修正

マイケル
マイケル
後は今回、構造体ファイルをシェーダーファイル内でincludeするようになったため、
D3D_COMPILE_STANDARD_FILE_INCLUDE フラグも指定するようにすれば完了です!
・・・
		// シェーダーオブジェクトの生成
#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, D3D_COMPILE_STANDARD_FILE_INCLUDE, "BasicVS", "vs_5_0", compileFlags, 0, &vsBlob, nullptr);
		D3DCompileFromFile(L"BasicPixelShader.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE, "BasicPS", "ps_5_0", compileFlags, 0, &psBlob, nullptr);
・・・

↑D3D_COMPILE_STANDARD_FILE_INCLUDEフラグを設定する

エレキベア
エレキベア
ここまでは簡単クマね

頂点レイアウトの修正

マイケル
マイケル
最後に、パイプラインステートに渡している頂点レイアウトにもUV値を追加しておきます。
TEXCOORDがUVを表すセマンティクスになります。
・・・
		// 頂点レイアウトの生成
		D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
		{
			{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, D3D12_APPEND_ALIGNED_ELEMENT, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
			{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
		};
・・・

↑頂点レイアウトにも追加

実行!

マイケル
マイケル
この状態で実行すると、下記のように左上が青(0,0,1,1)、右下が白(1,1,1,1)で表示されることが確認できるはずです!
20221123 02
エレキベア
エレキベア
これだけでも結構綺麗クマ〜〜〜

コードで書いたテクスチャを描画する

マイケル
マイケル
次に、コードで仮のテクスチャを定義して描画してみます。
前回軽く紹介した通り、テクスチャ等の情報をCPUとGPU間でやり取りする場合にはディスクリプタテーブルを使用してレジスタを関連付ける必要があります。
この辺りが少し複雑なので注意しましょう!

ダミーのテクスチャ

マイケル
マイケル
ダミーのテクスチャは下記にように定義しておきます。
格子状に白黒が描画されるテクスチャになります。
・・・
std::vector<UINT8> DXApplication::GenerateDummyTextureData()
{
	const UINT rowPitch = kDummyTextureWidth * kDummyTexturePixelSize;
	const UINT cellPitch = rowPitch >> 3;
	const UINT cellHeight = kDummyTextureWidth >> 3;
	const UINT textureSize = rowPitch * kDummyTextureHeight;

	std::vector<UINT8> data(textureSize);
	UINT8* pData = &data[0];

	for (UINT n = 0; n < textureSize; n += kDummyTexturePixelSize)
	{
		UINT x = n % rowPitch;
		UINT y = n / rowPitch;
		UINT i = x / cellPitch;
		UINT j = y / cellHeight;

		// Sample: 格子状
		if (i % 2 == j % 2)
		{
			pData[n] = 0x00;
			pData[n + 1] = 0x00;
			pData[n + 2] = 0x00;
			pData[n + 3] = 0xff;
		}
		else
		{
			pData[n] = 0xff;
			pData[n + 1] = 0xff;
			pData[n + 2] = 0xff;
			pData[n + 3] = 0xff;
		}
	}
・・・

↑格子状のテクスチャをコードで書いたもの

・・・
	// ダミーのテクスチャ情報
	static const UINT kDummyTextureWidth = 256;
	static const UINT kDummyTextureHeight = 256;
	static const UINT kDummyTexturePixelSize = 4;
	std::vector<UINT8> GenerateDummyTextureData();
・・・

↑ヘッダファイルの定義

マイケル
マイケル
これは公式サンプルのHelloTextureリポジトリ内にあるコードをそのまま拝借したものになります。
ロード処理は一旦後回しにしてこのコードをテクスチャと想定して描画してみましょう!

GitHub – microsoft / DirectX-Graphics-Samples (HelloTexture)
↑公式のサンプル

エレキベア
エレキベア
公式サンプルだと画像を読み込まずにコードで描画しているクマね

ピクセルシェーダーの修正

マイケル
マイケル
そしてUV値を出力していただけのピクセルシェーダーを下記のように修正します。
register(t0)、register(s0)等の部分は、登録したレジスターの0番目から情報を取得している処理になります。
#include"BasicType.hlsli"

Texture2D g_texture : register(t0);
SamplerState g_sampler : register(s0);

float4 BasicPS(BasicType input) : SV_TARGET
{
	return g_texture.Sample(g_sampler, input.uv);
}

↑レジスタからテクスチャ情報を取得して描画

エレキベア
エレキベア
これからこのレジスタにテクスチャの情報を登録して渡すクマね

ヘッダーファイルの修正

マイケル
マイケル
ヘッダーファイルには、テクスチャバッファーシェーダーリソースビュー(SRV)というディスクリプタヒープを追加します。
SRVはテクスチャ情報をまとめたビューで、シェーダーのレジスタとディスクリプタテーブルで紐づけることでシェーダー側から参照できるようにします。
・・・
	// パイプラインオブジェクト
	ComPtr<ID3D12Device> device_;
	ComPtr<ID3D12CommandAllocator> commandAllocator_;
	ComPtr<ID3D12GraphicsCommandList> commandList_;
	ComPtr<ID3D12CommandQueue> commandQueue_;
	ComPtr<IDXGISwapChain4> swapchain_;
	ComPtr<ID3D12DescriptorHeap> rtvHeap_;
	ComPtr<ID3D12DescriptorHeap> srvHeap_; // ★追加
	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<ID3D12Resource> textureBuffer_; // ★追加
・・・

↑テクスチャバッファーとSRVを定義

エレキベア
エレキベア
ややこしいクマ・・・・

ディスクリプタテーブルの生成

マイケル
マイケル
複雑になってきたので、改めて関連する用語についてまとめておきます。
ディスクリプタテーブルはディスクリプタヒープとシェーダーのレジスタを関連づけたもの、そしてディスクリプタテーブルをまとめるのがルートシグネチャになります。
用語概要
ディスクリプタGPUに送るリソースの仕様について示したもの。
ディスクリプタヒープディスクリプタをまとめたもの。
ディスクリプタテーブルルートシグネチャ内で設定し、ディスクリプタヒープとシェーダーのレジスタを関連付けたもの。
ルートシグネチャディスクリプタテーブルをまとめたもの。
マイケル
マイケル
前回は空のルートシグネチャを作成していましたが、今回はディスクリプタテーブルを作成してルートシグネチャに設定する必要があります。
ディスクリプタテーブルの実体であるルートパラメータとサンプラーを生成し、それらを元にルートシグネチャを生成します。
・・・
	// ルートシグネチャの生成
	{
		// ルートパラメータの生成
		// ディスクリプタテーブルの実体
		CD3DX12_DESCRIPTOR_RANGE1 discriptorRanges[1];
		discriptorRanges[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0, D3D12_DESCRIPTOR_RANGE_FLAG_DATA_STATIC);
		CD3DX12_ROOT_PARAMETER1 rootParameters[1];
		rootParameters[0].InitAsDescriptorTable(1, &discriptorRanges[0], D3D12_SHADER_VISIBILITY_PIXEL);
		// サンプラーの生成
		// テクスチャデータからどう色を取り出すかを決めるための設定
		D3D12_STATIC_SAMPLER_DESC sampler = {};
		sampler.Filter = D3D12_FILTER_MIN_MAG_MIP_POINT;
		sampler.AddressU = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.AddressV = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.AddressW = D3D12_TEXTURE_ADDRESS_MODE_BORDER;
		sampler.MipLODBias = 0;
		sampler.MaxAnisotropy = 0;
		sampler.ComparisonFunc = D3D12_COMPARISON_FUNC_NEVER;
		sampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
		sampler.MinLOD = 0.0f;
		sampler.MaxLOD = D3D12_FLOAT32_MAX;
		sampler.ShaderRegister = 0;
		sampler.RegisterSpace = 0;
		sampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
		// ルートパラメータ、サンプラーからルートシグネチャを生成
		CD3DX12_VERSIONED_ROOT_SIGNATURE_DESC rootSignatureDesc;
		rootSignatureDesc.Init_1_1(_countof(rootParameters), rootParameters, 1, &sampler, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
		ComPtr<ID3DBlob> rootSignatureBlob = nullptr;
		ComPtr<ID3DBlob> errorBlob = nullptr;
		ThrowIfFailed(D3DX12SerializeVersionedRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, &rootSignatureBlob, &errorBlob));
		ThrowIfFailed(device_->CreateRootSignature(0, rootSignatureBlob->GetBufferPointer(), rootSignatureBlob->GetBufferSize(), IID_PPV_ARGS(rootsignature_.ReleaseAndGetAddressOf())));
	}
・・・

↑ルートシグネチャの生成

エレキベア
エレキベア
あ、頭が痛いクマ・・・

テクスチャバッファ、シェーダーリソースビューの生成

マイケル
マイケル
次にテクスチャバッファ、シェーダーリソースビューの生成を行います。
まずはディスクリプタヒープの初期化処理にシェーダーリソースビューも追加しましょう。
・・・
	// ディスクリプタヒープの初期化
	{
		// レンダーターゲットビュー
		D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
		rtvHeapDesc.NumDescriptors = kFrameCount;
		rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
		rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
		ThrowIfFailed(device_->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(rtvHeap_.ReleaseAndGetAddressOf())));
		// シェーダーリソースビュー
		D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
		srvHeapDesc.NumDescriptors = 1;
		srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
		srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
		ThrowIfFailed(device_->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(srvHeap_.ReleaseAndGetAddressOf())));
	}
・・・

↑シェーダーリソースビューをディスクリプタヒープとして初期化

マイケル
マイケル
そして冒頭で載せたダミーのテクスチャを読み込み、それを元にテクスチャバッファ、シェーダーリソースビューを生成します。
・・・
	// TODO ここは後々実際にテクスチャを読み込むようにする
	// テクスチャのロード処理
	std::vector<UINT8> texture;
	D3D12_SUBRESOURCE_DATA textureData = {};
	{
		// ダミーテクスチャ読込
		texture = GenerateDummyTextureData();
		textureData.pData = &texture[0];
		textureData.RowPitch = kDummyTextureWidth * kDummyTexturePixelSize;
		textureData.SlicePitch = textureData.RowPitch * kDummyTextureHeight;
	}

	// シェーダーリソースビューの生成
	{
		// テクスチャバッファの生成
		D3D12_RESOURCE_DESC textureDesc = {};
		textureDesc.MipLevels = 1;
		textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		textureDesc.Width = kDummyTextureWidth;
		textureDesc.Height = kDummyTextureHeight;
		textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
		textureDesc.DepthOrArraySize = 1;
		textureDesc.SampleDesc.Count = 1;
		textureDesc.SampleDesc.Quality = 0;
		textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
		auto textureHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
		ThrowIfFailed(device_->CreateCommittedResource(
			&textureHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&textureDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(textureBuffer_.ReleaseAndGetAddressOf())));
		// シェーダーリソースビューの生成
		D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
		srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
		srvDesc.Format = textureDesc.Format;
		srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
		srvDesc.Texture2D.MipLevels = 1;
		device_->CreateShaderResourceView(textureBuffer_.Get(), &srvDesc, srvHeap_->GetCPUDescriptorHandleForHeapStart());
	}
・・・

↑テクスチャバッファ、シェーダーリソースビューの生成

エレキベア
エレキベア
これでテクスチャのデータ自体は出来たクマね

テクスチャバッファの転送処理

マイケル
マイケル
テクスチャデータの場合、パイプラインの外から設定されるデータのため、CPUからGPUへ転送を行う必要もあります。
アップロード用バッファの生成と、実際に転送を行う処理は下記になります。
・・・
	// テクスチャアップロード用バッファの生成
	ComPtr<ID3D12Resource> textureUploadBuffer;
	{
		const UINT64 textureBufferSize = GetRequiredIntermediateSize(textureBuffer_.Get(), 0, 1);
		auto textureUploadHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD);
		auto textureUploadDesc = CD3DX12_RESOURCE_DESC::Buffer(textureBufferSize);
		ThrowIfFailed(device_->CreateCommittedResource(
			&textureUploadHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&textureUploadDesc,
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&textureUploadBuffer)));
	}

	// コマンドリストの生成
	{
		// テクスチャバッファの転送
		UpdateSubresources(commandList_.Get(), textureBuffer_.Get(), textureUploadBuffer.Get(), 0, 0, 1, &textureData);
		auto uploadResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(textureBuffer_.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
		commandList_->ResourceBarrier(1, &uploadResourceBarrier);

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

	// コマンドリストの実行
	{
		ID3D12CommandList* commandLists[] = { commandList_.Get() };
		commandQueue_->ExecuteCommandLists(1, commandLists);
	}

	// GPU処理の終了を待機
	{
		ThrowIfFailed(commandQueue_->Signal(fence_.Get(), ++fenceValue_));
		if (fence_->GetCompletedValue() < fenceValue_) {
			ThrowIfFailed(fence_->SetEventOnCompletion(fenceValue_, fenceEvent_));
			WaitForSingleObject(fenceEvent_, INFINITE);
		}
	}
・・・
マイケル
マイケル
描画処理とは別に、ここでもコマンドリストに転送処理を格納して実行しています。
GPU側の処理になるため、フェンスでの待機処理を入れるのも忘れないようにしましょう!
エレキベア
エレキベア
これでようやくGPU側でテクスチャが使えるようになるクマね

描画処理の修正

マイケル
マイケル
最後に、描画処理でディスクリプタテーブルとシェーダーリソースビューを紐づければ完了です!!
・・・
		// パイプラインステートと必要なオブジェクトを設定
		commandList_->SetPipelineState(pipelinestate_.Get());         // パイプラインステート
		commandList_->SetGraphicsRootSignature(rootsignature_.Get()); // ルートシグネチャ
		commandList_->RSSetViewports(1, &viewport_);                  // ビューポート
		commandList_->RSSetScissorRects(1, &scissorrect_);            // シザー短形
		// ディスクリプタテーブル
		// ルートパラメータとディスクリプタヒープを紐づける
		ID3D12DescriptorHeap* ppHeaps[] = { srvHeap_.Get() };
		commandList_->SetDescriptorHeaps(_countof(ppHeaps), ppHeaps);
		commandList_->SetGraphicsRootDescriptorTable(0, srvHeap_->GetGPUDescriptorHandleForHeapStart());
・・・
エレキベア
エレキベア
長かったクマ〜〜〜〜

実行!

マイケル
マイケル
ここで実行して下記のように表示されれば成功です!
20221123 03↑格子状のテクスチャが描画される
マイケル
マイケル
ダミーのテクスチャ部分を適当に変えて模様を変えてみるのも面白いですね
・・・
		// Sapmle: グラデーション
		float rate = x / static_cast<float>(rowPitch);
		UINT dispColor = rate * 256;
		pData[n] = dispColor;
		pData[n + 1] = dispColor;
		pData[n + 2] = dispColor;
		pData[n + 3] = 0xff;
・・・
20221123 04↑グラデーション
・・・
		// Sample: ノイズ
		pData[n] = rand() % 256;
		pData[n + 1] = rand() % 256;
		pData[n + 2] = rand() % 256;
		pData[n + 3] = 255;
・・・
20221123 05↑ノイズ
エレキベア
エレキベア
コードで書いていると模様を変えるのも楽クマね

DirectXTexで読み込んだテクスチャを描画する

マイケル
マイケル
さあここまで来れば後一息!
DirectXTexというライブラリを使用して実際にテクスチャを読み込んで描画させてみます!
エレキベア
エレキベア
やっとクマ〜〜〜!!

DirectXTexの導入

マイケル
マイケル
DirectXTexはGitHubで公開されています。
こちらをローカル環境にクローンしましょう!

GitHub – microsoft/DirectXTex

git clone https://github.com/microsoft/DirectXTex.git
マイケル
マイケル
そして自身のVisualStudioのバージョンに合わせて DirectXTex_Desktop_XXX_Win10.sln となっているソリューションを開き、必要なプラットフォームでビルドします。
マイケル
マイケル
ビルドすると [workspace]\DirectXTex\DirectXTex\Bin\Desktop_2022_Win10\x64\Debug といったフォルダの配下にlibファイルが出力されるため、こちらをincludeして使用します。
エレキベア
エレキベア
使用するために一度手元でビルドする必要があるのクマね
マイケル
マイケル
そしてインクルードディレクトリ、ライブラリディレクトリにそれぞれ下記のパスを追加します。


・追加のインクルードディレクトリ
[workspace]\DirectXTex\DirectXTex

・追加のライブラリディレクトリ
[workspace]\DirectXTex\DirectXTex\Bin\Desktop_2022_Win10\x64\Debug
※x64、Debugビルドの場合

マイケル
マイケル
[workspace]\DirectXTex\DirectXTex までの部分は環境変数として定義しておくと綺麗になるのでおすすめです。
20221123 06↑DirectXTexフォルダを環境変数として定義
20221123 07↑インクルードディレクトリの追加
20221123 08↑ライブラリディレクトリの追加
マイケル
マイケル
後は下記のようにインクルードを追加してみて、実行してエラーが出ていなければ導入完了です!
・・・
#pragma once
#include <d3d12.h>
#include <dxgi1_6.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>
#include <DirectXTex.h> // ★追加

#include <wrl.h>
#include "d3dx12.h"

#include <stdexcept>
#include <vector>

#pragma comment(lib, "d3d12.lib")
#pragma comment(lib, "dxgi.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "DirectXTex.lib") // ★追加
・・・

↑インクルードの追加

エレキベア
エレキベア
簡単クマ〜〜〜〜

テクスチャのロード処理の追加

マイケル
マイケル
導入が完了したところで、今回は下記の適当に描いた画像をロードしてみます。
Test image↑適当に描いた絵(256×256)
エレキベア
エレキベア
(マジで適当クマ・・・)
マイケル
マイケル
ダミーのテクスチャを読み込んでいた箇所を下記のように修正します。
今回、画像は Assets/test_image.png というパスで格納しています。
・・・
	// テクスチャのロード処理
	DirectX::TexMetadata metadata = {};
	DirectX::ScratchImage scratchImg = {};
	std::vector<D3D12_SUBRESOURCE_DATA> textureSubresources;
	{
		ThrowIfFailed(LoadFromWICFile(L"Assets/test_image.png", DirectX::WIC_FLAGS_NONE, &metadata, scratchImg));
		ThrowIfFailed(PrepareUpload(device_.Get(), scratchImg.GetImages(), scratchImg.GetImageCount(), metadata, textureSubresources));
	}
・・・

↑実際に画像を読み込んでみる

マイケル
マイケル
そしてテクスチャバッファの生成、転送処理を読みこんだテクスチャに合わせて修正すれば完了です!
・・・
	// シェーダーリソースビューの生成
	{
		// テクスチャバッファの生成
		D3D12_RESOURCE_DESC textureDesc = {};
		textureDesc.MipLevels = 1;
		textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
		textureDesc.Width = static_cast<UINT>(metadata.width);   // ★修正
		textureDesc.Height = static_cast<UINT>(metadata.height); // ★修正
		textureDesc.Flags = D3D12_RESOURCE_FLAG_NONE;
		textureDesc.DepthOrArraySize = 1;
		textureDesc.SampleDesc.Count = 1;
		textureDesc.SampleDesc.Quality = 0;
		textureDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
		auto textureHeapProp = CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT);
		ThrowIfFailed(device_->CreateCommittedResource(
			&textureHeapProp,
			D3D12_HEAP_FLAG_NONE,
			&textureDesc,
			D3D12_RESOURCE_STATE_COPY_DEST,
			nullptr,
			IID_PPV_ARGS(textureBuffer_.ReleaseAndGetAddressOf())));
		// シェーダーリソースビューの生成
		D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
		srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
		srvDesc.Format = textureDesc.Format;
		srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
		srvDesc.Texture2D.MipLevels = 1;
		device_->CreateShaderResourceView(textureBuffer_.Get(), &srvDesc, srvHeap_->GetCPUDescriptorHandleForHeapStart());
	}

・・・

	// コマンドリストの生成
	{
		// テクスチャバッファの転送
		UpdateSubresources(commandList_.Get(), textureBuffer_.Get(), textureUploadBuffer.Get(), 0, 0, textureSubresources.size(), textureSubresources.data()); // ★修正
		auto uploadResourceBarrier = CD3DX12_RESOURCE_BARRIER::Transition(textureBuffer_.Get(), D3D12_RESOURCE_STATE_COPY_DEST, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
		commandList_->ResourceBarrier(1, &uploadResourceBarrier);

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

↑テクスチャバッファの生成、転送処理を一部修正

エレキベア
エレキベア
差し替えは楽だったクマね
マイケル
マイケル
一点注意点として、LoadFromWICFile処理ではCoInitializeExを呼び出していることを前提としているようで、下記のように事前に呼び出し処理を記述しておかないとE_NOINTERFACEエラーになるようでした。
今回はmain.cppの頭に追加しています。

参考:
COM are not initialized by using the initalizion code in DirectXTex.md – Issue – microsoft/DirectXTex

#include "Win32Application.h"
#include "DXApplication.h"

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int nCmdShow)
{
	// WICファイル読込のため、COMを初期化しておく
	auto result = CoInitializeEx(NULL, COINITBASE_MULTITHREADED);
	if (FAILED(result)) return 1;

	DXApplication dxApp(1280, 720, L"DX Sample");
	Win32Application::Run(&dxApp, hInstance);
	return 0;
}

↑CoInitializeExを追加

エレキベア
エレキベア
これは思わぬ罠クマね〜〜

実行!

マイケル
マイケル
ここまで実装したら実行!!
下記のように適当な画像が描画されれば成功です!!
20221123 10
エレキベア
エレキベア
やったクマ〜〜〜〜
くだらない画像を出すために大変だったクマ〜〜〜〜

おわりに

マイケル
マイケル
というわけで今回はDirectXTexを用いたテクスチャ描画でした!
どうだったかな??
エレキベア
エレキベア
テクスチャ一つ描画するだけで大変すぎるクマ・・・
Unity使いたいクマ・・・
マイケル
マイケル
慣れもあるのかもしれないけど、やっぱり生のDirectXとなると大変だね・・・
とりあえず3Dモデル描画までは頑張ってみよう!!
マイケル
マイケル
というわけで今回はこの辺で!!
アデューーー!!!
エレキベア
エレキベア
クマ〜〜〜〜〜

【DirextX12】第二回 DirextX12を使ったゲーム開発 〜DirectXTexを用いたテクスチャ描画〜 〜完〜

※次回の記事はこちら!

コメント