#はじめに
PDFアセットとしてインポートできるようになった!!#UE4 pic.twitter.com/hjLXLuAeQ8
— Naotsun (@Naotsun_UE) January 11, 2020
最近、以前作成したPDFを読み込むプラグインを改良していてUE4のエディタにPDFアセットとしてインポートできる機能を作っていました。
正常にインポートはできますが、PDFアセットの中の各ページのテクスチャがエディタを再起動した際になくなってしまう状態で、読み込んだテクスチャがアセットになっていなかったことが原因でした。
そこでUnrealC++からテクスチャアセットを作る方法を調べたのでご紹介します。
#つくるもの
タイトルにある通り、画像ファイルからテクスチャアセットを作成するノードを作ります。引数に画像のファイルパス、戻り値にロードしたテクスチャアセットと成功の可否をを持ちます。
#つくってみる
まずは適当なファンクションライブラリを作成してヘッダーで以下のように関数を定義します。
UFUNCTION(BlueprintCallable, Category = "Test")
static bool LoadTextureAssetFromFile(const FString& FilePath, class UTexture2D* &LoadedTexture);
次に実装部分です。まずは以下のヘッダーをインクルードします。
#include "Public/MyBlueprintFunctionLibrary.h"
#include "Engine/Texture2D.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "Misc/PackageName.h"
#include "AssetRegistryModule.h"
#include "IImageWrapperModule.h"
#include "IImageWrapper.h"
Texture2D.h
、FileHelper.h
、IImageWrapperModule.h
、IImageWrapper.h
は画像ファイルからテクスチャを作成する際に使用します。
PackageName.h
とAssetRegistryModule.h
はテクスチャをアセットとして保存する際に使用します。
Path.h
はファイルパスを扱うため、インクルードします。
関数の中身を見ていきましょう。
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
まず画像ファイルからテクスチャを作成するために、FModuleManager::LoadModuleChecked
関数でIImageWrapperModule
クラスのインスタンスを取得し、IImageWrapperModule::CreateImageWrapper
関数でIImageWrapper
クラスのインスタンスを取得します。
ここで読み込む画像ファイルの拡張子を指定します。今回はjpeg画像を読み込むのでEImageFormat::JPEG
とします。
TArray<uint8> RawFileData;
if (FFileHelper::LoadFileToArray(RawFileData, *FilePath))
{
const TArray<uint8>* UncompressedRawData = nullptr;
if (ImageWrapper.IsValid() &&
ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()) &&
ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedRawData)
)
{
次に画像ファイルのデータを読み込みます。FFileHelper::LoadFileToArray
関数を使用して画像のデータを読み込みます。テクスチャにするには圧縮されていないデータが必要なので、IImageWrapper::SetCompressed
関数で圧縮されたデータを指定し、IImageWrapper::GetRaw
関数で圧縮されていないデータを取得します。
FString Filename = FPaths::GetBaseFilename(FilePath);
int Width = ImageWrapper->GetWidth();
int Height = ImageWrapper->GetHeight();
上で説明した箇所のif文の中で、アセット名、画像の縦横のサイズを変数にしておきます。
FString PackagePath = TEXT("/Game/LoadedTexture/");
FString AbsolutePackagePath = FPaths::ProjectContentDir() + TEXT("/LoadedTexture/");
FPackageName::RegisterMountPoint(PackagePath, AbsolutePackagePath);
PackagePath += Filename;
UPackage* Package = CreatePackage(nullptr, *PackagePath);
Package->FullyLoad();
次にテクスチャアセットを保存するためにパッケージを作成します。まずは、パッケージの相対パスと絶対パスを定義し、FPackageName::RegisterMountPoint
関数で作成するテクスチャアセットの出力先を作ります。今回は[Content/LoadedTexture]に保存したいのでこのようになっています。
PackagePath
にテクスチャアセットの名前を足して、CreatePackage
関数でパッケージを作成します。
最後にUPackage::FullyLoad
関数で作成したパッケージをロードしておきます。
FName TextureName = MakeUniqueObjectName(Package, UTexture2D::StaticClass(), FName(*Filename));
UTexture2D* Texture = NewObject<UTexture2D>(Package, TextureName, RF_Public | RF_Standalone);
次にMakeUniqueObjectName
関数を用いて他のアセットと被らない名前を取得します。そして、先程作成したパッケージと名前を指定してテクスチャを作成します。
Texture->PlatformData = new FTexturePlatformData();
Texture->PlatformData->SizeX = Width;
Texture->PlatformData->SizeY = Height;
Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
Texture->NeverStream = false;
次に、画像のデータが格納されるFTexturePlatformData
を新しく作成し、テクスチャのサイズやMipMapの設定などを行います。
FTexture2DMipMap* Mip = new FTexture2DMipMap();
Texture->PlatformData->Mips.Add(Mip);
Mip->SizeX = Width;
Mip->SizeY = Height;
Mip->BulkData.Lock(LOCK_READ_WRITE);
uint8* TextureData = (uint8*)Mip->BulkData.Realloc(UncompressedRawData->Num());
FMemory::Memcpy(TextureData, UncompressedRawData->GetData(), UncompressedRawData->Num());
Mip->BulkData.Unlock();
次に、画像のピクセルデータが格納されるFTexture2DMipMap
を新しく作成し、こちらでもテクスチャのサイズを指定した後、BulkData.Lock
でリソースをロック->FMemory::Memcpy
関数でピクセルデータをコピー->BulkData.Unlock
でリソースのロックを解除の流れでテクスチャにデータを書き込みます。
Texture->AddToRoot();
Texture->Source.Init(Width, Height, 1, 1, ETextureSourceFormat::TSF_BGRA8, UncompressedRawData->GetData());
Texture->UpdateResource();
次にUTexture2D::UpdateResource
関数で今までの変更を適用するのですが、その前にFTextureSource::Init
関数を実行します。
これを忘れるとテクスチャアセットは作れますが、エディタを再起動した際にピクセルデータが失われます。
また、この時にもRGBのフォーマットが必要になりますが、IImageWrapper::GetRaw
関数でしたフォーマットと同じフォーマットを指定してください。これを間違えると色が反転したりします。
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(Texture);
LoadedTexture = Texture;
FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath, FPackageName::GetAssetPackageExtension());
return UPackage::SavePackage(Package, Texture, RF_Public | RF_Standalone, *PackageFilename, GError, nullptr, true, true, SAVE_NoError);
最後に作成したテクスチャをアセットとして保存します。
まず、FAssetRegistryModule::AssetCreated
関数で新たにアセットが作成されたことを通知します。
次に、FPackageName::LongPackageNameToFilename
関数でパッケージの拡張子付きにファイルパスを作り'UPackage::SavePackage'関数でテクスチャアセットのパッケージを保存します。
これで関数が出来上がりです。if文の関係上少しわかりにくくなってしまったので以下に全文を掲載します。
bool UMyBlueprintFunctionLibrary::LoadTextureAssetFromFile(const FString& FilePath, class UTexture2D* &LoadedTexture)
{
IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
// 画像データを読み込む
TArray<uint8> RawFileData;
if (FFileHelper::LoadFileToArray(RawFileData, *FilePath))
{
// 非圧縮の画像データを取得
const TArray<uint8>* UncompressedRawData = nullptr;
if (ImageWrapper.IsValid() &&
ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()) &&
ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, UncompressedRawData)
)
{
FString Filename = FPaths::GetBaseFilename(FilePath);
int Width = ImageWrapper->GetWidth();
int Height = ImageWrapper->GetHeight();
// パッケージを作成
FString PackagePath(TEXT("/Game/LoadedTexture/"));
FString AbsolutePackagePath = FPaths::ProjectContentDir() + TEXT("/LoadedTexture/");
FPackageName::RegisterMountPoint(PackagePath, AbsolutePackagePath);
PackagePath += Filename;
UPackage* Package = CreatePackage(nullptr, *PackagePath);
Package->FullyLoad();
// テクスチャを作成
FName TextureName = MakeUniqueObjectName(Package, UTexture2D::StaticClass(), FName(*Filename));
UTexture2D* Texture = NewObject<UTexture2D>(Package, TextureName, RF_Public | RF_Standalone);
// テクスチャの設定
Texture->PlatformData = new FTexturePlatformData();
Texture->PlatformData->SizeX = Width;
Texture->PlatformData->SizeY = Height;
Texture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
Texture->NeverStream = false;
// ピクセルデータをテクスチャに書き込む
FTexture2DMipMap* Mip = new FTexture2DMipMap();
Texture->PlatformData->Mips.Add(Mip);
Mip->SizeX = Width;
Mip->SizeY = Height;
Mip->BulkData.Lock(LOCK_READ_WRITE);
uint8* TextureData = (uint8*)Mip->BulkData.Realloc(UncompressedRawData->Num());
FMemory::Memcpy(TextureData, UncompressedRawData->GetData(), UncompressedRawData->Num());
Mip->BulkData.Unlock();
// テクスチャを更新
Texture->AddToRoot();
Texture->Source.Init(Width, Height, 1, 1, ETextureSourceFormat::TSF_BGRA8, UncompressedRawData->GetData());
Texture->UpdateResource();
// パッケージを保存
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(Texture);
LoadedTexture = Texture;
FString PackageFilename = FPackageName::LongPackageNameToFilename(PackagePath, FPackageName::GetAssetPackageExtension());
return UPackage::SavePackage(Package, Texture, RF_Public | RF_Standalone, *PackageFilename, GError, nullptr, true, true, SAVE_NoError);
}
}
return false;
}
#おわりに
画像ファイルからテクスチャを作るものについては、いくつかサンプルがありましたが、テクスチャアセットにするものがほとんどなく大変でした。特に、FTextureSource::Init
関数が無くてアセットはできるのにエディタを再起動するとデータだけ無くなる状態になりがちなので気を付けましょう。
内容的に画像が少なく文字が多めになってしまい退屈な記事になってしましたが、これからテクスチャアセットを作ろうとしている方のお役に立てたら嬉しいです。
この記事で紹介したプロジェクトは以下でダウンロードできます。
https://github.com/Naotsun19B/CreateTextureAsset