はかせのラボ

私の頭の中を書いていく雑記ブログです

DirectX12 MMDモデルにテクスチャを貼りたい

あいさつ

どうも、はかせです。
前回はメモリリークを解消しながら
ComPtrの動きについて学びました。

今回は前に上げた白塗りのモデルに
テクスチャマッピングを行っていきたいと思います。
ちなみに白塗りモデルの話はこちら
hakase0274.hatenablog.com

モデルのテクスチャマッピングに必要なもの

まずはモデルにテクスチャマッピングするにあたり
何が必要なのかです。

調べた感じ以下の二つがあれば
なんとかなりそうです。
・モデルのマテリアルデータ
・マテリアルに紐づくテクスチャ

マテリアルデータってのは
AlphaとかDiffuseとかのことです。
今回はとりあえずテクスチャ貼ることが目的なんで
そんな使ってないです。
Alphaぐらいですかね。

マテリアルに紐づくテクスチャはもろ
テクスチャマッピングで貼るテクスチャです。
これがなければ話になりません。

ではそれぞれやっていきます。

モデルのマテリアルデータ

これはぶっちゃけPMDファイル読み込みの時に
既に読み込んで持っています。

あとはこいつをシェーダーの定数バッファにポイしてやります。

HRESULT D3D12Manager::CreateMaterialBuffer()
{
	D3D12_HEAP_PROPERTIES materialHeapProperties
	{
		D3D12_HEAP_TYPE_UPLOAD,
		D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
		D3D12_MEMORY_POOL_UNKNOWN,
		1,
		1
	};
	D3D12_RESOURCE_DESC materialResourceDesc
	{
		D3D12_RESOURCE_DIMENSION_BUFFER,
		0,
		sizeof(ResMaterialBuffer) * mPMDLoader.Materials.size(),
		1,
		1,
		1,
		DXGI_FORMAT_UNKNOWN,
		{ 1, 0 },
		D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
		D3D12_RESOURCE_FLAG_NONE
	};
	auto hr = mDevice->CreateCommittedResource(
		&materialHeapProperties,
		D3D12_HEAP_FLAG_NONE,
		&materialResourceDesc,
		D3D12_RESOURCE_STATE_GENERIC_READ,
		nullptr,
		IID_PPV_ARGS(mPMDMaterialBuffer.GetAddressOf()));
	if (FAILED(hr)) throw std::exception();

	unsigned char* dst = nullptr;

	mPMDMaterialBuffer->Map(0, nullptr, reinterpret_cast<void**>(&dst));

	unsigned int materialSize = static_cast<unsigned int>(sizeof(ResMaterialBuffer));

	// 定数バッファビューの設定.
	D3D12_CONSTANT_BUFFER_VIEW_DESC materialBufferDesc = {};
	materialBufferDesc.BufferLocation = mPMDMaterialBuffer->GetGPUVirtualAddress();
	materialBufferDesc.SizeInBytes = materialSize;

	unsigned int offset = 0;
	for (size_t i = 0; i < mPMDLoader.Materials.size(); ++i)
	{
		memcpy(dst + offset, &mPMDLoader.Materials[i], materialSize);
		auto handle = mPMDDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
		handle.ptr += mPMDDescriptorHeapIncrimentSize * unsigned int(i);
		mDevice->CreateConstantBufferView(&materialBufferDesc, handle);
		materialBufferDesc.BufferLocation += materialSize;
		offset += materialSize;
	}

	mPMDMaterialBuffer->SetName(L"MaterialBuffer");

	return S_OK;
}

ほとんどネット上に落ちてるコンスタントバッファのコピペです。
違う点としてはマテリアル分だけループさせて
都度ポインタをインクリメントしてるくらいですかね。

モデルに紐づくテクスチャ

これはマテリアルが対応するテクスチャの名前を持っています。
なのでその名前のテクスチャを読み込んでShaderResourceViewとして
デスクリプタにポイします。

