#memo

indiedev太郎

HDRテクスチャの受け渡しについて

UE4からOpenCVにテクスチャを渡す時、RenderTargetのテクスチャがHDR画像だとおかしなことになるみたいなことを以前書いたけど、今考えたら単にデータ型が違うだけで、HDR画像をそのまま処理できたら黒飛び問題とかがマシになる
RenderTargetのテクスチャ設定でHDRのチェックを入れると、DetailsタブのFormatがB8G8R8A8からFloatRGBAになることがわかる

Unreal Engine | ERawImageFormat::Type

UE4が扱う画像フォーマットの形式一覧はこれで、テクスチャ生成した時のログを見ると、

LogTexture:Display: Building textures: T_TRender512_3 (RGBA16F)

と出ているので、正確にはRGBA16Fで出力されている、普通の32bitfloatならそのままmatに渡せそうなのにめんどくさい

UTexture2Dに対してHasHDRSource()メソッドを使用するとHDRにチェック入ってるかどうかが返ってくるので、処理をわけれそう

今の実装は、
Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_ONLY)
をFColorにキャストして、R,G,Bを見ているのだけれど、FColorのR,G,Bはuint8なので、このままだとRGBが丸められてしまう

Unreal Engine | FFloat16Color
Unreal Engine | FLinearColor

どうしようと思ったらちゃんと構造体が用意されているので、FFloat16Colorを使用してみる
FFloat16からfloatへの変換は

Unreal Engine | FFloat16

GetFloat()でいけそうなのでどんな感じか確認していく

FFloat16Color* Data = static_cast<FFloat16Color*>(Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_ONLY));

これでRGBA16FのテクスチャデータがFFloat16Colorになる、各要素へのアクセスは

SrcData[i].B.GetFloat()
SrcData[i].G.GetFloat()
SrcData[i].R.GetFloat()

これで見れる、数字はuint8のものから1/255で返ってきているので、そのままMat(CV_32FC3)に代入すれば計算で使えるんじゃないでしょうか

Mat HDRTexture2Mat(UTexture2D* Src) {
	// テクスチャサイズ取得
	Size size(Src->PlatformData->Mips[0].SizeX, Src->PlatformData->Mips[0].SizeY);
	// Mat作成
	Mat img(size, CV_32FC3);
	int ch = img.channels();

	FFloat16Color* SrcData = static_cast<FFloat16Color*>(Src->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_ONLY));

	for (int y = 0, i = 0; y < size.height; y++)
	{
		float* ptr = img.ptr<float>(y);
		for (int x = 0; x < size.width; x++, i++)
		{
			ptr[x * ch + 0] = SrcData[i].B.GetFloat();
			ptr[x * ch + 1] = SrcData[i].G.GetFloat();
			ptr[x * ch + 2] = SrcData[i].R.GetFloat();
		}
	}

	Src->PlatformData->Mips[0].BulkData.Unlock();

	return img;

}

これで動いたけど、OpenCV側のデータアクセス方法が若干闇い、HDRじゃない方はキャストとloop中の処理をちょっと変える

Mat Texture2Mat(UTexture2D* Src) {
	// テクスチャサイズ取得
	Size size(Src->PlatformData->Mips[0].SizeX, Src->PlatformData->Mips[0].SizeY);
	// Mat作成
	Mat img(size, CV_8UC3);
	int ch = img.channels();

	FColor* SrcData = static_cast<FColor*>(Src->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_ONLY));

	for (int y = 0, i = 0; y < size.height; y++)
	{
		uchar* ptr = img.ptr<uchar>(y);
		for (int x = 0; x < size.width; x++, i++)
		{
			ptr[x * ch + 0] = SrcData[i].B;
			ptr[x * ch + 1] = SrcData[i].G;
			ptr[x * ch + 2] = SrcData[i].R;
		}
	}

	Src->PlatformData->Mips[0].BulkData.Unlock();

	img.convertTo(img, CV_32FC3, 1.0f / 255);
	return img;

}

で、ここまで作ってフィルターとかかけてもトーンジャンプとか酷いので、あれと思って直接データ値を比較したら、全くHDRのデータが来ていない
RenderTargetのCaptureSourceでHDRかポストプロセス付きのLDRかを指定するところがあって、ここもHDRに変更するとちゃんと0-1範囲外のデータも来るようになった

いや、ポストプロセス付きのHDRデータがほしいんだけど無理なんスかね・・・・・・・・・・・・・(死)