添加 可以生成image的camera的操作

This commit is contained in:
顾宏 2026-02-27 17:13:37 +08:00
parent 872ab77972
commit d10f4763e7
9 changed files with 325 additions and 5 deletions

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,245 @@
#include "CameraCaptureActor.h"
#include "Components/SceneCaptureComponent2D.h"
#include "Camera/CameraComponent.h"
#include "Engine/TextureRenderTarget2D.h"
#include "Engine/World.h"
#include "HAL/PlatformFilemanager.h"
#include "Misc/Paths.h"
#include "Misc/FileHelper.h"
#include "IImageWrapper.h"
#include "IImageWrapperModule.h"
#include "Modules/ModuleManager.h"
#include "Async/Async.h"
ACameraCaptureActor::ACameraCaptureActor()
{
PrimaryActorTick.bCanEverTick = true;
}
void ACameraCaptureActor::BeginPlay()
{
Super::BeginPlay();
BuildViewCaptures();
}
void ACameraCaptureActor::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
if (!bCaptureEveryFrame) return;
UpdateViewTransforms();
CaptureViews();
if (bGenerateAtlas)
{
GenerateAtlas();
}
}
#if WITH_EDITOR
void ACameraCaptureActor::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent)
{
Super::PostEditChangeProperty(PropertyChangedEvent);
// 任何关键参数变化都重建(避免 RT 尺寸/数量不匹配)
BuildViewCaptures();
}
#endif
void ACameraCaptureActor::BuildViewCaptures()
{
// Destroy old components
for (USceneCaptureComponent2D* Comp : ViewCaptures)
{
if (Comp)
{
Comp->DestroyComponent();
}
}
ViewCaptures.Empty();
ViewRTs.Empty();
AtlasRT = nullptr;
if (ViewCount < 1) ViewCount = 1;
if (ViewWidth < 16) ViewWidth = 16;
if (ViewHeight < 16) ViewHeight = 16;
// Recreate view captures
for (int32 i = 0; i < ViewCount; ++i)
{
UTextureRenderTarget2D* RT = NewObject<UTextureRenderTarget2D>(this);
RT->RenderTargetFormat = RTF_RGBA8;
RT->ClearColor = FLinearColor::Black;
RT->InitAutoFormat(ViewWidth, ViewHeight);
RT->UpdateResourceImmediate(true);
USceneCaptureComponent2D* Capture = NewObject<USceneCaptureComponent2D>(this);
Capture->SetupAttachment(GetRootComponent());
Capture->RegisterComponent();
Capture->TextureTarget = RT;
Capture->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
Capture->bCaptureEveryFrame = false; // 手动触发
Capture->bCaptureOnMovement = false;
ViewCaptures.Add(Capture);
ViewRTs.Add(RT);
}
// Atlas RT (optional)
if (bGenerateAtlas)
{
if (AtlasGridX < 1) AtlasGridX = 1;
if (AtlasGridY < 1) AtlasGridY = 1;
AtlasRT = NewObject<UTextureRenderTarget2D>(this);
AtlasRT->RenderTargetFormat = RTF_RGBA8;
AtlasRT->ClearColor = FLinearColor::Black;
AtlasRT->InitAutoFormat(ViewWidth * AtlasGridX, ViewHeight * AtlasGridY);
AtlasRT->UpdateResourceImmediate(true);
}
}
void ACameraCaptureActor::UpdateViewTransforms()
{
// 以本 ActorCameraActor的 transform 为中心
const FVector Center = GetActorLocation();
const FRotator Rot = GetActorRotation();
const FVector Right = Rot.Quaternion().GetRightVector();
// FOV 从主 CameraComponent 取
float FOV = 90.f;
if (UCameraComponent* Cam = GetCameraComponent())
{
FOV = Cam->FieldOfView;
}
for (int32 i = 0; i < ViewCaptures.Num(); ++i)
{
if (!ViewCaptures[i]) continue;
// 线性水平分布:居中对称
const float Offset = (i - (ViewCount - 1) * 0.5f) * Baseline;
const FVector Pos = Center + Right * Offset;
ViewCaptures[i]->SetWorldLocationAndRotation(Pos, Rot);
ViewCaptures[i]->FOVAngle = FOV;
}
}
void ACameraCaptureActor::CaptureViews()
{
for (USceneCaptureComponent2D* Capture : ViewCaptures)
{
if (Capture)
{
Capture->CaptureScene();
}
}
}
static void FlipVertical(TArray<FColor>& Pixels, int32 W, int32 H)
{
for (int32 Row = 0; Row < H / 2; ++Row)
{
const int32 A = Row * W;
const int32 B = (H - 1 - Row) * W;
for (int32 Col = 0; Col < W; ++Col)
{
Swap(Pixels[A + Col], Pixels[B + Col]);
}
}
}
void ACameraCaptureActor::GenerateAtlas()
{
if (!AtlasRT) return;
// 这里做一个“可编译的基础版”CPU 拼合 AtlasPixels可选保存到磁盘
// 注意:把像素写回 AtlasRT 需要更底层的 RHI/渲染线程操作;高性能版我建议改成 GPU Blit。
const int32 AtlasW = AtlasRT->SizeX;
const int32 AtlasH = AtlasRT->SizeY;
TArray<FColor> AtlasPixels;
AtlasPixels.SetNumZeroed(AtlasW * AtlasH);
const int32 MaxViews = FMath::Min(ViewCount, AtlasGridX * AtlasGridY);
for (int32 i = 0; i < MaxViews; ++i)
{
UTextureRenderTarget2D* RT = ViewRTs.IsValidIndex(i) ? ViewRTs[i] : nullptr;
if (!RT) continue;
FTextureRenderTargetResource* Res = RT->GameThread_GetRenderTargetResource();
if (!Res) continue;
TArray<FColor> Pixels;
Pixels.SetNumUninitialized(ViewWidth * ViewHeight);
FReadSurfaceDataFlags ReadFlags(RCM_UNorm);
ReadFlags.SetLinearToGamma(true);
if (!Res->ReadPixels(Pixels, ReadFlags)) continue;
FlipVertical(Pixels, ViewWidth, ViewHeight);
const int32 GX = i % AtlasGridX;
const int32 GY = i / AtlasGridX;
const int32 DstX0 = GX * ViewWidth;
const int32 DstY0 = GY * ViewHeight;
for (int32 y = 0; y < ViewHeight; ++y)
{
const int32 DstY = DstY0 + y;
if (DstY < 0 || DstY >= AtlasH) continue;
const int32 SrcRow = y * ViewWidth;
const int32 DstRow = DstY * AtlasW;
for (int32 x = 0; x < ViewWidth; ++x)
{
const int32 DstX = DstX0 + x;
if (DstX < 0 || DstX >= AtlasW) continue;
AtlasPixels[DstRow + DstX] = Pixels[SrcRow + x];
}
}
}
// ✅ 如果你需要“输出到目录”,这里就直接把 AtlasPixels 编码写盘
// 你也可以加开关bDumpAtlasToDisk
const FString OutDir = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("CameraCapture"));
IPlatformFile& PF = FPlatformFileManager::Get().GetPlatformFile();
if (!PF.DirectoryExists(*OutDir)) PF.CreateDirectoryTree(*OutDir);
const FString FullPath = FPaths::Combine(
OutDir,
FString::Printf(TEXT("atlas_%010lld.png"), (int64)GFrameCounter)
);
auto EncodeAndWrite = [AtlasPixels = MoveTemp(AtlasPixels), AtlasW, AtlasH, FullPath]()
{
IImageWrapperModule& ImageWrapperModule =
FModuleManager::LoadModuleChecked<IImageWrapperModule>(TEXT("ImageWrapper"));
TSharedPtr<IImageWrapper> Wrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
if (!Wrapper.IsValid()) return;
if (!Wrapper->SetRaw(AtlasPixels.GetData(), AtlasPixels.Num() * sizeof(FColor),
AtlasW, AtlasH, ERGBFormat::BGRA, 8))
{
return;
}
const TArray64<uint8>& Compressed = Wrapper->GetCompressed();
TArray<uint8> Bytes;
Bytes.Append(Compressed.GetData(), (int32)Compressed.Num());
FFileHelper::SaveArrayToFile(Bytes, *FullPath);
};
// 基础版默认异步写盘,避免卡
Async(EAsyncExecution::ThreadPool, MoveTemp(EncodeAndWrite));
}

