专栏目录:
小IVan:专题概述及目录![图标](https://i-blog.csdnimg.cn/blog_migrate/d6ea8e09530a242b2ee5802be9b6a242.png)
本节将会介绍虚幻的贴图资源读取,写入,存出等操作。这也是做Instanceing动画,工具开发等的基础。
【1】向UTexture2D写入数据
![v2-cc282403dd7f66e100560ffc176fd9c9_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/2cbd8f77f831ae136e6db367d1aa54a2.jpeg)
我把蓝色写入UTexture2D
效果如下:
写入前
![v2-23bfbd1cc52084cc8e0bb35e8a952d37_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/c3cc453c32fa43508da1f104abb09c01.jpeg)
写入后
![v2-6682f467a15eb7eed47ade522b058f17_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/c746284052c61465630e3542a9edc747.jpeg)
【2】向FRHITexture2D写入数据
![v2-76542f0de0033e2fd3e572f72113229c_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/f95fe152cb7928e891e92e4043c76b0e.jpeg)
效果如下
![v2-cb29b84859b7e56fbbcac4610f57bf7a_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/3869c3c88e5f9df28af16c2880b132c4.jpeg)
【3】从FRHITexture2D读取数据
平时写代码的时候很少用到位操作
![v2-69d6a8ab6e4de94e0da2e1ac53559ac8_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/ae339a0a27e1f04034da00e3c1ea3739.jpeg)
【4】从UTexture2D转换为FTexture2D并且读取它然后写成文件到磁盘
void UTestShaderBlueprintLibrary::TextureWriting(UTexture2D* TextureToBeWrite, AActor* selfref)
{
check(IsInGameThread());
if (selfref == nullptr && TextureToBeWrite == nullptr)return;
//TextureToBeWrite->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
//TextureToBeWrite->SRGB = 0;
//FTexture2DMipMap& mipmap = TextureToBeWrite->PlatformData->Mips[0];
//void* Data = mipmap.BulkData.Lock(LOCK_READ_WRITE);
//int32 texturex = TextureToBeWrite->PlatformData->SizeX;
//int32 texturey = TextureToBeWrite->PlatformData->SizeY;
//TArray<FColor>colors;
//for (int32 x = 0; x < texturex * texturey; x++)
//{
// colors.Add(FColor::Blue);
//}
//int32 stride = (int32)(sizeof(uint8) * 4);
//FMemory::Memcpy(Data, colors.GetData(), texturex * texturey * stride);
//mipmap.BulkData.Unlock();
//TextureToBeWrite->UpdateResource();
/*
struct FUpdateTextureContext
{
uint8* SourceBuffer;
uint32 BufferPitch;
FTexture2DResource* DestTextureResource;
};
FUpdateTextureContext UpdateTextureContext = {
uint8*()
}
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
UpdateDynamicTexture,
FUpdateTexture
);
*/
UWorld* World = selfref->GetWorld();
ERHIFeatureLevel::Type FeatureLevel = World->Scene->GetFeatureLevel();
ENQUEUE_RENDER_COMMAND(CaptureCommand)(
[FeatureLevel, TextureToBeWrite](FRHICommandListImmediate& RHICmdList)
{
TextureWriting_RenderThread
(
RHICmdList,
FeatureLevel,
TextureToBeWrite
);
}
);
}
static void TextureWriting_RenderThread(
FRHICommandListImmediate& RHICmdList,
ERHIFeatureLevel::Type FeatureLevel,
UTexture2D* Texture
)
{
check(IsInRenderingThread());
if (Texture == nullptr)
{
return;
}
FTextureReferenceRHIRef MyTextureRHI = Texture->TextureReference.TextureReferenceRHI;
FRHITexture* TexRef = MyTextureRHI->GetTextureReference()->GetReferencedTexture();
FRHITexture2D* TexRef2D = (FRHITexture2D*)TexRef;
TArray<FColor> Bitmap;
uint32 LolStride = 0;
char* TextureDataPtr = (char*)RHICmdList.LockTexture2D(TexRef2D, 0, EResourceLockMode::RLM_ReadOnly, LolStride, false);
for (uint32 Row = 0; Row < TexRef2D->GetSizeY(); ++Row)
{
uint32* PixelPtr = (uint32*)TextureDataPtr;
for (uint32 Col = 0; Col < TexRef2D->GetSizeX(); ++Col)
{
uint32 EncodedPixel = *PixelPtr;
uint8 r = (EncodedPixel & 0x000000FF);
uint8 g = (EncodedPixel & 0x0000FF00) >> 8;
uint8 b = (EncodedPixel & 0x00FF0000) >> 16;
uint8 a = (EncodedPixel & 0xFF000000) >> 24;
FColor col = FColor(r, g, b, a);
Bitmap.Add(FColor(b, g, r, a));
PixelPtr++;
}
// move to next row:
TextureDataPtr += LolStride;
}
RHICmdList.UnlockTexture2D(TexRef2D, 0, false);
if (Bitmap.Num())
{
IFileManager::Get().MakeDirectory(*FPaths::ScreenShotDir(), true);
const FString ScreenFileName(FPaths::ScreenShotDir() / TEXT("VisualizeTexture"));
uint32 ExtendXWithMSAA = Bitmap.Num() / Texture->GetSizeY();
// Save the contents of the array to a bitmap file. (24bit only so alpha channel is dropped)
FFileHelper::CreateBitmap(*ScreenFileName, ExtendXWithMSAA, Texture->GetSizeY(), Bitmap.GetData());
UE_LOG(LogConsoleResponse, Display, TEXT("Content was saved to \"%s\""), *FPaths::ScreenShotDir());
}
else
{
UE_LOG(LogConsoleResponse, Error, TEXT("Failed to save BMP, format or texture type is not supported"));
}
}
这个转换比较麻烦,需要各种取资源和强制转换
![v2-c7522d8187844b55a943407be2ec9e9a_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/97b225c90ad567f60e6469e803dd6be4.png)
最后注意一下UTexture2D的设置
![v2-230d0d7b8842c6ad1a6a37f90db9218f_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/b1c0fc4760ff9ca3ef48ec2793f4ee0f.jpeg)
因为UTexture源资源格式的原因,所以rgba顺序是反的
![v2-92e24d23d9edd69a5f0920e6627ade32_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/9c355c7cf2318e69065cfd4edce7a786.jpeg)
最后的效果:
![v2-33e29d84422c3e6a2029e5bcc4ca8fbd_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/d6039442b4147b62ee5ebda6df6f62b6.jpeg)
这样我们就完成了资源的转换,读取,写出等操作。
【5】从FTexture2D反向把数组向UTexture2D写入
先来看效果吧:
![v2-fa2067c6c80ee3c552df32db7b39580f_b.gif](https://i-blog.csdnimg.cn/blog_migrate/76c327ac6dec593f369c6827207a595a.gif)
这里我向UTexture2D中反复写入数组颜色和重置它。
下面是完整代码
void UTestShaderBlueprintLibrary::TextureWriting(UTexture2D* TextureToBeWrite, AActor* selfref)
{
check(IsInGameThread());
if (selfref == nullptr && TextureToBeWrite == nullptr)return;
//TextureToBeWrite->CompressionSettings = TextureCompressionSettings::TC_VectorDisplacementmap;
//TextureToBeWrite->SRGB = 0;
//FTexture2DMipMap& mipmap = TextureToBeWrite->PlatformData->Mips[0];
//void* Data = mipmap.BulkData.Lock(LOCK_READ_WRITE);
//int32 texturex = TextureToBeWrite->PlatformData->SizeX;
//int32 texturey = TextureToBeWrite->PlatformData->SizeY;
//TArray<FColor>colors;
//for (int32 x = 0; x < texturex * texturey; x++)
//{
// colors.Add(FColor::Blue);
//}
//int32 stride = (int32)(sizeof(uint8) * 4);
//FMemory::Memcpy(Data, colors.GetData(), texturex * texturey * stride);
//mipmap.BulkData.Unlock();
//TextureToBeWrite->UpdateResource();
/*
struct FUpdateTextureContext
{
uint8* SourceBuffer;
uint32 BufferPitch;
FTexture2DResource* DestTextureResource;
};
FUpdateTextureContext UpdateTextureContext = {
uint8*()
}
ENQUEUE_UNIQUE_RENDER_COMMAND_ONEPARAMETER(
UpdateDynamicTexture,
FUpdateTexture
);
*/
UWorld* World = selfref->GetWorld();
ERHIFeatureLevel::Type FeatureLevel = World->Scene->GetFeatureLevel();
ENQUEUE_RENDER_COMMAND(CaptureCommand)(
[FeatureLevel, TextureToBeWrite](FRHICommandListImmediate& RHICmdList)
{
TextureWriting_RenderThread
(
RHICmdList,
FeatureLevel,
TextureToBeWrite
);
}
);
}
static void TextureWriting_RenderThread(
FRHICommandListImmediate& RHICmdList,
ERHIFeatureLevel::Type FeatureLevel,
UTexture2D* Texture
)
{
check(IsInRenderingThread());
if (Texture == nullptr)
{
return;
}
FTextureReferenceRHIRef MyTextureRHI = Texture->TextureReference.TextureReferenceRHI;
FRHITexture* TexRef = MyTextureRHI->GetTextureReference()->GetReferencedTexture();
FRHITexture2D* TexRef2D = (FRHITexture2D*)TexRef;
TArray<FColor> Bitmap;
//TArray<uint32> sourcedata;
//-----------------------------------
uint32 LolStride = 0;
char* TextureDataPtr = (char*)RHICmdList.LockTexture2D(TexRef2D, 0, EResourceLockMode::RLM_WriteOnly, LolStride, false);
for (uint32 Row = 0; Row < TexRef2D->GetSizeY(); ++Row)
{
uint32* PixelPtr = (uint32*)TextureDataPtr;
for (uint32 Col = 0; Col < TexRef2D->GetSizeX(); ++Col)
{
uint32 EncodedPixel = *PixelPtr;
uint8 r = 255;
uint8 g = 0;
uint8 b = 0;
uint8 a = 255;
*PixelPtr = r | (g << 8) | (b << 16) | (a << 24);
//sourcedata.Add(*PixelPtr);
PixelPtr++;
}
// move to next row:
TextureDataPtr += LolStride;
}
RHICmdList.UnlockTexture2D(TexRef2D, 0, false);
//FUpdateTextureRegion2D region = FUpdateTextureRegion2D(0, 0, 0, 0, TexRef2D->GetSizeX(), TexRef2D->GetSizeY());
//RHIUpdateTexture2D(TexRef2D, 0, region, sizeof(uint32) * TexRef2D->GetSizeX() * TexRef2D->GetSizeY(), (uint8*)sourcedata.GetData());
//-----------------------------------
//Bitmap.Reset();
TextureDataPtr = (char*)RHICmdList.LockTexture2D(TexRef2D, 0, EResourceLockMode::RLM_ReadOnly, LolStride, false);
for (uint32 Row = 0; Row < TexRef2D->GetSizeY(); ++Row)
{
uint32* PixelPtr = (uint32*)TextureDataPtr;
for (uint32 Col = 0; Col < TexRef2D->GetSizeX(); ++Col)
{
uint32 EncodedPixel = *PixelPtr;
uint8 r = (EncodedPixel & 0x000000FF);
uint8 g = (EncodedPixel & 0x0000FF00) >> 8;
uint8 b = (EncodedPixel & 0x00FF0000) >> 16;
uint8 a = (EncodedPixel & 0xFF000000) >> 24;
FColor col = FColor(r, g, b, a);
Bitmap.Add(FColor(b, g, r, a));
PixelPtr++;
}
// move to next row:
TextureDataPtr += LolStride;
}
RHICmdList.UnlockTexture2D(TexRef2D, 0, false);
if (Bitmap.Num())
{
IFileManager::Get().MakeDirectory(*FPaths::ScreenShotDir(), true);
const FString ScreenFileName(FPaths::ScreenShotDir() / TEXT("VisualizeTexture"));
uint32 ExtendXWithMSAA = Bitmap.Num() / Texture->GetSizeY();
// Save the contents of the array to a bitmap file. (24bit only so alpha channel is dropped)
FFileHelper::CreateBitmap(*ScreenFileName, ExtendXWithMSAA, Texture->GetSizeY(), Bitmap.GetData());
UE_LOG(LogConsoleResponse, Display, TEXT("Content was saved to \"%s\""), *FPaths::ScreenShotDir());
}
else
{
UE_LOG(LogConsoleResponse, Error, TEXT("Failed to save BMP, format or texture type is not supported"));
}
}
【6】贴图格式转换给图片的数据带来的损失
我们各种类型强制转换当然会给数据带来精度损失,下面我们就来测试一下这个损失如何步骤如下:
(1)先把要处理的图片读进GPU然后用CS处理
(2)把CS输出到磁盘,采用BMP格式
![v2-c03ac995b38f61fd8d16c8d83ec2a277_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/df4f2353fa9337a2792d325aa97c5c85.png)
上面是我CS的原始输出数据
输出到BMP到磁盘后
![v2-3140dc30931b2fb8accb8357c41a0db4_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/3b238fc4bb4ac47e33bf321d319e23a0.jpeg)
然后我再把它转换成tga格式
![v2-39a8426c199054c9ef8cdcd5c3227a8a_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/c4e8ea1abb432d306f92cb862341ef9d.jpeg)
RGB三通道的值都还是正确的,然而alpha的值却出现了错误
![v2-0b18ccb7bf04199b1f7bbf77f9f957a6_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/3a42474ebd0b32f971ff141af3800b50.jpeg)
【7】将CS计算出的数据直接拷贝到RenderTarget中
当CS完成了计算,如何将CS的计算结果拿出来用呢。下面就开始做一个测试
先建一个RT格式大小要和CS的UAV大小格式保持一致
![v2-569603fee75385175c433cf0f5e9e01d_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/641aa7182e8df00c6abda4dd9fbe5a2a.jpeg)
然后把CS计算好的UAV的数据直接拷贝到RT中
![v2-416dea3f2b7bc8731837c02c3cdef6a0_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/db60a11ddd380f445259438ee3a3a482.jpeg)
4.20加入了一个新的接口帮我们写了很多代码,可以直接用CopyTexture
![v2-d6602482e08a2499f43d403526c44cad_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/7c652c0ac6b098d03ed31e62371bf640.jpeg)
拷贝之后:
![v2-8b8b32e4808ff791a1a887ca0674bc60_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/91daa13b636085020f5ebe75c3fb093d.jpeg)
![v2-d2e7a3426c2553a35b363f36f6b35339_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/6df63785e2b435e8ccaf63cc39edd2ad.jpeg)
在渲染线程做这些操作是非常轻松的。这样我们就能把CS的数据拿出来用了。需要注意的是,这些操作都要建立在像素数据类型,贴图尺寸大小必须完全对上的基础上才行的。那么问题来了,如果我的RT大小就是和ComputeShader的UAV大小不一致格式也不一致怎么办呢,当然方法是有了,我们可以把CS计算的结果当成一个Texture资源在传到绘制管线中,调用Draw把UAV画到一个新的RT上,不过这样感觉没必要了。
顺带这里说一个bug,如果在我们的shader里没写序列化的话会出现一些奇怪的无报错的bug。
![v2-e502f23be5639fd03adee5ec9d8eca63_b.jpg](https://i-blog.csdnimg.cn/blog_migrate/e579b687018e2abe7447e79fce876467.jpeg)
例如我上面代码的OutputSurface没有在Serialize中序列化,则会出现“第一次编译shader开引擎,能正常写入数据,等你第二次开引擎再向写入的时候会发现写入失败,RT中写入了一些异常值”。所以shader这里的序列化务必记得校对一下shader的变量是否都序列化了。
从此以后在虚幻中把贴图数据读取,把数组写入贴图,把贴图写到磁盘等一系列流程就完成啦!还有一部分资源操作相关的讲解我放到了工具篇里
YivanLee:虚幻4渲染编程(工具篇)【第八卷:Asset creation】![图标](https://i-blog.csdnimg.cn/blog_migrate/4b8bfa373498eba2f56aef4145a06a4a.png)
Enjoy it !