原文链接:(Ue4实用代码)UTexture2D的读取与写入数据并且保存成Asset
UTexture2D的读取与写入数据
在阅读 @YivanLee
的文章 —— YivanLee:虚幻4渲染编程(Shader篇)【第六卷:资源操作】
我发现 “向UTexture2D写入数据” 这节有些纰漏:
- 作者没有说明
Texture2D->PlatformData->Mips[0].BulkData.LockReadOnly()返回空指针的详细情况。 - 作者实现的写入是临时性的。
在查阅国外资料时,我在 isaratech 的网站里找到解决方案,在此我将进行简单说明。(原文地址在结尾会介绍)
从 UTexture2D 读取数据
读取方法如下:
const FColor* FormatedImageData = static_cast<const FColor*>( MyTexture2D->PlatformData->Mips[0].BulkData.LockReadOnly());
for(int32 X = 0; X < MyTexture2D->SizeX; X++)
{
for (int32 Y = 0; Y < MyTexture2D->SizeY; Y++)
{
FColor PixelColor = FormatedImageData[Y * MyTexture2D->SizeX + X];
// Do the job with the pixel
}
}
MyTexture2D->PlatformData->Mips[0].BulkData.Unlock();
但有的时候 LockReadOnly() 会返回空指针,这个时候你需要去检查 UTexture2D 的设置:
CompressionSettings是否为VectorDisplacementmap。MipGenSettings是否为NoMipmaps。sRGB是否为未勾选状态。
但通常情况下都 不可能同时 符合这 3 个条件,所以我们需要将 UTexture2D 的 3 个选项设置成上述这 3 个状态。在处理所需操作后再进行恢复。代码如下:
TextureCompressionSettings OldCompressionSettings = MyTexture2D->CompressionSettings;
TextureMipGenSettings OldMipGenSettings = MyTexture2D->MipGenSettings;
bool OldSRGB = MyTexture2D->SRGB;
MyTexture2D->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
MyTexture2D->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
MyTexture2D->SRGB = false;
MyTexture2D->UpdateResource();
const FColor* FormatedImageData = static_cast<const FColor*>( MyTexture2D->PlatformData->Mips[0].BulkData.LockReadOnly());
for(int32 X = 0; X < MyTexture2D->SizeX; X++)
{
for (int32 Y = 0; Y < MyTexture2D->SizeY; Y++)
{
FColor PixelColor = FormatedImageData[Y * MyTexture2D->SizeX + X];
//做若干操作
}
}
MyTexture2D->PlatformData->Mips[0].BulkData.Unlock();
Texture->CompressionSettings = OldCompressionSettings;
Texture->MipGenSettings = OldMipGenSettings;
Texture->SRGB = OldSRGB;
Texture->UpdateResource();
向 UTexture2D 写入数据
写入操作也是类似,不过我们需要调用若干 UPackage 与 FAssetRegistryModule 的代码。
下文中的 Asset 相关操作需要在包含头文件:
#include "AssetRegistryModule.h"
同时需要在项目模块 C# 文件的 PublicDependencyModuleNames.AddRange 中加入 AssetTools。
取得 Package 的指针
这里可以使用 CreatePackage 来创建新的包或是调用 FindPackage 来取得已有包的 UPackage 指针。
包这个概念来源于 UE3 与 UDK 时代,当时引擎所有的资源都存放在一个一个的包中,修改过资源后还需要选中并且点击保存包选项,才能真正保存,但是 UE4 淡化了这个概念。你可以把包理解为 Content 下各个文件夹。
//创建名为 PackageName 值的包
//PackageName 为 FString 类型
FString AssetPath = TEXT("/Game/")+ PackageName+ TEXT("/");
AssetPath += TextureName;
UPackage* Package = CreatePackage(NULL, *AssetPath);
Package->FullyLoad();
FindPackage 本人还没用过,不过用法应该差不多。
取得 UTexture2D 的指针
你可以创建一个新的 UTexture2D,也可以通过蓝图指定。
//创建
UTexture2D* NewTexture = NewObject<UTexture2D>(Package, *TextureName, RF_Public | RF_Standalone | RF_MarkAsRootSet);
NewTexture->AddToRoot();
NewTexture->PlatformData = new FTexturePlatformData();
NewTexture->PlatformData->SizeX = TextureWidth;
NewTexture->PlatformData->SizeY = TextureHeight;
NewTexture->PlatformData->NumSlices = 1;
//设置像素格式
NewTexture->PlatformData->PixelFormat = EPixelFormat::PF_B8G8R8A8;
写入数据
//创建一个 uint8 的数组并取得指针
//这里需要考虑之前设置的像素格式
uint8* Pixels = new uint8[TextureWidth * TextureHeight * 4];
for (int32 y = 0; y < TextureHeight; y++)
{
for (int32 x = 0; x < TextureWidth; x++)
{
int32 curPixelIndex = ((y * TextureWidth) + x);
//这里可以设置 4 个通道的值
//这里需要考虑像素格式,之前设置了 PF_B8G8R8A8,那么这里通道的顺序就是 BGRA
Pixels[4 * curPixelIndex] = 100;
Pixels[4 * curPixelIndex + 1] = 50;
Pixels[4 * curPixelIndex + 2] = 20;
Pixels[4 * curPixelIndex + 3] = 255;
}
}
//创建第一个 MipMap
FTexture2DMipMap* Mip = new FTexture2DMipMap();
NewTexture->PlatformData->Mip.Add(Mip);
Mip->SizeX = TextureWidth;
Mip->SizeY = TextureHeight;
//锁定 Texture 让它可以被修改
Mip->BulkData.Lock(LOCK_READ_WRITE);
uint8* TextureData = (uint8*)Mip->BulkData.Realloc(TextureWidth * TextureHeight * 4);
FMemory::Memcpy(TextureData, Pixels, sizeof(uint8) * TextureHeight * TextureWidth * 4);
Mip->BulkData.Unlock();
//通过以上步骤,我们完成数据的临时写入
//执行完以下这两个步骤,编辑器中的 asset 会显示可以保存的状态(如果是指定 Asset 来获取 UTexture2D 的指针的情况下)
NewTexture->Source.Init(TextureWidth, TextureHeight, 1, 1, ETextureSourceFormat::TSF_BGRA8, Pixels);
NewTexture->UpdateResource();
创建 Asset 并清理无用数据
Package->MarkPackageDirty();
FAssetRegistryModule::AssetCreated(NewTexture);
//通过 asset 路径获取包中文件名
FString PackageFileName = FPackageName::LongPackageNameToFilename(AssetPath, FPackageName::GetAssetPackageExtension());
//进行保存
bool bSaved = UPackage::SavePackage(Package, NewTexture, EObjectFlags::RF_Public | EObjectFlags::RF_Standalone, *PackageFileName, GError, nullptr, true, true, SAVE_NoError);
delete[] Pixels;
之后你就可以在编辑器中找到新生成的 Asset。如果你是向选中的 Asset 写入数据(执行到上一步),调用 UPackage::SavePackage 即可进行保存。
总结
本文包含以下知识:
UTexture2D的部分结构、属性以及读取设置方法。UPackage部分的部分操作。Asset的创建操作。
读者可以执行通过阅读 UPackage、FAssetRegistryModule 的代码学习 Asset 更多的操作。
个人推荐的 isaratech 网站文章
生成一个新的贴图 Asset 保存
UE4 – Save a procedurally generated texture as a new asset
生成一个新的带有节点的 MaterialAsset
UE4 – Programmatically create a new material and inner nodes