View File

@ -0,0 +1,75 @@
#pragma once
#include "CoreMinimal.h"
#include "Camera/CameraActor.h"
#include "CameraCaptureActor.generated.h"
UENUM(BlueprintType)
enum class EViewDistribution : uint8
{
LinearHorizontal UMETA(DisplayName = "Linear Horizontal"),
Circular UMETA(DisplayName = "Circular")
};
UCLASS()
class CAMERACAPTURE_API ACameraCaptureActor : public ACameraActor
{
GENERATED_BODY()
public:
ACameraCaptureActor();
// ===== 多视图参数 =====
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiView")
int32 ViewCount = 9;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiView")
float Baseline = 5.0f; // cm
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiView")
EViewDistribution Distribution = EViewDistribution::LinearHorizontal;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiView")
int32 ViewWidth = 1024;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiView")
int32 ViewHeight = 1024;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "MultiView")
bool bCaptureEveryFrame = true;
// Atlas
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Atlas")
bool bGenerateAtlas = true;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Atlas")
int32 AtlasGridX = 3;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Atlas")
int32 AtlasGridY = 3;
protected:
virtual void BeginPlay() override;
virtual void Tick(float DeltaSeconds) override;
#if WITH_EDITOR
virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
private:
void BuildViewCaptures();
void UpdateViewTransforms();
void CaptureViews();
void GenerateAtlas();
private:
UPROPERTY(Transient)
TArray<class USceneCaptureComponent2D*> ViewCaptures;
UPROPERTY(Transient)
TArray<class UTextureRenderTarget2D*> ViewRTs;
UPROPERTY(Transient)
class UTextureRenderTarget2D* AtlasRT;
};

View File

@ -1,4 +1,4 @@
// Copyright 2026 (c) Jupiter. All Rights Reserved.
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;

View File

@ -1,4 +1,4 @@
// Copyright 2026 (c) Jupiter. All Rights Reserved.
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;

View File

@ -1,4 +1,4 @@
// Copyright 2026 (c) Jupiter. All Rights Reserved.
// Copyright Epic Games, Inc. All Rights Reserved.
#include "CameraCaptureDemo.h"
#include "Modules/ModuleManager.h"

View File

@ -1,4 +1,4 @@
// Copyright 2026 (c) Jupiter. All Rights Reserved.
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once

View File

@ -1,4 +1,4 @@
// Copyright 2026 (c) Jupiter. All Rights Reserved.
// Copyright Epic Games, Inc. All Rights Reserved.
using UnrealBuildTool;
using System.Collections.Generic;