UEC++学习(十七)利用SceneCaptureComponent2d进行截图

       

         最近有个需求是需要将场景中的actor进行截图,并且将截图保存成png,png中需要将场景背景忽略掉,只显示特定的actor。

        这里是通过SceneCapture2d组件捕捉场景后,将背景的alpha通道设置为0,实现背景透明的功能。

(一)截图成png

(1)在.h文件

	UFUNCTION(BlueprintCallable)
		UTexture2D* SaveCaptureScene(bool IsSave, class USceneCaptureComponent2D* SceneCapture, const FString& SavePath, int32 NewWidth = 0, int32 NewHeight = 0, int32 CompressionQuality = 100);
        //保存为png
		bool SaveRenderTargetToDisk(UTextureRenderTarget2D* RenderTarget, const FString& FilePath, int32 NewWidth, int32 NewHeight, int32 CompressionQuality);
        //设置png的size
		void ResizeImage(const TArray<FColor>& SourceBitmap, int32 SourceWidth, int32 SourceHeight, int32 TargetWidth, int32 TargetHeight, TArray<FColor>& OutBitmap);
        //将png转为texture2d
		UTexture2D* LoadTextureFromFile(const FString& FilePath);

(2)在.cpp文件


#include "Engine/TextureRenderTarget2D.h"
#include "Components/SceneCaptureComponent2D.h"
#include "IImageWrapperModule.h"
#include "IImageWrapper.h"
#include "FileHelper.h"
#include "Paths.h"
#include "Engine/Texture2D.h"
#include "Misc/FileHelper.h"
#include "ImageUtils.h" 


UTexture2D* YourClass::SaveCaptureScene(const UObject* WorldContextObject, bool IsSave, USceneCaptureComponent2D* SceneCapture, const FString& SavePath, int32 NewWidth, int32 NewHeight, int32 CompressionQuality )
{
	if (!IsSave)
	{
		SceneCapture->SetVisibility(false);
		return nullptr;
	}

	if (SceneCapture && SceneCapture->TextureTarget)
	{
		SceneCapture->SetVisibility(true);
		// 捕捉场景
		SceneCapture->CaptureScene();

		// 将 RenderTarget 保存为 PNG 文件
		if (SaveRenderTargetToDisk(SceneCapture->TextureTarget, *SavePath,NewWidth,NewHeight,CompressionQuality))
		{
			// 导入 PNG 为 UTexture2D
			UTexture2D* CapturedTexture = LoadTextureFromFile(*SavePath);

			if (CapturedTexture)
			{
				// 在此处处理导入的纹理,例如将其应用到材质中
				UE_LOG(LogTemp, Log, TEXT("LYM_Texture imported successfully."));
				return CapturedTexture;
			}
			else
			{
				UE_LOG(LogTemp, Error, TEXT("LYM_Failed to import texture."));
				return nullptr;
			}
		}
		else
		{
			UE_LOG(LogTemp, Error, TEXT("LYM_Failed to SavePNG"));
			return nullptr;

		}
	}
	UE_LOG(LogTemp, Error, TEXT("LYM_SceneCapture or TextureTarget is invalid."));
	return nullptr;

}

bool YourClass::SaveRenderTargetToDisk(UTextureRenderTarget2D* RenderTarget, const FString& FilePath, int32 NewWidth, int32 NewHeight, int32 CompressionQuality)
{
	FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource();
	if (!RenderTargetResource)
	{
		return false;
	}
	// 创建位图
	TArray<FColor> Bitmap;

	if (!RenderTargetResource->ReadPixels(Bitmap))
	{
		return false;
	}

	int32 Width = RenderTarget->SizeX;
	int32 Height = RenderTarget->SizeY;

	// 调整图像大小(如果指定了新的宽度和高度)
	if (NewWidth > 0 && NewHeight > 0)
	{
		TArray<FColor> ResizedBitmap;
		//FImageUtils::ImageResize(Width, Height, Bitmap, NewWidth, NewHeight, ResizedBitmap);
		ResizeImage(Bitmap, Width, Height, NewWidth, NewHeight, ResizedBitmap);
		Bitmap = MoveTemp(ResizedBitmap);  // 使用调整后的图像
		Width = NewWidth;
		Height = NewHeight;
	}

	// 处理位图,设置背景透明
	for (FColor& Pixel : Bitmap)
	{
		if (Pixel == FColor::Black) // 假设背景是黑色,或者你可以用特定的颜色进行判断
		{
			Pixel.A = 0; // 设置 Alpha 通道为 0,透明
			UE_LOG(LogTemp, Display, TEXT("LYM_ SetColor = 0"));
		}
	}

	// 压缩为 PNG
	IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);

	if (ImageWrapper.IsValid() && ImageWrapper->SetRaw(Bitmap.GetData(), Bitmap.Num() * sizeof(FColor), Width, Height, ERGBFormat::BGRA, 8))
	{
		// 设置压缩质量
		const TArray<uint8, FDefaultAllocator64> PngData = ImageWrapper->GetCompressed(CompressionQuality);
		if (FFileHelper::SaveArrayToFile(PngData, *FilePath))
		{
			return true;
		}
	}

	return false;
}

