// @lint-ignore-every LICENSELINT // Copyright Epic Games, Inc. All Rights Reserved. #include "OculusXRSceneCaptureCubemap.h" #include "OculusXRHMDPrivate.h" #include "IImageWrapper.h" #include "IImageWrapperModule.h" #include "Kismet/GameplayStatics.h" #include "GameFramework/PlayerController.h" #include "Components/SceneCaptureComponent2D.h" #include "Engine/World.h" #include "Engine/StaticMeshActor.h" #include "Engine/TextureRenderTarget2D.h" #include "TextureResource.h" #include "HAL/FileManager.h" #include "Misc/FileHelper.h" #include "XRThreadUtils.h" #include "RenderingThread.h" //------------------------------------------------------------------------------------------------- // UOculusXRSceneCaptureCubemap //------------------------------------------------------------------------------------------------- UOculusXRSceneCaptureCubemap::UOculusXRSceneCaptureCubemap() : Stage(None) , CaptureBoxSideRes(2048) , CaptureFormat(EPixelFormat::PF_A16B16G16R16) , OverriddenLocation(FVector::ZeroVector) , OverriddenOrientation(FQuat::Identity) , CaptureOffset(FVector::ZeroVector) { } void UOculusXRSceneCaptureCubemap::StartCapture(UWorld* World, uint32 InCaptureBoxSideRes, EPixelFormat InFormat) { CaptureBoxSideRes = InCaptureBoxSideRes; CaptureFormat = InFormat; FVector Location = OverriddenLocation; FQuat Orientation = OverriddenOrientation; APlayerController* CapturePlayerController = UGameplayStatics::GetPlayerController(GWorld, 0); if (CapturePlayerController) { FRotator Rotation; CapturePlayerController->GetPlayerViewPoint(Location, Rotation); Rotation.Pitch = Rotation.Roll = 0; Orientation = FQuat(Rotation); Location += CaptureOffset; } if (!OverriddenOrientation.IsIdentity()) { Orientation = OverriddenOrientation; } if (!OverriddenLocation.IsZero()) { Location = OverriddenLocation; } const FVector ZAxis(0, 0, 1); const FVector YAxis(0, 1, 0); const FQuat FaceOrientations[] = { { ZAxis, PI / 2 }, { ZAxis, -PI / 2 }, // right, left { YAxis, -PI / 2 }, { YAxis, PI / 2 }, // top, bottom { ZAxis, 0 }, { ZAxis, -PI } }; // front, back for (int i = 0; i < 6; ++i) { USceneCaptureComponent2D* CaptureComponent = NewObject(); CaptureComponent->SetVisibility(true); CaptureComponent->SetHiddenInGame(false); CaptureComponent->FOVAngle = 90.f; CaptureComponent->bCaptureEveryFrame = true; CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; const FName TargetName = MakeUniqueObjectName(this, UTextureRenderTarget2D::StaticClass(), TEXT("SceneCaptureTextureTarget")); CaptureComponent->TextureTarget = NewObject(this, TargetName); CaptureComponent->TextureTarget->InitCustomFormat(CaptureBoxSideRes, CaptureBoxSideRes, CaptureFormat, false); CaptureComponents.Add(CaptureComponent); CaptureComponent->RegisterComponentWithWorld(GWorld); CaptureComponent->SetWorldLocationAndRotation(Location, Orientation * FaceOrientations[i]); CaptureComponent->UpdateContent(); } Stage = SettingPos; FActorSpawnParameters SpawnInfo; SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; SpawnInfo.bNoFail = true; SpawnInfo.ObjectFlags = RF_Transient; AStaticMeshActor* InGameActor; InGameActor = World->SpawnActor(SpawnInfo); OutputDir = FPaths::ProjectSavedDir() + TEXT("/Cubemaps"); IFileManager::Get().MakeDirectory(*OutputDir); } void UOculusXRSceneCaptureCubemap::Tick(float DeltaTime) { ExecuteOnRenderThread([]() { TickRenderingTickables(); }); if (Stage == SettingPos) { Stage = Capturing; return; } //Read Whole Capture Buffer IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked(FName("ImageWrapper")); TSharedPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); TArray OneFaceSurface, WholeCubemapData; OneFaceSurface.AddUninitialized(CaptureBoxSideRes * CaptureBoxSideRes); WholeCubemapData.AddUninitialized(CaptureBoxSideRes * 6 * CaptureBoxSideRes); // Read pixels for (int cubeFaceIdx = 0; cubeFaceIdx < 6; ++cubeFaceIdx) { auto RenderTarget = CaptureComponents[cubeFaceIdx]->TextureTarget->GameThread_GetRenderTargetResource(); RenderTarget->ReadPixelsPtr(OneFaceSurface.GetData(), FReadSurfaceDataFlags()); // enforce alpha to be 1 for (FColor& Color : OneFaceSurface) { Color.A = 255; } // copy subimage into whole cubemap array const uint32 Stride = CaptureBoxSideRes * 6; const uint32 XOff = cubeFaceIdx * CaptureBoxSideRes; const uint32 StripSizeInBytes = CaptureBoxSideRes * sizeof(FColor); for (uint32 y = 0; y < CaptureBoxSideRes; ++y) { FMemory::Memcpy(WholeCubemapData.GetData() + XOff + y * Stride, OneFaceSurface.GetData() + y * CaptureBoxSideRes, StripSizeInBytes); } } ImageWrapper->SetRaw(WholeCubemapData.GetData(), WholeCubemapData.GetAllocatedSize(), CaptureBoxSideRes * 6, CaptureBoxSideRes, ERGBFormat::BGRA, 8); const TArray64& PNGData = ImageWrapper->GetCompressed(100); const FString Filename = OutputDir + FString::Printf(TEXT("/Cubemap-%d-%s.png"), CaptureBoxSideRes, *FDateTime::Now().ToString(TEXT("%m.%d-%H.%M.%S"))); FFileHelper::SaveArrayToFile(PNGData, *Filename); check(Stage == Capturing); Stage = Finished; for (int i = 0; i < CaptureComponents.Num(); ++i) { CaptureComponents[i]->UnregisterComponent(); } CaptureComponents.SetNum(0); RemoveFromRoot(); // We're done here, so remove ourselves from the root set. @TODO: Fix this later } #if !UE_BUILD_SHIPPING void UOculusXRSceneCaptureCubemap::CaptureCubemapCommandHandler(const TArray& Args, UWorld* World, FOutputDevice& Ar) { bool bCreateOculusMobileCubemap = false; FVector CaptureOffset(FVector::ZeroVector); float Yaw = 0.f; for (const FString& Arg : Args) { FParse::Value(*Arg, TEXT("XOFF="), CaptureOffset.X); FParse::Value(*Arg, TEXT("YOFF="), CaptureOffset.Y); FParse::Value(*Arg, TEXT("ZOFF="), CaptureOffset.Z); FParse::Value(*Arg, TEXT("YAW="), Yaw); if (Arg.Equals(TEXT("MOBILE"), ESearchCase::IgnoreCase)) { bCreateOculusMobileCubemap = true; } } UOculusXRSceneCaptureCubemap* CubemapCapturer = NewObject(); CubemapCapturer->AddToRoot(); // TODO: Don't add the object to the GC root CubemapCapturer->SetOffset((FVector)CaptureOffset); if (Yaw != 0.f) { FRotator Rotation(FRotator::ZeroRotator); Rotation.Yaw = Yaw; const FQuat Orient(Rotation); CubemapCapturer->SetInitialOrientation(Orient); } const uint32 CaptureHeight = 2048; CubemapCapturer->StartCapture(World, bCreateOculusMobileCubemap ? CaptureHeight / 2 : CaptureHeight); } #endif