UE输出场景2D全景图和2D全景深度图
1 准备
创建一个立方体渲染目标,使用USceneCaptureComponentCube组件,捕获全景图使用SCS_FinalColorHDR或者SCS_FinalColorLDR,捕获深度使用SCS_SceneDepth,然后通过引擎的CubemapHelpers::GenerateLongLatUnwrap函数来把渲染纹理转成2d全景图。
2 说明
捕获深度时候,纹理我使用的PF_A32B32G32R32F这个精度够也支持在Linux平台使用,其他格式可以自行探索,RenderTarget->OverrideFormat = PF_FloatRGBA;这一步代码是为了GenerateLongLatUnwrap函数能够正常处理成16位的结果。
3 代码
1 捕获2d全景图
// ===== 1. 捕获 RGB HDR ===== SceneCapture->CaptureSource = ESceneCaptureSource::SCS_FinalColorHDR; RenderTarget->Init(FaceSize, PF_FloatRGBA); SceneCapture->CaptureScene(); FlushRenderingCommands(); TArray64<uint8> RGBRawData; FIntPoint RGBOutputSize; EPixelFormat RGBOutputFormat; bool bSuccess = CubemapHelpers::GenerateLongLatUnwrap(RenderTarget, RGBRawData, RGBOutputSize, RGBOutputFormat); if (!bSuccess) { UE_LOG(LogTemp, Error, TEXT("RGB GenerateLongLatUnwrap Error")); return; } TArray<FLinearColor> ExrPixels; int32 RGBPixelCount = RGBOutputSize.X * RGBOutputSize.Y; ExrPixels.SetNum(RGBPixelCount); for (int32 i = 0; i < RGBPixelCount; i++) { const uint8* PixelPtr = &RGBRawData[i * 8]; FFloat16 R16, G16, B16, A16; FMemory::Memcpy(&R16, PixelPtr, 2); FMemory::Memcpy(&G16, PixelPtr + 2, 2); FMemory::Memcpy(&B16, PixelPtr + 4, 2); ExrPixels[i] = FLinearColor(R16.GetFloat(), G16.GetFloat(), B16.GetFloat(), 1.0f); } { TArray<FColor> Pixels; Pixels.SetNum(ExrPixels.Num()); for (int i = 0; i < ExrPixels.Num(); i++) { Pixels[i] = ExrPixels[i].ToFColor(true); } TArray64<uint8> PNGData; FImageUtils::PNGCompressImageArray(RGBOutputSize.X, RGBOutputSize.Y, Pixels, PNGData); FFileHelper::SaveArrayToFile(PNGData, *SaveRGBFilePath); }2 捕获2d全景深度图
// ===== 捕获深度 ===== UTextureRenderTargetCube* RenderTarget = SceneCapture->TextureTarget; const int32 FaceSize = width; SceneCapture->CaptureSource = ESceneCaptureSource::SCS_SceneDepth; RenderTarget->Init(FaceSize, PF_A32B32G32R32F); SceneCapture->CaptureScene(); FlushRenderingCommands(); // 这一步代码是为了GenerateLongLatUnwrap函数能够正常处理成16位的结果 RenderTarget->OverrideFormat = PF_FloatRGBA; TArray64<uint8> DepthRawData; FIntPoint DepthOutputSize; EPixelFormat DepthOutputFormat; bSuccess = CubemapHelpers::GenerateLongLatUnwrap(RenderTarget, DepthRawData, DepthOutputSize, DepthOutputFormat); if (!bSuccess) { UE_LOG(LogTemp, Error, TEXT("Depth GenerateLongLatUnwrap Error")); return; } TArray<float> SceneDepths; int32 DepthPixelCount = DepthOutputSize.X * DepthOutputSize.Y; SceneDepths.SetNum(DepthPixelCount); float MaxClipDepth = 65504; for (int32 i = 0; i < DepthPixelCount; i++) { const uint8* PixelPtr = &DepthRawData[i * 8]; FFloat16 R16; FMemory::Memcpy(&R16, PixelPtr, 2); float Depth = R16.GetFloat(); if (Depth > 0.0f && Depth < MaxClipDepth) { SceneDepths[i] = Depth; } else { SceneDepths[i] = 0; } } { float MinDepth = TNumericLimits<float>::Max(); float MaxDepth = TNumericLimits<float>::Min(); for (int32 i = 0; i < SceneDepths.Num(); ++i) { float Depth = SceneDepths[i]; if (Depth > 0) { MinDepth = FMath::Min(MinDepth, Depth); MaxDepth = FMath::Max(MaxDepth, Depth); } } float DepthRange = MaxDepth - MinDepth; if (DepthRange <= KINDA_SMALL_NUMBER) DepthRange = 1.0f; TArray<uint16> DepthData16; DepthData16.SetNumUninitialized(SceneDepths.Num()); for (int32 i = 0; i < SceneDepths.Num(); ++i) { float Depth = SceneDepths[i]; float NormalizedDepth = (Depth - MinDepth) / DepthRange; NormalizedDepth = FMath::Clamp(NormalizedDepth, 0.0f, 1.0f); float FlippedDepth = 1.0f - NormalizedDepth; DepthData16[i] = static_cast<uint16>(FlippedDepth * MAX_uint16); } IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); if (ImageWrapper->SetRaw(DepthData16.GetData(), DepthData16.GetAllocatedSize(), DepthOutputSize.X, DepthOutputSize.Y, ERGBFormat::Gray, 16)) { TArray64<uint8> CompressedData = ImageWrapper->GetCompressed(); if (CompressedData.Num() > 0) { FFileHelper::SaveArrayToFile(CompressedData, *SaveDepthFilePath); } } else { UE_LOG(LogTemp, Error, TEXT("Depth SetRaw failed")); } }