171 lines
4.7 KiB
C++
171 lines
4.7 KiB
C++
// Copyright 2026 (c) Jupiter. All Rights Reserved.
|
|
|
|
#include "CameraCaptureSubSystem.h"
|
|
#include "CameraCaptureActor.h"
|
|
#include "Components/SceneCaptureComponent2D.h"
|
|
#include "Camera/CameraComponent.h"
|
|
|
|
#include "EngineUtils.h"
|
|
#include "Engine/World.h"
|
|
#include "Engine/TextureRenderTarget2D.h"
|
|
#include "IImageWrapper.h"
|
|
#include "IImageWrapperModule.h"
|
|
#include "HAL/PlatformFilemanager.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Async/Async.h"
|
|
|
|
void UCameraCaptureSubSystem::Initialize(FSubsystemCollectionBase& Collection)
|
|
{
|
|
Super::Initialize(Collection);
|
|
|
|
OutPutDirectoryPath = FPaths::Combine(FPaths::ProjectSavedDir(), TEXT("CameraCapture"));
|
|
}
|
|
|
|
void UCameraCaptureSubSystem::Deinitialize()
|
|
{
|
|
Super::Deinitialize();
|
|
}
|
|
|
|
void UCameraCaptureSubSystem::OnWorldBeginPlay(UWorld& InWorld)
|
|
{
|
|
Super::OnWorldBeginPlay(InWorld);
|
|
|
|
for (TActorIterator<ACameraCaptureActor> It(GetWorld()); It; ++It)
|
|
{
|
|
|
|
ACameraCaptureActor* CameraCaptureActor = Cast<ACameraCaptureActor>(*It);
|
|
if (CameraCaptureActor)
|
|
{
|
|
AllCaptureCameras.Emplace(CameraCaptureActor);
|
|
}
|
|
}
|
|
|
|
if (AllCaptureCameras.Num() != CAMERA_WIDTH_NUMS * CAMERA_HEIGHTH_NUMS)
|
|
{
|
|
return;
|
|
}
|
|
|
|
|
|
}
|
|
|
|
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 UCameraCaptureSubSystem::Tick(float DeltaTime)
|
|
{
|
|
CaptureViewToImageTick();
|
|
|
|
}
|
|
|
|
TStatId UCameraCaptureSubSystem::GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(UCameraCaptureSubSystem, STATGROUP_Tickables);
|
|
}
|
|
|
|
void UCameraCaptureSubSystem::CaptureViewToImageTick()
|
|
{
|
|
if (AllCaptureCameras.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FColor> AtlasPixels;
|
|
const int32 AtlasW = ViewWidth * AtlasGridX;
|
|
const int32 AtlasH = ViewHeight * AtlasGridY;
|
|
AtlasPixels.SetNumZeroed(AtlasW * AtlasH);
|
|
|
|
for (int32 CameraIndex = 0; CameraIndex < AllCaptureCameras.Num(); CameraIndex++)
|
|
{
|
|
if (!AllCaptureCameras[CameraIndex].IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
UTextureRenderTarget2D* RenderTarget = AllCaptureCameras[CameraIndex]->GetViewRT();
|
|
if (!RenderTarget)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FTextureRenderTargetResource* RenderTargetRes = RenderTarget->GameThread_GetRenderTargetResource();
|
|
if (!RenderTargetRes)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
TArray<FColor> Pixels;
|
|
Pixels.SetNumUninitialized(ViewWidth * ViewHeight);
|
|
|
|
FReadSurfaceDataFlags ReadFlags(RCM_UNorm);
|
|
ReadFlags.SetLinearToGamma(true);
|
|
|
|
if (!RenderTargetRes->ReadPixels(Pixels, ReadFlags)) continue;
|
|
FlipVertical(Pixels, ViewWidth, ViewHeight);
|
|
|
|
const int32 GX = CameraIndex % AtlasGridX;
|
|
const int32 GY = CameraIndex / 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];
|
|
}
|
|
}
|
|
}
|
|
|
|
AsyncWriteImageToDisk(AtlasPixels, AtlasW, AtlasH);
|
|
}
|
|
|
|
void UCameraCaptureSubSystem::AsyncWriteImageToDisk(TArray<FColor>& AtlasPixels, const int32 AtlasW, const int32 AtlasH)
|
|
{
|
|
const FString FullPath = FPaths::Combine( OutPutDirectoryPath, 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));
|
|
}
|