BlueRoseNote/03-UnrealEngine/Editor/UTexture2D的读取与写入数据并且保存成Asset.md
2023-06-29 11:55:02 +08:00

168 lines
6.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
title: UTexture2D的读取与写入数据并且保存成Asset
date: 2022-11-04 10:11:39
excerpt:
tags:
rating: ⭐
---
## UTexture2D的读取与写入数据
在阅读@YivanLee的文章
https://zhuanlan.zhihu.com/p/45171840
我发现“**向UTexture2D写入数据**”这节有些纰漏:
1. 作者没有说明Texture2D->PlatformData->Mips[0].BulkData.LockReadOnly()返回空指针的详细情况。
2. 作者实现的写入是临时性的。
在查阅国外资料时我在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的设置
1. CompressionSettings 是否为VectorDisplacementmap。
2. MipGenSettings 是否为NoMipmaps。
3. 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->Mips.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即可进行保存。
## 总结
本文包含以下知识:
1. UTexture2D的部分结构、属性以及读取设置方法。
2. UPackage部分的部分操作。
3. Asset的创建操作。
读者可以执行通过阅读UPackage、FAssetRegistryModule的代码学习Asset更多的Asset操作。
## 个人推荐的isaratech网站文章
网站
https://isaratech.com/
生成一个新的贴图Asset保存
https://isaratech.com/save-a-procedurally-generated-texture-as-a-new-asset/
生成一个新的带有节点的MaterialAsset
https://isaratech.com/ue4-programmatically-create-a-new-material-and-inner-nodes/