From d10f4763e714020cfa83a5dfb5e057162d98421e Mon Sep 17 00:00:00 2001 From: Hong Date: Fri, 27 Feb 2026 17:13:37 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20=E5=8F=AF=E4=BB=A5?= =?UTF-8?q?=E7=94=9F=E6=88=90image=E7=9A=84camera=E7=9A=84=E6=93=8D?= =?UTF-8?q?=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Blueprints/BP_CameraCaptureActor.uasset | Bin 0 -> 23105 bytes Content/Levels/L_Test.umap | Bin 48587 -> 50418 bytes .../Source/CameraCaptureActor.cpp | 245 ++++++++++++++++++ .../CameraCapture/Source/CameraCaptureActor.h | 75 ++++++ Source/CameraCaptureDemo.Target.cs | 2 +- .../CameraCaptureDemo.Build.cs | 2 +- .../CameraCaptureDemo/CameraCaptureDemo.cpp | 2 +- Source/CameraCaptureDemo/CameraCaptureDemo.h | 2 +- Source/CameraCaptureDemoEditor.Target.cs | 2 +- 9 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 Content/Blueprints/BP_CameraCaptureActor.uasset create mode 100644 Plugins/CameraCapture/Source/CameraCaptureActor.cpp create mode 100644 Plugins/CameraCapture/Source/CameraCaptureActor.h diff --git a/Content/Blueprints/BP_CameraCaptureActor.uasset b/Content/Blueprints/BP_CameraCaptureActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..76997a98bd0cd74048357b71b3eb72e8ec746ea9 GIT binary patch literal 23105 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 literal 48587 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 RX-ZMsH(ws4VReZA{{W=~mc{@8 diff --git a/Plugins/CameraCapture/Source/CameraCaptureActor.cpp b/Plugins/CameraCapture/Source/CameraCaptureActor.cpp new file mode 100644 index 0000000..33aa258 --- /dev/null +++ b/Plugins/CameraCapture/Source/CameraCaptureActor.cpp @@ -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(this); + RT->RenderTargetFormat = RTF_RGBA8; + RT->ClearColor = FLinearColor::Black; + RT->InitAutoFormat(ViewWidth, ViewHeight); + RT->UpdateResourceImmediate(true); + + USceneCaptureComponent2D* Capture = NewObject(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(this); + AtlasRT->RenderTargetFormat = RTF_RGBA8; + AtlasRT->ClearColor = FLinearColor::Black; + AtlasRT->InitAutoFormat(ViewWidth * AtlasGridX, ViewHeight * AtlasGridY); + AtlasRT->UpdateResourceImmediate(true); + } +} + +void ACameraCaptureActor::UpdateViewTransforms() +{ + // 以本 Actor(CameraActor)的 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& 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 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 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(TEXT("ImageWrapper")); + + TSharedPtr 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& Compressed = Wrapper->GetCompressed(); + TArray Bytes; + Bytes.Append(Compressed.GetData(), (int32)Compressed.Num()); + FFileHelper::SaveArrayToFile(Bytes, *FullPath); + }; + + // 基础版默认异步写盘,避免卡 + Async(EAsyncExecution::ThreadPool, MoveTemp(EncodeAndWrite)); +} \ No newline at end of file diff --git a/Plugins/CameraCapture/Source/CameraCaptureActor.h b/Plugins/CameraCapture/Source/CameraCaptureActor.h new file mode 100644 index 0000000..218f921 --- /dev/null +++ b/Plugins/CameraCapture/Source/CameraCaptureActor.h @@ -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 ViewCaptures; + + UPROPERTY(Transient) + TArray ViewRTs; + + UPROPERTY(Transient) + class UTextureRenderTarget2D* AtlasRT; +}; \ No newline at end of file diff --git a/Source/CameraCaptureDemo.Target.cs b/Source/CameraCaptureDemo.Target.cs index dfafe8e..b1cf729 100644 --- a/Source/CameraCaptureDemo.Target.cs +++ b/Source/CameraCaptureDemo.Target.cs @@ -1,4 +1,4 @@ -// Copyright 2026 (c) Jupiter. All Rights Reserved. +// Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic; diff --git a/Source/CameraCaptureDemo/CameraCaptureDemo.Build.cs b/Source/CameraCaptureDemo/CameraCaptureDemo.Build.cs index a8f6631..183a0ca 100644 --- a/Source/CameraCaptureDemo/CameraCaptureDemo.Build.cs +++ b/Source/CameraCaptureDemo/CameraCaptureDemo.Build.cs @@ -1,4 +1,4 @@ -// Copyright 2026 (c) Jupiter. All Rights Reserved. +// Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; diff --git a/Source/CameraCaptureDemo/CameraCaptureDemo.cpp b/Source/CameraCaptureDemo/CameraCaptureDemo.cpp index 131af7a..95bec95 100644 --- a/Source/CameraCaptureDemo/CameraCaptureDemo.cpp +++ b/Source/CameraCaptureDemo/CameraCaptureDemo.cpp @@ -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" diff --git a/Source/CameraCaptureDemo/CameraCaptureDemo.h b/Source/CameraCaptureDemo/CameraCaptureDemo.h index aabcf54..677c8e2 100644 --- a/Source/CameraCaptureDemo/CameraCaptureDemo.h +++ b/Source/CameraCaptureDemo/CameraCaptureDemo.h @@ -1,4 +1,4 @@ -// Copyright 2026 (c) Jupiter. All Rights Reserved. +// Copyright Epic Games, Inc. All Rights Reserved. #pragma once diff --git a/Source/CameraCaptureDemoEditor.Target.cs b/Source/CameraCaptureDemoEditor.Target.cs index e381b09..d0d1079 100644 --- a/Source/CameraCaptureDemoEditor.Target.cs +++ b/Source/CameraCaptureDemoEditor.Target.cs @@ -1,4 +1,4 @@ -// Copyright 2026 (c) Jupiter. All Rights Reserved. +// Copyright Epic Games, Inc. All Rights Reserved. using UnrealBuildTool; using System.Collections.Generic;