HRESULT D3D12Manager::CreateTextureAndShaderResouceView()
{
	//マテリアルバッファ分ずらす
	auto offset = 1 + static_cast<int>(mPMDLoader.Materials.size());
	//テクスチャ配列の要素を確保しておく
	mTextureList.resize(mPMDLoader.mTextureNameList.size());
	//COMを使うためにスレッドにアパートメントを設定する
	ThrowFailed(CoInitialize(NULL));

	for (int i = 0; i < mPMDLoader.mTextureNameList.size(); i++) 
	{
		//WICを使ってテクスチャを読み込む
		std::unique_ptr<uint8_t[]> wicData;
		D3D12_SUBRESOURCE_DATA subresouceData;
		ThrowFailed(LoadWICTextureFromFile(
			mDevice.Get(),
			mPMDLoader.mTextureNameList[i].c_str(),
			mTextureList[i].GetAddressOf(),
			wicData,
			subresouceData));

		D3D12_RESOURCE_DESC textureDesc = mTextureList[i]->GetDesc();
		const UINT64 uploadBufferSize = GetRequiredIntermediateSize(mTextureList[i].Get(), 0, 1);

		//シェーダーリソースビューを作る
		D3D12_HEAP_PROPERTIES props = 
		{
			D3D12_HEAP_TYPE_DEFAULT,
			D3D12_CPU_PAGE_PROPERTY_UNKNOWN,
			D3D12_MEMORY_POOL_UNKNOWN,
			1,
			1
		};

		mDevice->CreateCommittedResource(
			&props,
			D3D12_HEAP_FLAG_NONE,
			&textureDesc,
			D3D12_RESOURCE_STATE_COMMON,
			nullptr,
			IID_PPV_ARGS(&mTextureList[i]));

		auto mipLevel = (textureDesc.MipLevels == 0) ? -1 : textureDesc.MipLevels;

		D3D12_SHADER_RESOURCE_VIEW_DESC viewDesc = {};
		viewDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
		viewDesc.Format = textureDesc.Format;
		viewDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
		viewDesc.Texture2D.MipLevels = mipLevel;
		viewDesc.Texture2D.MostDetailedMip = 0;

		//マテリアルバッファ分ポインタをずらしておく
		auto handle = mPMDDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
		handle.ptr += mPMDDescriptorHeapIncrimentSize * (offset + i);
		mDevice->CreateShaderResourceView(mTextureList[i].Get(), &viewDesc, handle);

		//GPU転送用バッファを作る
		ComPtr<ID3D12Resource> textureUploadHeap;
		ThrowFailed(mDevice->CreateCommittedResource(
			&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
			D3D12_HEAP_FLAG_NONE,
			&CD3DX12_RESOURCE_DESC::Buffer(uploadBufferSize),
			D3D12_RESOURCE_STATE_GENERIC_READ,
			nullptr,
			IID_PPV_ARGS(&textureUploadHeap)));
		textureUploadHeap->SetName(L"TextureUploadHeap");

		//テクスチャをGPUに転送する
		UpdateSubresources(mCommandList.Get(), mTextureList[i].Get(), textureUploadHeap.Get(), 0, 0, 1, &subresouceData);

		//リソースアクセスの同期を取る必要があることを知らせる
		mCommandList->ResourceBarrier(
			1,
			&CD3DX12_RESOURCE_BARRIER::Transition(
				mTextureList[i].Get(),
				D3D12_RESOURCE_STATE_COPY_DEST,
				D3D12_RESOURCE_STATE_GENERIC_READ));

		ThrowFailed(mCommandList->Close());

		//GPUにテクスチャを転送
		ID3D12CommandList* ppCommandLists[] = { mCommandList.Get() };
		mCommandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);

		//完了待ち
		ThrowFailed(WaitForPreviousFrame());

		//次のために各種リセット
		ThrowFailed(mCommandAllocator->Reset());
		ThrowFailed(mCommandList->Reset(mCommandAllocator.Get(), mPipelineState.Get()));

		//デバッグ用に名前を付けておく
		mTextureList[i]->SetName(mPMDLoader.mTextureNameList[i].c_str());
	}

	mRenderTargetViewIndex = mSwapChain->GetCurrentBackBufferIndex();

	CoUninitialize();

	return S_OK;
}

テクスチャ読み込みは以前やったことがあるので
そこまで苦労はしませんでした。
ちなみにそれはこちら
hakase0274.hatenablog.com

違う点としては
・複数テクスチャを読み込むためループで読み込み
・事前にマテリアルがデスクリプタに入ってるためその分だけポインタずらす

こんなところですかね。

こういう管理を直接やらなきゃいけないのは
はっきり言ってめんどくさいですね。
それ用のクラスでも作りましょうかねぇ・・・・

実行してみる

さて必要と思われることは一通りやりました。
では実行してみましょう。
f:id:hakase0274:20191016171804p:plain
できてるっぽいけどできてない・・・??

基本的には問題なさそうですが、
顔の辺りがなんか変・・・・・・

あとがき

今回はテクスチャマッピングでした。

なぜ顔がおかしくなったかは現在調査中です。
マテリアルとかの当て方にミスがあるのか
シェーダー地雷を踏みぬいてしまったのか・・・

一応他のモデルとかでも起こるんかなと思って
他のモデル探したんですが、
PMDモデルってもう全然ないですね。
ほとんどPMX。
まぁPMDのほぼ上位互換フォーマットですし
仕方ないですかね。
(表示するのをPMXに変更するまである)

今回の記事良ければスターやコメント等よろしくお願いします。
それでは今回はこの辺でノシ