虚幻4渲染编程(Shader篇)【第六卷:资源操作】

专栏目录:

小IVan:专题概述及目录​zhuanlan.zhihu.com图标

本节将会介绍虚幻的贴图资源读取,写入,存出等操作。这也是做Instanceing动画,工具开发等的基础。


【1】向UTexture2D写入数据

v2-cc282403dd7f66e100560ffc176fd9c9_b.jpg

我把蓝色写入UTexture2D

效果如下:

写入前

v2-23bfbd1cc52084cc8e0bb35e8a952d37_b.jpg

写入后

v2-6682f467a15eb7eed47ade522b058f17_b.jpg

【2】向FRHITexture2D写入数据

v2-76542f0de0033e2fd3e572f72113229c_b.jpg

效果如下

v2-cb29b84859b7e56fbbcac4610f57bf7a_b.jpg

【3】从FRHITexture2D读取数据

平时写代码的时候很少用到位操作

v2-69d6a8ab6e4de94e0da2e1ac53559ac8_b.jpg

【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

最后注意一下UTexture2D的设置

v2-230d0d7b8842c6ad1a6a37f90db9218f_b.jpg

因为UTexture源资源格式的原因,所以rgba顺序是反的

v2-92e24d23d9edd69a5f0920e6627ade32_b.jpg

最后的效果:

v2-33e29d84422c3e6a2029e5bcc4ca8fbd_b.jpg

这样我们就完成了资源的转换,读取,写出等操作。


【5】从FTexture2D反向把数组向UTexture2D写入

先来看效果吧:

v2-fa2067c6c80ee3c552df32db7b39580f_b.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

上面是我CS的原始输出数据

输出到BMP到磁盘后

v2-3140dc30931b2fb8accb8357c41a0db4_b.jpg

然后我再把它转换成tga格式

v2-39a8426c199054c9ef8cdcd5c3227a8a_b.jpg

RGB三通道的值都还是正确的,然而alpha的值却出现了错误

v2-0b18ccb7bf04199b1f7bbf77f9f957a6_b.jpg

【7】将CS计算出的数据直接拷贝到RenderTarget中

当CS完成了计算,如何将CS的计算结果拿出来用呢。下面就开始做一个测试

先建一个RT格式大小要和CS的UAV大小格式保持一致

v2-569603fee75385175c433cf0f5e9e01d_b.jpg

然后把CS计算好的UAV的数据直接拷贝到RT中

v2-416dea3f2b7bc8731837c02c3cdef6a0_b.jpg

4.20加入了一个新的接口帮我们写了很多代码,可以直接用CopyTexture

v2-d6602482e08a2499f43d403526c44cad_b.jpg

拷贝之后:

v2-8b8b32e4808ff791a1a887ca0674bc60_b.jpg
v2-d2e7a3426c2553a35b363f36f6b35339_b.jpg

在渲染线程做这些操作是非常轻松的。这样我们就能把CS的数据拿出来用了。需要注意的是,这些操作都要建立在像素数据类型,贴图尺寸大小必须完全对上的基础上才行的。那么问题来了,如果我的RT大小就是和ComputeShader的UAV大小不一致格式也不一致怎么办呢,当然方法是有了,我们可以把CS计算的结果当成一个Texture资源在传到绘制管线中,调用Draw把UAV画到一个新的RT上,不过这样感觉没必要了。

顺带这里说一个bug,如果在我们的shader里没写序列化的话会出现一些奇怪的无报错的bug。

v2-e502f23be5639fd03adee5ec9d8eca63_b.jpg

例如我上面代码的OutputSurface没有在Serialize中序列化,则会出现“第一次编译shader开引擎,能正常写入数据,等你第二次开引擎再向写入的时候会发现写入失败,RT中写入了一些异常值”。所以shader这里的序列化务必记得校对一下shader的变量是否都序列化了。


从此以后在虚幻中把贴图数据读取,把数组写入贴图,把贴图写到磁盘等一系列流程就完成啦!还有一部分资源操作相关的讲解我放到了工具篇里

YivanLee:虚幻4渲染编程(工具篇)【第八卷:Asset creation】​zhuanlan.zhihu.com图标

Enjoy it !

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cpongo11

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值