void YourClass::ResizeImage(const TArray<FColor>& SourceBitmap, int32 SourceWidth, int32 SourceHeight, int32 TargetWidth, int32 TargetHeight, TArray<FColor>& OutBitmap)
{

    //(1)如果需要填充整个png使用这个
	//OutBitmap.SetNumUninitialized(TargetWidth * TargetHeight);

	//float XRatio = static_cast<float>(SourceWidth) / TargetWidth;
	//float YRatio = static_cast<float>(SourceHeight) / TargetHeight;

	//for (int32 y = 0; y < TargetHeight; ++y)
	//{
	//	for (int32 x = 0; x < TargetWidth; ++x)
	//	{
	//		int32 SrcX = FMath::Clamp(static_cast<int32>(x * XRatio), 0, SourceWidth - 1);
	//		int32 SrcY = FMath::Clamp(static_cast<int32>(y * YRatio), 0, SourceHeight - 1);

	//		OutBitmap[y * TargetWidth + x] = SourceBitmap[SrcY * SourceWidth + SrcX];
	//	}
	//}

    
    //(2)如果截图需要居中对齐,用这个
	// 清空目标位图并初始化为透明
	OutBitmap.SetNumZeroed(TargetWidth * TargetHeight);

	// 计算宽高比
	float SourceAspect = static_cast<float>(SourceWidth) / SourceHeight;
	float TargetAspect = static_cast<float>(TargetWidth) / TargetHeight;

	int32 NewWidth, NewHeight;
	if (SourceAspect > TargetAspect)
	{
		// 宽度受限
		NewWidth = TargetWidth;
		NewHeight = static_cast<int32>(TargetWidth / SourceAspect);
	}
	else
	{
		// 高度受限
		NewHeight = TargetHeight;
		NewWidth = static_cast<int32>(TargetHeight * SourceAspect);
	}

	// 调整后的图像居中对齐的偏移量
	int32 OffsetX = (TargetWidth - NewWidth) / 2;
	int32 OffsetY = (TargetHeight - NewHeight) / 2;

	// 转换 TArray<FColor> 为 TArray<FLinearColor>
	TArray<FLinearColor> SourceData;
	SourceData.SetNum(SourceWidth * SourceHeight);
	for (int32 i = 0; i < SourceWidth * SourceHeight; ++i)
	{
		FColor Color = SourceBitmap[i];
		SourceData[i] = Color.ReinterpretAsLinear();
	}

	// 缩放图像
	TArray<FLinearColor> ResizedData;
	ResizedData.SetNum(NewWidth * NewHeight);
	FImageUtils::ImageResize(SourceWidth, SourceHeight, SourceData, NewWidth, NewHeight, ResizedData);

	// 转换 TArray<FLinearColor> 回 TArray<FColor>
	TArray<FColor> ResizedBitmap;
	ResizedBitmap.SetNum(NewWidth * NewHeight);
	for (int32 i = 0; i < NewWidth * NewHeight; ++i)
	{
		ResizedBitmap[i] = ResizedData[i].ToFColor(true);
	}

	// 将调整后的图像绘制到目标位图中
	for (int32 Y = 0; Y < NewHeight; Y++)
	{
		for (int32 X = 0; X < NewWidth; X++)
		{
			int32 SourceIndex = Y * NewWidth + X;
			int32 TargetIndex = (Y + OffsetY) * TargetWidth + (X + OffsetX);

			OutBitmap[TargetIndex] = ResizedBitmap[SourceIndex];
		}
	}
}

