はかせのラボ

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

DirectX12 Lat式ミクさんを正常に描画出来た話

あいさつ

どうも、はかせです。
前回表示したモデルにテクスチャを貼り付けてみました。

概ね正常にできていたのですが、
顔の部分だけおかしくなっていました。

今日はそれを直した話です。

前回のやつ

まずは前回表示したものの確認です。
f:id:hakase0274:20191016171804p:plain

顎と鼻の部分が黒くなっています。
あと少しわかりづらいですが、
目の周り以外が白のダミーテクスチャになっています。

黒くなっている原因

Twitterで教えてもらったのですが、
何やら今回私が使っているLat式のモデルは
一部のサーフェイスが反転しているそうです。

サーフェイスが反転しているため裏面が見えていたというわけですね。
(私は今回特別な処理をしてなかったので裏面は黒)

なのでこれに対処していきます。

対処法

理屈としては黒い裏面が見えているのが問題なので
裏面を描画しなければいいだけです。

DirectX12的に言えば
RasterizerStateのCullModeを変えるだけです。

D3D12_GRAPHICS_PIPELINE_STATE_DESC pipelineStateDesc{};
//pipelineStateDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;←NONEでは両面描画されてしまう
pipelineStateDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;//←BACKだと裏面は描画されない

これで裏面の黒い部分は無くなりました。
ただこれでは何も描画されてない面が見えるだけ=不十分です。

何も描画されていないということは透明ということなので
透過処理をすることで裏になっている
本来表であるべき面が見えるようになります。

透過処理はDirectX11の時に何回も吐く思いをしながらやったので
ササっとやります。
気になる人はこの辺をご覧ください
hakase0274.hatenablog.com
hakase0274.hatenablog.com
hakase0274.hatenablog.com
hakase0274.hatenablog.com

では実際にやっていきます。
といってもさっきと同じく
今度はBlendStateの設定を弄るだけです。

//ブレンドステートの設定
pipelineStateDesc.BlendState.AlphaToCoverageEnable = TRUE;
pipelineStateDesc.BlendState.IndependentBlendEnable = FALSE;
for (int i = 0; i < _countof(pipelineStateDesc.BlendState.RenderTarget); ++i)
{
	//見やすくするため変数化
	auto rt= pipelineStateDesc.BlendState.RenderTarget[i];
	rt.BlendEnable = TRUE;
	rt.LogicOpEnable = FALSE;
	rt.SrcBlend = D3D12_BLEND_SRC_ALPHA;
	rt.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
	rt.BlendOp = D3D12_BLEND_OP_ADD;
	rt.SrcBlendAlpha = D3D12_BLEND_SRC_ALPHA;
	rt.DestBlendAlpha = D3D12_BLEND_INV_SRC_ALPHA;
	rt.BlendOpAlpha = D3D12_BLEND_OP_ADD;
	rt.LogicOp = D3D12_LOGIC_OP_NOOP;
	rt.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
}

さてこれで黒かった面は消え去り
きれいなミクさんのご尊顔を拝めるはず。
f:id:hakase0274:20191017170142p:plain

ミクさんの顔が血まみれになったぁぁぁ・・・
(赤いのは見やすくするためダミーテクスチャの色を変えたからです)

ただこれできっと黒い問題は解決したはず。
(ダミーを赤にしても黒かったのがなくなったから)

血まみれ問題原因

これは冒頭でも言ったダミーテクスチャが顔についてる問題ですね。
ぐだぐだ言ってもしょうがないんで結論から言うと、
顔の上にもう一個マテリアルが引っ付いてた
ってことでした。

どういうことかって言うと
PMDにしろPMXにしろMMDってのは
トゥーンレンダリングが前提になっています。
(トゥーンレンダリングはまた後日)

なので顔の部分に顔の影用のマテリアルが存在していました。
もちろんその影はトゥーンテクスチャを用いて
描画されるため普通のテクスチャは存在していません。

ですが今回私が作ったやつでは
テクスチャが割り当たっていないマテリアルには
ダミーテクスチャを当てるようにしています。

つまり今回の顔血まみれ事件は原因は
本来割り当てる必要が無かったマテリアルにまで
テクスチャを割り当ててしまったから

です。

なので顔のマテリアルにダミーを当てなきゃいいだけの話です。
ただ私がまだ初心者すぎるせいかうまいやり方が思いつきませんでした。
というかどのマテリアルが顔に当たってるかとかよくわかりません。
(mayaとかblenderなら見れるんだけどね)

なので今回は脳筋戦法で
ダミーを透明テクスチャにしましたw

成果

さて長々と語ってきましたが、
これで今度こそ準備完了です。

ミクさんのご尊顔を拝みましょう。
f:id:hakase0274:20191017172008p:plain

Lat式のミクさんはかわいいですね。
苦労したかいがあります。

あとがき

今回は前回のおかしかった描画を直した話でした。

正直サーフェイスが反転しているなんて
教えてもらわなきゃ気づけなかったかもしれません。

本当に教えていただきありがとうございました。
(名前は許可とってないのでここでは書きません)

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

ちなみに

蛇足ですが、
顔の前になんかマテリアルがあるってことを調べたやり方です。
何か特別変わったことをしたわけではなく
CullModeをFRONT(表面を描画しない)にしただけです。

CullModeをFRONTにした時の結果です。
f:id:hakase0274:20191017171612p:plain

黒問題はCullModeが違うので出てますが、
赤い部分は消え去っています。

つまり表面にダミーテクスチャが描画されている
マテリアルがあるということが見えました。

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に変更するまである)

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