UTexture2D* YourClass::LoadTextureFromFile(const FString& FilePath)
{
	TArray<uint8> RawFileData;
	if (!FFileHelper::LoadFileToArray(RawFileData, *FilePath))
	{
		return nullptr; // 文件加载失败
	}

	IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper"));
	EImageFormat ImageFormat = ImageWrapperModule.DetectImageFormat(RawFileData.GetData(), RawFileData.Num());

	if (ImageFormat == EImageFormat::Invalid)
	{
		return nullptr; // 图像格式无效
	}

	TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(ImageFormat);

	if (ImageWrapper.IsValid() && ImageWrapper->SetCompressed(RawFileData.GetData(), RawFileData.Num()))
	{
		TArray<uint8> RawData;
		if (ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, RawData))
		{
			// 创建 UTexture2D 并填充它的数据
			UTexture2D* Texture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);

			if (Texture)
			{
				void* TextureData = Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
				//锁定和解锁纹理数据以写入图像内容
				FMemory::Memcpy(TextureData, RawData.GetData(), RawData.Num());
				Texture->PlatformData->Mips[0].BulkData.Unlock();
				//更新纹理资源以使更改生效
				Texture->UpdateResource();

				return Texture;
			}
		}
	}

	return nullptr; // 导入失败
}

(3)在蓝图中添加SceneCapture2d组件:首先如果你不需要这个组件一直捕捉的话,需要将它隐藏并且将自动激活关了,不然会很耗性能

(4)组建一个RT用于捕捉:

(5)将PrimitiveRenderMode设置为UseShowOnlyList,CaptureSource设置为FinalColor(LDR)inRGB

(60设置为UseShowOnlyList后,就需要将你需要截图的特定actor添加到数组中。因为我这个showactors中有很多别的actor,所以需要拿到所有的子类一起添加进去

(7)截图并保存:GetProjectSaveDirectory是项目的save缓存路径,剩下两个是项目content路径和项目路径。

PS:使用这种方法时场景最好是白天的时候,不然可能会出现一坨黑的情况。

(二)实时显示

(1)创建一个RT

(2)如果是需要在ui上一直实时显示的话,就需要基于RT创建一个材质用于ui。

(3)将节点修改为UserInterface(用于ui)和Translucent(可使用透明)

(4)在ui中创建一张image使用这个材质

(5)scenecapture组件使用默认设置即可

(6)UI中可以实时的看到捕捉到的画面

(7)但是这种会将背景也一起捕捉到,如果不需要背景的话,就需要使用UseShowOnlyList方式。将需要显示的actor添加到数组中

(8)材质用再将opacity节点连上,如果不连的话,背景就是纯黑色的

(9)修改完之后运行即可看到ui中只有需要显示的actor,背景就被剔除掉了。

(三)小地图

如果是需要做小地图那种的,就是显示人物在场景中移动,

(1~5) 前五步和第二种实时显示的情况步骤是一样的,

(6)将ProjectionType改为Orthographic,另外一个orthoWidth就是你需要显示的size大小,根据需要设置

(7)修改完之后就能在ui中看到效果

UEC++中生成一个R8格式的Texture2D,可以通过以下步骤实现: 1. 首先,需要包含必要的头文件: ```cpp #include "Engine/Texture2D.h" #include "Runtime/Core/Public/Misc/FileHelper.h" #include "Runtime/ImageWrapper/Public/IImageWrapperModule.h" #include "Runtime/ImageWrapper/Public/IImageWrapper.h" ``` 2. 创建一个新的Texture2D对象,并设置其格式为PF_R8(单通道8位浮点格式): ```cpp UTexture2D* CreateR8Texture(int32 Width, int32 Height) { UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height, PF_R8); return Texture; } ``` 3. 填充Texture2D的数据。这里以简单的灰度值填充为例: ```cpp void FillTextureWithGrayScale(UTexture2D* Texture, float GrayValue) { if (!Texture) return; // 获取纹理数据 uint8* Data = static_cast<uint8*>(Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE)); // 计算每个像素的大小(对于R8格式,每个像素1字节) int32 PixelSize = 1; // 填充数据 for (int32 Y = 0; Y < Texture->GetSizeY(); Y++) { for (int32 X = 0; X < Texture->GetSizeX(); X++) { int32 Index = Y * Texture->GetSizeX() + X; Data[Index] = static_cast<uint8>(GrayValue * 255.0f); } } // 解锁数据 Texture->PlatformData->Mips[0].BulkData.Unlock(); // 更新资源 Texture->UpdateResource(); } ``` 4. 使用这些函数来创建一个R8格式的Texture2D并填充数据: ```cpp UTexture2D* MyTexture = CreateR8Texture(256, 256); FillTextureWithGrayScale(MyTexture, 0.5f); // 用0.5的灰度值填充纹理 ``` 以上代码展示了如何在UEC++中创建一个R8格式的Texture2D,并填充了简单的灰度值。你可以根据需要调整填充数据的逻辑,以适应不同的应用场景。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值