x

Search in
Sort by:

Question Status:

Search help

  • Simple searches use one or more words. Separate the words with spaces (cat dog) to search cat,dog or both. Separate the words with plus signs (cat +dog) to search for items that may contain cat but must contain dog.
  • You can further refine your search on the search results page, where you can search by keywords, author, topic. These can be combined with each other. Examples
    • cat dog --matches anything with cat,dog or both
    • cat +dog --searches for cat +dog where dog is a mandatory term
    • cat -dog -- searches for cat excluding any result containing dog
    • [cats] —will restrict your search to results with topic named "cats"
    • [cats] [dogs] —will restrict your search to results with both topics, "cats", and "dogs"

Office Holiday

Epic Games' offices will be on holiday from June 22nd to July 7th. During this period support will be limited. Our offices will reopen on Monday, July 8th. 

Capturing stereoscopic 360 from Unreal Engine 4

Hello folks! I have been struggling with this for days already with no succes, not even in the slack could find a proper solution. I did everything mentioned in this article: https://www.unrealengine.com/blog/capturing-stereoscopic-360-screenshots-videos-movies-unreal-engine-4#comments-template

And I still can't get the engine to properly combine both frames, or solve the right eye issue or capture de PP, everything in the code is as it must, I'm pasting the code in here (the hub doesn't let me upload the file :C), in case anyone needs it and can solve it:

 // Copyright 2015 Kite & Lightning.  All rights reserved.
 
 #include "StereoPanoramaPrivatePCH.h"
 
 DEFINE_LOG_CATEGORY( LogStereoPanorama );
 
 //Rotated Grid Supersampling
 const int32 maxNumSamples = 16;
 struct SamplingPattern
 {
     int numSamples;
     FVector2D ssOffsets[maxNumSamples];
 };
 const SamplingPattern g_ssPatterns[] =
 {
     {
         1,
         {
             FVector2D(0, 0),
         }
     },
     {
         4,
         {
             FVector2D(0.125f, 0.625f),
             FVector2D(0.375f, 0.125f),
             FVector2D(0.625f, 0.875f),
             FVector2D(0.875f, 0.375f),
         }
     },
     {
         16,
         {
             FVector2D(0.125f, 0.125f),
             FVector2D(0.125f, 0.375f),
             FVector2D(0.125f, 0.625f),
             FVector2D(0.125f, 0.875f),
             FVector2D(0.375f, 0.125f),
             FVector2D(0.375f, 0.375f),
             FVector2D(0.375f, 0.625f),
             FVector2D(0.375f, 0.875f),
             FVector2D(0.625f, 0.125f),
             FVector2D(0.625f, 0.375f),
             FVector2D(0.625f, 0.625f),
             FVector2D(0.625f, 0.875f),
             FVector2D(0.875f, 0.125f),
             FVector2D(0.875f, 0.375f),
             FVector2D(0.875f, 0.625f),
             FVector2D(0.875f, 0.875f),
         }
     },
 
 };
 
 void USceneCapturer::InitCaptureComponent( USceneCaptureComponent2D* CaptureComponent, float HFov, float VFov, EStereoscopicPass InStereoPass )
 {
     CaptureComponent->SetVisibility( true );
     CaptureComponent->SetHiddenInGame( false );
 
     //CaptureComponent->CaptureStereoPass = InStereoPass;
     CaptureComponent->FOVAngle = FMath::Max( HFov, VFov );
     CaptureComponent->bCaptureEveryFrame = false;
     CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR;
 
     const FName TargetName = MakeUniqueObjectName(this, UTextureRenderTarget2D::StaticClass(), TEXT("SceneCaptureTextureTarget"));
     CaptureComponent->TextureTarget = NewObject<UTextureRenderTarget2D>(this, TargetName);
     //TODO: ikrimae: Not sure why the render target needs to be float to avoid banding. Seems like captures to this RT and then applies PP
     //               on top of it which causes degredation.
     CaptureComponent->TextureTarget->InitCustomFormat(CaptureWidth, CaptureHeight, PF_A16B16G16R16, false);
     CaptureComponent->TextureTarget->ClearColor = FLinearColor::Red;
     //*NEW* Set up post settings based on the player camera manager
 
     if (GetWorld())
 
     {
 
         APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
 
         if (PlayerController && PlayerController->PlayerCameraManager)
 
         {
 
             CaptureComponent->PostProcessSettings = PlayerController->PlayerCameraManager->CameraCache.POV.PostProcessSettings;
 
             CaptureComponent->PostProcessBlendWeight = PlayerController->PlayerCameraManager->CameraCache.POV.PostProcessBlendWeight;
 
         }
 
     }
 
     // Disable effects that we don't want for capture
 
     CaptureComponent->PostProcessSettings.bOverride_GrainIntensity = true;
 
     CaptureComponent->PostProcessSettings.GrainIntensity = 0.0f;
 
     CaptureComponent->PostProcessSettings.bOverride_MotionBlurAmount = true;
 
     CaptureComponent->PostProcessSettings.MotionBlurAmount = 0.0f;
 
     CaptureComponent->PostProcessSettings.bOverride_ScreenSpaceReflectionIntensity = true;
 
     CaptureComponent->PostProcessSettings.ScreenSpaceReflectionIntensity = 0.0f;
 
     CaptureComponent->PostProcessSettings.bOverride_VignetteIntensity = true;
 
     CaptureComponent->PostProcessSettings.VignetteIntensity = 0.0f;
 
     //*NEW*
     CaptureComponent->RegisterComponentWithWorld( GWorld );
 
     // UE4 cannot serialize an array of subobject pointers, so add these objects to the root
     CaptureComponent->AddToRoot();
 }
 
 USceneCapturer::USceneCapturer(FVTableHelper& Helper)
     : Super(Helper)
     , ImageWrapperModule(FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")))
     , bIsTicking(false)
     , CapturePlayerController(NULL)
     , CaptureGameMode(NULL)
     , hAngIncrement(FStereoPanoramaManager::HorizontalAngularIncrement->GetFloat())
     , vAngIncrement(FStereoPanoramaManager::VerticalAngularIncrement->GetFloat())
     , eyeSeparation(FStereoPanoramaManager::EyeSeparation->GetFloat())
     , NumberOfHorizontalSteps((int32)(360.0f / hAngIncrement))
     , NumberOfVerticalSteps((int32)(180.0f / vAngIncrement) + 1) /* Need an extra b/c we only grab half of the top & bottom slices */
     , SphericalAtlasWidth(FStereoPanoramaManager::StepCaptureWidth->GetInt())
     , SphericalAtlasHeight(SphericalAtlasWidth / 2)
     , bForceAlpha(FStereoPanoramaManager::ForceAlpha->GetInt() != 0)
     , bEnableBilerp(FStereoPanoramaManager::EnableBilerp->GetInt() != 0)
     , SSMethod(FMath::Clamp<int32>(FStereoPanoramaManager::SuperSamplingMethod->GetInt(), 0, ARRAY_COUNT(g_ssPatterns)))
     , bOverrideInitialYaw(FStereoPanoramaManager::ShouldOverrideInitialYaw->GetInt() != 0)
     , ForcedInitialYaw(FRotator::ClampAxis(FStereoPanoramaManager::ForcedInitialYaw->GetFloat()))
     , OutputDir(FStereoPanoramaManager::OutputDir->GetString().IsEmpty() ? FPaths::GameSavedDir() / TEXT("StereoPanorama") : FStereoPanoramaManager::OutputDir->GetString())
     , dbgDisableOffsetRotation(FStereoPanoramaManager::FadeStereoToZeroAtSides->GetInt() != 0)
 {}
 
 USceneCapturer::USceneCapturer()
     : ImageWrapperModule( FModuleManager::LoadModuleChecked<IImageWrapperModule>( FName( "ImageWrapper" ) ) )
     , bIsTicking( false )
     , CapturePlayerController( NULL )
     , CaptureGameMode( NULL )
     , hAngIncrement( FStereoPanoramaManager::HorizontalAngularIncrement->GetFloat() )
     , vAngIncrement( FStereoPanoramaManager::VerticalAngularIncrement->GetFloat() )
     , eyeSeparation( FStereoPanoramaManager::EyeSeparation->GetFloat() )
     , NumberOfHorizontalSteps( ( int32 )( 360.0f / hAngIncrement ) )
     , NumberOfVerticalSteps( ( int32 )( 180.0f / vAngIncrement ) + 1 ) /* Need an extra b/c we only grab half of the top & bottom slices */
     , SphericalAtlasWidth( FStereoPanoramaManager::StepCaptureWidth->GetInt() )
     , SphericalAtlasHeight( SphericalAtlasWidth / 2)
     , bForceAlpha( FStereoPanoramaManager::ForceAlpha->GetInt() != 0 )
     , bEnableBilerp( FStereoPanoramaManager::EnableBilerp->GetInt() != 0 )
     , SSMethod( FMath::Clamp<int32>(FStereoPanoramaManager::SuperSamplingMethod->GetInt(), 0, ARRAY_COUNT(g_ssPatterns)) )
     , bOverrideInitialYaw( FStereoPanoramaManager::ShouldOverrideInitialYaw->GetInt() != 0 )
     , ForcedInitialYaw( FRotator::ClampAxis(FStereoPanoramaManager::ForcedInitialYaw->GetFloat()) )
     , OutputDir( FStereoPanoramaManager::OutputDir->GetString().IsEmpty() ? FPaths::GameSavedDir() / TEXT("StereoPanorama") : FStereoPanoramaManager::OutputDir->GetString() )
     , dbgDisableOffsetRotation( FStereoPanoramaManager::FadeStereoToZeroAtSides->GetInt() != 0 )
 {
     //NOTE: ikrimae: Keeping the old sampling mechanism just until we're sure the new way is always better
     dbgMatchCaptureSliceFovToAtlasSliceFov = false;
 
     float captureHFov = 0, captureVFov = 0;
 
     if (dbgMatchCaptureSliceFovToAtlasSliceFov)
     {
         //Slicing Technique 1: Match Capture Slice StripWidth to match the pixel dimensions of AtlasWidth/NumHorizSteps & s.t. stripwidth/stripheight fovs match hAngIncr & vAngIncr
         //                     Legacy technique but allows setting the strip width to match atlas slice width
         //                     Pretty wasteful and will break if CaptureHFov & hangIncr/vAngIncr diverge greatly b/c resultant texture will exceed GPU bounds
         //                     StripHeight is computed based on solving CpxV = CpxH * SpxV / SpxH
         //                                                               CpxV = CV   * SpxV / SV
         //                                                               captureVfov = 2 * atan( tan(captureHfov / 2) * (SpxV / SpxH) )
         sliceHFov = hAngIncrement;
         sliceVFov = vAngIncrement;
 
         //TODO: ikrimae: Also do a quick test to see if there are issues with setting fov to something really small ( < 1 degree)
         //               And it does. Current noted issues: screen space effects like SSAO, AA, SSR are all off
         //                                                  local eyeadaptation also causes problems. Should probably turn off all PostProcess effects
         //                                                  small fovs cause floating point errors in the sampling function (probably a bug b/c no thought put towards that)
         captureHFov = FStereoPanoramaManager::CaptureHorizontalFOV->GetFloat();
 
         ensure(captureHFov >= hAngIncrement);
 
         //TODO: ikrimae: In hindsight, there's no reason that strip size should be this at all. Just select a square FOV larger than hAngIncr & vAngIncr
         //               and then sample the resulting plane accordingly. Remember when updating to this to recheck the math in resample function. Might
         //               have made assumptions about capture slice dimensions matching the sample strips
         StripWidth = SphericalAtlasWidth / NumberOfHorizontalSteps;
         //The scenecapture cube won't allow horizontal & vertical fov to not match the aspect ratio so we have to compute the right dimensions here for square pixels
         StripHeight = StripWidth * FMath::Tan(FMath::DegreesToRadians(vAngIncrement / 2.0f)) / FMath::Tan(FMath::DegreesToRadians(hAngIncrement / 2.0f));
 
         const FVector2D slicePlaneDim = FVector2D(
             2.0f * FMath::Tan(FMath::DegreesToRadians(hAngIncrement) / 2.0f),
             2.0f * FMath::Tan(FMath::DegreesToRadians(vAngIncrement) / 2.0f));
 
         const float capturePlaneWidth = 2.0f * FMath::Tan(FMath::DegreesToRadians(captureHFov) / 2.0f);
 
         //TODO: ikrimae: This is just to let the rest of the existing code work. Sampling rate of the slice can be whatever.
         //      Ex: To match the highest sampling frequency of the spherical atlas, it should match the area of differential patch
         //      at ray direction of pixel(0,1) in the atlas
 
         //Need stripwidth/slicePlaneDim.X = capturewidth / capturePlaneDim.X
         CaptureWidth = capturePlaneWidth * StripWidth / slicePlaneDim.X;
         CaptureHeight = CaptureWidth * StripHeight / StripWidth;
 
         captureVFov = FMath::RadiansToDegrees(2 * FMath::Atan(FMath::Tan(FMath::DegreesToRadians(captureHFov / 2.0f)) * CaptureHeight / CaptureWidth));
 
         //float dbgCapturePlaneDimY = 2.0f * FMath::Tan(FMath::DegreesToRadians(captureVFov) / 2.0f);
         //float dbgCaptureHeight = dbgCapturePlaneDimY * StripHeight / slicePlaneDim.Y;
     }
     else
     {
         //Slicing Technique 2: Each slice is a determined square FOV at a configured preset resolution.
         //                     Strip Width/Strip Height is determined based on hAngIncrement & vAngIncrement
         //                     Just make sure pixels/captureHFov >= pixels/hAngIncr && pixels/vAngIncr
 
         captureVFov = captureHFov = FStereoPanoramaManager::CaptureHorizontalFOV->GetFloat();
         sliceVFov   = sliceHFov   = captureHFov;
 
         ensure(captureHFov >= FMath::Max(hAngIncrement, vAngIncrement));
         
         //TODO: ikrimae: Re-do for floating point accuracy
         const FVector2D slicePlaneDim = FVector2D(
             2.0f * FMath::Tan(FMath::DegreesToRadians(hAngIncrement) / 2.0f),
             2.0f * FMath::Tan(FMath::DegreesToRadians(vAngIncrement) / 2.0f));
 
         const FVector2D capturePlaneDim = FVector2D(
             2.0f * FMath::Tan(FMath::DegreesToRadians(captureHFov) / 2.0f),
             2.0f * FMath::Tan(FMath::DegreesToRadians(captureVFov) / 2.0f));
 
         CaptureHeight = CaptureWidth = FStereoPanoramaManager::CaptureSlicePixelWidth->GetInt();
 
         StripWidth  = CaptureWidth  * slicePlaneDim.X / capturePlaneDim.X;
         StripHeight = CaptureHeight * slicePlaneDim.Y / capturePlaneDim.Y;
 
         //TODO: ikrimae: Come back and check for the actual right sampling rate
         check(StripWidth  >=  (SphericalAtlasWidth / NumberOfHorizontalSteps) && 
               StripHeight >= (SphericalAtlasHeight / NumberOfVerticalSteps));
         
         //Ensure Width/Height is always even
         StripWidth  += StripWidth & 1;
         StripHeight += StripHeight & 1;
 
     }
 
     UnprojectedAtlasWidth  = NumberOfHorizontalSteps * StripWidth;
     UnprojectedAtlasHeight = NumberOfVerticalSteps   * StripHeight;
 
     //NOTE: ikrimae: Ensure that the main gameview is > CaptureWidth x CaptureHeight. Bug in UE4 that won't re-alloc scene render targets to the correct size
     //               when the scenecapture component > current window render target. https://answers.unrealengine.com/questions/80531/scene-capture-2d-max-resolution.html
     //TODO: ikrimae: Ensure that r.SceneRenderTargetResizeMethod=2
     FSystemResolution::RequestResolutionChange(CaptureWidth, CaptureHeight, EWindowMode::Windowed);
 
 
 
     for( int CaptureIndex = 0; CaptureIndex < FStereoPanoramaManager::ConcurrentCaptures->GetInt(); CaptureIndex++ )
     {
         FString LeftCounter = FString::Printf( TEXT( "LeftEyeCaptureComponent_%04d" ), CaptureIndex );
         USceneCaptureComponent2D* LeftEyeCaptureComponent = CreateDefaultSubobject<USceneCaptureComponent2D>( *LeftCounter );
         InitCaptureComponent( LeftEyeCaptureComponent, captureHFov, captureVFov, EStereoscopicPass::eSSP_LEFT_EYE );
         LeftEyeCaptureComponents.Add( LeftEyeCaptureComponent );
 
         FString RightCounter = FString::Printf( TEXT( "RightEyeCaptureComponent_%04d" ), CaptureIndex );
         USceneCaptureComponent2D* RightEyeCaptureComponent = CreateDefaultSubobject<USceneCaptureComponent2D>( *RightCounter );
         InitCaptureComponent( RightEyeCaptureComponent, captureHFov, captureVFov, EStereoscopicPass::eSSP_RIGHT_EYE );
         RightEyeCaptureComponents.Add( RightEyeCaptureComponent );
     }
 
     CurrentStep = 0;
     TotalSteps = 0;
     FrameDescriptors = TEXT( "FrameNumber, GameClock, TimeTaken(s)" LINE_TERMINATOR );
 
     CaptureStep = ECaptureStep::Reset;
 }
 
 void USceneCapturer::Reset()
 {
     for( int CaptureIndex = 0; CaptureIndex < FStereoPanoramaManager::ConcurrentCaptures->GetInt(); CaptureIndex++ )
     {
         USceneCaptureComponent2D* LeftEyeCaptureComponent = LeftEyeCaptureComponents[CaptureIndex];
         USceneCaptureComponent2D* RightEyeCaptureComponent = RightEyeCaptureComponents[CaptureIndex];
 
         LeftEyeCaptureComponent->SetVisibility( false );
         LeftEyeCaptureComponent->SetHiddenInGame( true );
         
         // UE4 cannot serialize an array of subobject pointers, so work around the GC problems
         LeftEyeCaptureComponent->RemoveFromRoot();
 
         RightEyeCaptureComponent->SetVisibility( false );
         RightEyeCaptureComponent->SetHiddenInGame( true );
         
         // UE4 cannot serialize an array of subobject pointers, so work around the GC problems
         RightEyeCaptureComponent->RemoveFromRoot();
     }
 
     UnprojectedLeftEyeAtlas.Empty();
     UnprojectedRightEyeAtlas.Empty();
 }
 
 void USceneCapturer::SetPositionAndRotation( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, int32 CaptureIndex )
 {
     FRotator Rotation = StartRotation;
     Rotation.Yaw += CurrentHorizontalStep * hAngIncrement;
     Rotation.Pitch -= CurrentVerticalStep * vAngIncrement;
 
     Rotation = Rotation.Clamp();
 
     FVector Offset( 0.0f, eyeSeparation / 2.0f, 0.0f );
     if (dbgDisableOffsetRotation)
     {
         //For rendering near field objects, we don't rotate the capture components around the stereo pivot, but instead
         //around each capture component
         const auto rotAngleOffset = FRotator::ClampAxis(Rotation.Yaw - StartRotation.Yaw);
         float eyeSeparationDampeningFactor = 1.0f;
         if (rotAngleOffset <= 90.0f)
         {
             eyeSeparationDampeningFactor = FMath::Lerp(1.0f, 0.0f, rotAngleOffset / 90.0f);
         }
         else if (rotAngleOffset <= 270.0f)
         {
             eyeSeparationDampeningFactor = 0.0f;
         }
         else
         {
             eyeSeparationDampeningFactor = FMath::Lerp(0.0f, 1.0f, (rotAngleOffset - 270.0f) / 90.0f);
         }
 
         Offset = StartRotation.RotateVector(Offset * eyeSeparationDampeningFactor);
     }
     else
     {
         Offset = Rotation.RotateVector(Offset);
     }
 
     LeftEyeCaptureComponents[CaptureIndex]->SetWorldLocationAndRotation( StartLocation - Offset, Rotation );
     LeftEyeCaptureComponents[CaptureIndex]->UpdateContent();
     RightEyeCaptureComponents[CaptureIndex]->SetWorldLocationAndRotation( StartLocation + Offset, Rotation );
     RightEyeCaptureComponents[CaptureIndex]->UpdateContent();
 }
 
 void USceneCapturer::ValidateParameters()
 {
     // Angular increment needs to be a factor of 360 to avoid seams i.e. 360 / angular increment needs to be a whole number
     if( ( int32 )( NumberOfHorizontalSteps * hAngIncrement ) != 360 )
     {
         UE_LOG( LogStereoPanorama, Warning, TEXT( "Horizontal angular step (%g) is not a factor of 360! This will lead to a seam between the start and end points" ), hAngIncrement );
     }
 
     if( ( int32 )( (NumberOfVerticalSteps - 1) * vAngIncrement ) != 180 )
     {
         UE_LOG( LogStereoPanorama, Warning, TEXT( "Vertical angular step (%g) is not a factor of 180! This will lead to a seam between the start and end points" ), vAngIncrement );
     }
 
     TotalSteps = NumberOfHorizontalSteps * NumberOfVerticalSteps;
     if( ( SphericalAtlasWidth & 1 ) != 0)
     {
         UE_LOG(LogStereoPanorama, Warning, TEXT("The Atlas Width (%d) must be even! Otherwise the Atlas height will not divide evenly."), SphericalAtlasWidth);
     }
 
 
     // The strip width needs to be an even number and a factor of the number of steps
     if( ( StripWidth & 1 ) != 0 )
     {
         UE_LOG( LogStereoPanorama, Warning, TEXT( "Strip width (%d) needs to be even to avoid bad offsets" ), StripWidth );
     }
 
     if( StripWidth * NumberOfHorizontalSteps != SphericalAtlasWidth )
     {
         UE_LOG( LogStereoPanorama, Warning, TEXT( "The number of horizontal steps (%d) needs to be a factor of the atlas width (%d)" ), NumberOfHorizontalSteps, SphericalAtlasWidth );
     }
 
     if ((StripHeight & 1) != 0)
     {
         UE_LOG(LogStereoPanorama, Warning, TEXT("Strip height (%d) needs to be even to avoid bad offsets"), StripHeight);
     }
 
     if (StripHeight * (NumberOfVerticalSteps - 1) != SphericalAtlasHeight)
     {
         UE_LOG(LogStereoPanorama, Warning, TEXT("The number of vertical steps (%d) needs to be a factor of the atlas height (%d)"), NumberOfVerticalSteps, SphericalAtlasHeight);
     }
 
     //TODO: ikrimae: Validate capturewidth & captureheight. Need to be even
 
     UE_LOG( LogStereoPanorama, Display, TEXT( "Stereo panoramic screenshot parameters" ) );
     UE_LOG( LogStereoPanorama, Display, TEXT( " ... capture size: %d x %d" ), CaptureWidth, CaptureHeight );
     UE_LOG( LogStereoPanorama, Display, TEXT( " ... spherical atlas size: %d x %d" ), SphericalAtlasWidth, SphericalAtlasHeight );
     UE_LOG( LogStereoPanorama, Display, TEXT( " ... intermediate atlas size: %d x %d" ), UnprojectedAtlasWidth, UnprojectedAtlasHeight );
     UE_LOG( LogStereoPanorama, Display, TEXT( " ... strip size: %d x %d" ), StripWidth, StripHeight );
     UE_LOG( LogStereoPanorama, Display, TEXT( " ... horizontal steps: %d at %g degrees" ), NumberOfHorizontalSteps, hAngIncrement );
     UE_LOG( LogStereoPanorama, Display, TEXT( " ... vertical steps: %d at %g degrees" ), NumberOfVerticalSteps, vAngIncrement );
 }
 
 void USceneCapturer::SetInitialState( int32 InStartFrame, int32 InEndFrame, FStereoCaptureDoneDelegate& InStereoCaptureDoneDelegate )
 {
     if( bIsTicking )
     {
         UE_LOG( LogStereoPanorama, Warning, TEXT( "Already capturing a scene; concurrent captures are not allowed" ) );
         return;
     }
 
     CapturePlayerController = UGameplayStatics::GetPlayerController( GWorld, 0 );
     CaptureGameMode = UGameplayStatics::GetGameMode( GWorld );
 
     if( CaptureGameMode == NULL || CapturePlayerController == NULL )
     {
         UE_LOG( LogStereoPanorama, Warning, TEXT( "Missing GameMode or PlayerController" ) );
         return;
     }
 
     // Calculate the steps and validate they will produce good results
     ValidateParameters();
 
     // Setup starting criteria
     StartFrame        = InStartFrame;
     EndFrame          = InEndFrame;
     CurrentFrameCount = 0;
     CurrentStep       = 0;
     CaptureStep       = ECaptureStep::Unpause;
 
     Timestamp = FString::Printf( TEXT( "%s" ), *FDateTime::Now().ToString() );
     
     //SetStartPosition();
 
     // Create storage for atlas textures
     check( UnprojectedAtlasWidth * UnprojectedAtlasHeight <= MAX_int32 );
     UnprojectedLeftEyeAtlas.AddUninitialized(  UnprojectedAtlasWidth * UnprojectedAtlasHeight );
     UnprojectedRightEyeAtlas.AddUninitialized( UnprojectedAtlasWidth * UnprojectedAtlasHeight );
 
     StartTime        = FDateTime::UtcNow();
     OverallStartTime = StartTime;
     bIsTicking       = true;
 
     StereoCaptureDoneDelegate = InStereoCaptureDoneDelegate;
 }
 
 void USceneCapturer::CopyToUnprojAtlas( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, TArray<FColor>& Atlas, TArray<FColor>& SurfaceData )
 {
     int32 XOffset = StripWidth * CurrentHorizontalStep;
     int32 YOffset = StripHeight * CurrentVerticalStep;
 
     int32 StripSize = StripWidth * sizeof( FColor );
     for (int32 Y = 0; Y < StripHeight; Y++)
     {
         void* Destination = &Atlas[( ( Y + YOffset ) * UnprojectedAtlasWidth ) + XOffset];
         void* Source = &SurfaceData[StripWidth * Y];
         FMemory::Memcpy( Destination, Source, StripSize );
     }
 }
 
 TArray<FColor> USceneCapturer::SaveAtlas(FString Folder, const TArray<FColor>& SurfaceData)
 - const bool CombineAtlasesOnOutput = true;
 {
     SCOPE_CYCLE_COUNTER( STAT_SPSavePNG );
     
     TArray<FColor> SphericalAtlas;
     SphericalAtlas.AddZeroed(SphericalAtlasWidth * SphericalAtlasHeight);
 
     const FVector2D slicePlaneDim = FVector2D(
         2.0f * FMath::Tan(FMath::DegreesToRadians(sliceHFov) / 2.0f),
         2.0f * FMath::Tan(FMath::DegreesToRadians(sliceVFov) / 2.0f));
 
     //For each direction,
     //    Find corresponding slice
     //    Calculate intersection of slice plane
     //    Calculate intersection UVs by projecting onto plane tangents
     //    Supersample that UV coordinate from the unprojected atlas
     {
         SCOPE_CYCLE_COUNTER(STAT_SPSampleSpherical);
         // Dump out how long the process took
         const FDateTime SamplingStartTime = FDateTime::UtcNow();
         UE_LOG(LogStereoPanorama, Log, TEXT("Sampling atlas..."));
 
         for (int32 y = 0; y < SphericalAtlasHeight; y++)
         {
             for (int32 x = 0; x < SphericalAtlasWidth; x++)
             {
                 FLinearColor samplePixelAccum = FLinearColor(0, 0, 0, 0);
 
                 //TODO: ikrimae: Seems that bilinear filtering sans supersampling is good enough. Supersampling sans bilerp seems best.
                 //               After more tests, come back to optimize by folding supersampling in and remove this outer sampling loop.
                 const auto& ssPattern = g_ssPatterns[SSMethod];
 
                 for (int32 SampleCount = 0; SampleCount < ssPattern.numSamples; SampleCount++)
                 {
                     const float sampleU = ((float)x + ssPattern.ssOffsets[SampleCount].X) / SphericalAtlasWidth;
                     const float sampleV = ((float)y + ssPattern.ssOffsets[SampleCount].Y) / SphericalAtlasHeight;
 
                     const float sampleTheta = sampleU * 360.0f;
                     const float samplePhi = sampleV * 180.0f;
 
                     const FVector sampleDir = FVector(
                         FMath::Sin(FMath::DegreesToRadians(samplePhi)) * FMath::Cos(FMath::DegreesToRadians(sampleTheta)),
                         FMath::Sin(FMath::DegreesToRadians(samplePhi)) * FMath::Sin(FMath::DegreesToRadians(sampleTheta)),
                         FMath::Cos(FMath::DegreesToRadians(samplePhi)));
 
 
                     //TODO: ikrimae: ugh, ugly.
                     const int32 sliceXIndex = FMath::TruncToInt(FRotator::ClampAxis(sampleTheta + hAngIncrement / 2.0f) / hAngIncrement);
                     int32 sliceYIndex = 0;
 
                     //Slice Selection = slice with max{sampleDir dot  sliceNormal }
                     {
                         float largestCosAngle = 0;
                         for (int VerticalStep = 0; VerticalStep < NumberOfVerticalSteps; VerticalStep++)
                         {
                             const FVector2D sliceCenterThetaPhi = FVector2D(
                                 hAngIncrement * sliceXIndex,
                                 vAngIncrement * VerticalStep);
 
                             //TODO: ikrimae: There has got to be a faster way. Rethink reparametrization later
                             const FVector sliceDir = FVector(
                                 FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                                 FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                                 FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)));
 
                             const float cosAngle = sampleDir | sliceDir;
 
                             if (cosAngle > largestCosAngle)
                             {
                                 largestCosAngle = cosAngle;
                                 sliceYIndex = VerticalStep;
                             }
                         }
                     }
 
 
                     const FVector2D sliceCenterThetaPhi = FVector2D(
                         hAngIncrement * sliceXIndex,
                         vAngIncrement * sliceYIndex);
 
                     //TODO: ikrimae: Reparameterize with an inverse mapping (e.g. project from slice pixels onto final u,v coordinates.
                     //               Should make code simpler and faster b/c reduces to handful of sin/cos calcs per slice. 
                     //               Supersampling will be more difficult though.
 
                     const FVector sliceDir = FVector(
                         FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                         FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                         FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)));
 
                     const FPlane slicePlane = FPlane(sliceDir, -sliceDir);
 
                     //Tangents from partial derivatives of sphere equation
                     const FVector slicePlanePhiTangent = FVector(
                         FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                         FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                         -FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))).GetSafeNormal();
 
                     //Should be reconstructed to get around discontinuity of theta tangent at nodal points
                     const FVector slicePlaneThetaTangent = (sliceDir ^ slicePlanePhiTangent).GetSafeNormal();
                     //const FVector slicePlaneThetaTangent = FVector(
                     //    -FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                     //    FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)),
                     //    0).SafeNormal();
 
                     check(!slicePlaneThetaTangent.IsZero() && !slicePlanePhiTangent.IsZero());
 
                     const double t = (double)-slicePlane.W / (sampleDir | sliceDir);
                     const FVector sliceIntersection = FVector(t * sampleDir.X, t * sampleDir.Y, t * sampleDir.Z);
 
                     //Calculate scalar projection of sliceIntersection onto tangent vectors. a dot b / |b| = a dot b when tangent vectors are normalized
                     //Then reparameterize to U,V of the sliceplane based on slice plane dimensions
                     const float sliceU = (sliceIntersection | slicePlaneThetaTangent) / slicePlaneDim.X;
                     const float sliceV = (sliceIntersection | slicePlanePhiTangent) / slicePlaneDim.Y;
 
                     check(sliceU >= -(0.5f + KINDA_SMALL_NUMBER) &&
                         sliceU <= (0.5f + KINDA_SMALL_NUMBER));
 
                     check(sliceV >= -(0.5f + KINDA_SMALL_NUMBER) &&
                         sliceV <= (0.5f + KINDA_SMALL_NUMBER));
 
                     //TODO: ikrimae: Supersample/bilinear filter
                     const int32 slicePixelX = FMath::TruncToInt(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceU * StripWidth : sliceU * CaptureWidth);
                     const int32 slicePixelY = FMath::TruncToInt(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceV * StripHeight : sliceV * CaptureHeight);
 
                     FLinearColor slicePixelSample;
 
                     if (bEnableBilerp)
                     {
                         //TODO: ikrimae: Clean up later; too tired now
                         const int32 sliceCenterPixelX = (sliceXIndex + 0.5f) * StripWidth;
                         const int32 sliceCenterPixelY = (sliceYIndex + 0.5f) * StripHeight;
 
                         const FIntPoint atlasSampleTL(sliceCenterPixelX + FMath::Clamp(slicePixelX    , -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY    , -StripHeight/2, StripHeight/2));
                         const FIntPoint atlasSampleTR(sliceCenterPixelX + FMath::Clamp(slicePixelX + 1, -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY    , -StripHeight/2, StripHeight/2));
                         const FIntPoint atlasSampleBL(sliceCenterPixelX + FMath::Clamp(slicePixelX    , -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY + 1, -StripHeight/2, StripHeight/2));
                         const FIntPoint atlasSampleBR(sliceCenterPixelX + FMath::Clamp(slicePixelX + 1, -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY + 1, -StripHeight/2, StripHeight/2));
 
                         const FColor pixelColorTL = SurfaceData[atlasSampleTL.Y * UnprojectedAtlasWidth + atlasSampleTL.X];
                         const FColor pixelColorTR = SurfaceData[atlasSampleTR.Y * UnprojectedAtlasWidth + atlasSampleTR.X];
                         const FColor pixelColorBL = SurfaceData[atlasSampleBL.Y * UnprojectedAtlasWidth + atlasSampleBL.X];
                         const FColor pixelColorBR = SurfaceData[atlasSampleBR.Y * UnprojectedAtlasWidth + atlasSampleBR.X];
 
                         const float fracX = FMath::Frac(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceU * StripWidth : sliceU * CaptureWidth);
                         const float fracY = FMath::Frac(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceV * StripHeight : sliceV * CaptureHeight);
 
                         //Reinterpret as linear (a.k.a dont apply srgb inversion)
                         slicePixelSample = FMath::BiLerp(
                             pixelColorTL.ReinterpretAsLinear(), pixelColorTR.ReinterpretAsLinear(),
                             pixelColorBL.ReinterpretAsLinear(), pixelColorBR.ReinterpretAsLinear(),
                             fracX, fracY);
                     }
                     else
                     {
                         const int32 sliceCenterPixelX = (sliceXIndex + 0.5f) * StripWidth;
                         const int32 sliceCenterPixelY = (sliceYIndex + 0.5f) * StripHeight;
 
                         const int32 atlasSampleX = sliceCenterPixelX + slicePixelX;
                         const int32 atlasSampleY = sliceCenterPixelY + slicePixelY;
 
 
                         slicePixelSample = SurfaceData[atlasSampleY * UnprojectedAtlasWidth + atlasSampleX].ReinterpretAsLinear();
                     }
 
                     samplePixelAccum += slicePixelSample;
 
                     ////Output color map of projections
                     //const FColor debugEquiColors[12] = {
                     //    FColor(205, 180, 76),
                     //    FColor(190, 88, 202),
                     //    FColor(127, 185, 194),
                     //    FColor(90, 54, 47),
                     //    FColor(197, 88, 53),
                     //    FColor(197, 75, 124),
                     //    FColor(130, 208, 72),
                     //    FColor(136, 211, 153),
                     //    FColor(126, 130, 207),
                     //    FColor(83, 107, 59),
                     //    FColor(200, 160, 157),
                     //    FColor(80, 66, 106)
                     //};
 
                     //samplePixelAccum = ssPattern.numSamples * debugEquiColors[sliceYIndex * 4 + sliceXIndex];
                 }
 
                 SphericalAtlas[y * SphericalAtlasWidth + x] = (samplePixelAccum / ssPattern.numSamples).Quantize();
 
                 // Force alpha value
                 if (bForceAlpha)
                 {
                     SphericalAtlas[y * SphericalAtlasWidth + x].A = 255;
                 }
             }
         }
 
         //Blit the first column into the last column to make the stereo image seamless at theta=360
         for (int32 y = 0; y < SphericalAtlasHeight; y++)
         {
             SphericalAtlas[y * SphericalAtlasWidth + (SphericalAtlasWidth - 1)] = SphericalAtlas[y * SphericalAtlasWidth + 0];
         }
 
         const FTimespan SamplingDuration = FDateTime::UtcNow() - SamplingStartTime;
         UE_LOG(LogStereoPanorama, Log, TEXT("...done! Duration: %g seconds"), SamplingDuration.GetTotalSeconds());
     }
     
     // Generate name
     FString FrameString = FString::Printf( TEXT( "%s_%05d.png" ), *Folder, CurrentFrameCount );
     FString AtlasName =  OutputDir / Timestamp / FrameString;
     
     UE_LOG( LogStereoPanorama, Log, TEXT( "Writing atlas: %s" ), *AtlasName );
 
     // Write out PNG
     //TODO: ikrimae: Use threads to write out the images for performance
     IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
 
     if (!CombineAtlasesOnOutput) //*NEW* - Don't do this here if we're going to combine them.
 
     {
 
         ImageWrapper->SetRaw(SphericalAtlas.GetData(), SphericalAtlas.GetAllocatedSize(), SphericalAtlasWidth, SphericalAtlasHeight, ERGBFormat::BGRA, 8);
 
         const TArray& PNGData = ImageWrapper->GetCompressed(100);
 
         FFileHelper::SaveArrayToFile(PNGData, *AtlasName);
 
     }
 
     if (FStereoPanoramaManager::GenerateDebugImages->GetInt() != 0)
     {
         FString FrameStringUnprojected = FString::Printf(TEXT("%s_%05d_Unprojected.png"), *Folder, CurrentFrameCount);
         FString AtlasNameUnprojected = OutputDir / Timestamp / FrameStringUnprojected;
 
         ImageWrapper->SetRaw(SurfaceData.GetData(), SurfaceData.GetAllocatedSize(), UnprojectedAtlasWidth, UnprojectedAtlasHeight, ERGBFormat::BGRA, 8);
         const TArray<uint8>& PNGDataUnprojected = ImageWrapper->GetCompressed(100);
         FFileHelper::SaveArrayToFile(PNGDataUnprojected, *AtlasNameUnprojected);
     }
     ImageWrapper.Reset();
 
     UE_LOG( LogStereoPanorama, Log, TEXT( " ... done!" ), *AtlasName );
 
     return SphericalAtlas;
 }
 
 void USceneCapturer::CaptureComponent( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, FString Folder, USceneCaptureComponent2D* CaptureComponent, TArray<FColor>& Atlas )
 {
     TArray<FColor> SurfaceData;
 
     {
         SCOPE_CYCLE_COUNTER( STAT_SPReadStrip );
         FTextureRenderTargetResource* RenderTarget = CaptureComponent->TextureTarget->GameThread_GetRenderTargetResource();
 
         //TODO: ikrimae: Might need to validate that this divides evenly. Might not matter
         int32 CenterX = CaptureWidth / 2;
         int32 CenterY = CaptureHeight / 2;
 
         SurfaceData.AddUninitialized( StripWidth * StripHeight );
 
         // Read pixels
         FIntRect Area( CenterX - ( StripWidth / 2 ), CenterY - ( StripHeight / 2 ), CenterX + ( StripWidth / 2 ), CenterY + ( StripHeight / 2) );
         auto readSurfaceDataFlags = FReadSurfaceDataFlags();
         readSurfaceDataFlags.SetLinearToGamma(false);
         RenderTarget->ReadPixelsPtr( SurfaceData.GetData(), readSurfaceDataFlags, Area );
     }
 
     // Copy off strip to atlas texture
     CopyToUnprojAtlas( CurrentHorizontalStep, CurrentVerticalStep, Atlas, SurfaceData );
 
     if( FStereoPanoramaManager::GenerateDebugImages->GetInt() != 0 )
     {
         SCOPE_CYCLE_COUNTER( STAT_SPSavePNG );
 
         // Generate name
         FString TickString = FString::Printf( TEXT( "_%05d_%04d_%04d" ), CurrentFrameCount, CurrentHorizontalStep, CurrentVerticalStep );
         FString CaptureName = OutputDir / Timestamp / Folder / TickString + TEXT( ".png" );
         UE_LOG( LogStereoPanorama, Log, TEXT( "Writing snapshot: %s" ), *CaptureName );
 
         // Write out PNG
         if (FStereoPanoramaManager::GenerateDebugImages->GetInt() == 2)
         {
             //Read Whole Capture Buffer
             IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper( EImageFormat::PNG );
 
             TArray<FColor> SurfaceDataWhole;
             SurfaceDataWhole.AddUninitialized(CaptureWidth * CaptureHeight);
             // Read pixels
             FTextureRenderTargetResource* RenderTarget = CaptureComponent->TextureTarget->GameThread_GetRenderTargetResource();
             RenderTarget->ReadPixelsPtr(SurfaceDataWhole.GetData(), FReadSurfaceDataFlags());
 
             // Force alpha value
             if (bForceAlpha)
             {
                 for (FColor& Color : SurfaceDataWhole)
                 {
                     Color.A = 255;
                 }
             }
 
             ImageWrapper->SetRaw(SurfaceDataWhole.GetData(), SurfaceDataWhole.GetAllocatedSize(), CaptureWidth, CaptureHeight, ERGBFormat::BGRA, 8);
             const TArray<uint8>& PNGData = ImageWrapper->GetCompressed(100);
 
             FFileHelper::SaveArrayToFile(PNGData, *CaptureName);
             ImageWrapper.Reset();
         }
         else
         {
             if (bForceAlpha)
             {
                 for (FColor& Color : SurfaceData)
                 {
                     Color.A = 255;
                 }
             }
 
             IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG);
             ImageWrapper->SetRaw(SurfaceData.GetData(), SurfaceData.GetAllocatedSize(), StripWidth, StripHeight, ERGBFormat::BGRA, 8);
             const TArray<uint8>& PNGData = ImageWrapper->GetCompressed(100);
 
             FFileHelper::SaveArrayToFile( PNGData, *CaptureName );
             ImageWrapper.Reset();
         }
     }
 }
 
 //TODO: ikrimae: Come back and actually work out the timings. Trickery b/c SceneCaptureCubes Tick at the end of the frame so we're effectively queuing up the next
 //               step (pause, unpause, setposition) for the next frame. FlushRenderingCommands() added haphazardly to test but didn't want to remove them so close to delivery. 
 //               Think through when we actually need to flush and document.
 void USceneCapturer::Tick( float DeltaTime )
 {
     if( !bIsTicking )
     {
         return;
     }
 
     if ( CurrentFrameCount < StartFrame )
     {
         //Skip until we're at the frame we want to render
         CurrentFrameCount++;
         CaptureStep = ECaptureStep::Pause;
     }
     else if( CurrentStep < TotalSteps )
     {
         if (CaptureStep == ECaptureStep::Unpause)
         {
             FlushRenderingCommands();
             CaptureGameMode->ClearPause();
             //GPauseRenderingRealtimeClock = false;
             CaptureStep = ECaptureStep::Pause;
             FlushRenderingCommands();
         }
         else if (CaptureStep == ECaptureStep::Pause)
         {
             FlushRenderingCommands();
             CaptureGameMode->SetPause(CapturePlayerController);
             //GPauseRenderingRealtimeClock = true;
             CaptureStep = ECaptureStep::SetStartPosition;
             FlushRenderingCommands();
         }
         else if (CaptureStep == ECaptureStep::SetStartPosition)
         {
             //SetStartPosition();
             ENQUEUE_UNIQUE_RENDER_COMMAND(
                 SceneCapturer_HeartbeatTickTickables,
             {
                 TickRenderingTickables();
             });
 
             FlushRenderingCommands();
             
             FRotator Rotation;
             CapturePlayerController->GetPlayerViewPoint(StartLocation, Rotation);
             
             Rotation.Roll = 0.0f;
             Rotation.Yaw = (bOverrideInitialYaw) ? ForcedInitialYaw : Rotation.Yaw;
             Rotation.Pitch = 90.0f;
             StartRotation = Rotation;
             CaptureStep = ECaptureStep::SetPosition;
             FlushRenderingCommands();
         }
         else if (CaptureStep == ECaptureStep::SetPosition)
         {
             FlushRenderingCommands();
             for (int32 CaptureIndex = 0; CaptureIndex < FStereoPanoramaManager::ConcurrentCaptures->GetInt(); CaptureIndex++)
             {
                 int32 CurrentHorizontalStep;
                 int32 CurrentVerticalStep;
                 if (GetComponentSteps(CurrentStep + CaptureIndex, CurrentHorizontalStep, CurrentVerticalStep))
                 {
                     SetPositionAndRotation(CurrentHorizontalStep, CurrentVerticalStep, CaptureIndex);
                 }
             }
 
             CaptureStep = ECaptureStep::Read;
             FlushRenderingCommands();
         }
         else if (CaptureStep == ECaptureStep::Read)
         {
             FlushRenderingCommands();
             for (int32 CaptureIndex = 0; CaptureIndex < FStereoPanoramaManager::ConcurrentCaptures->GetInt(); CaptureIndex++)
             {
                 int32 CurrentHorizontalStep;
                 int32 CurrentVerticalStep;
                 if (GetComponentSteps(CurrentStep, CurrentHorizontalStep, CurrentVerticalStep))
                 {
                     CaptureComponent(CurrentHorizontalStep, CurrentVerticalStep, TEXT("Left"), LeftEyeCaptureComponents[CaptureIndex], UnprojectedLeftEyeAtlas);
                     CaptureComponent(CurrentHorizontalStep, CurrentVerticalStep, TEXT("Right"), RightEyeCaptureComponents[CaptureIndex], UnprojectedRightEyeAtlas);
 
                     CurrentStep++;
                 }
             }
 
             CaptureStep = ECaptureStep::SetPosition;
             FlushRenderingCommands();
         }
         else
         {
             //ECaptureStep::Reset:
         }
     }
     else
     {
         TArray<FColor> SphericalLeftEyeAtlas  = SaveAtlas( TEXT( "Left" ), UnprojectedLeftEyeAtlas );
         TArray<FColor> SphericalRightEyeAtlas = SaveAtlas(TEXT("Right"), UnprojectedRightEyeAtlas);
         //*NEW* - Begin
 
         if (CombineAtlasesOnOutput)
 
         {
 
             TArray CombinedAtlas;
 
             CombinedAtlas.Append(SphericalLeftEyeAtlas);
 
             CombinedAtlas.Append(SphericalRightEyeAtlas);
 
             IImageWrapperPtr ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::JPEG);
 
             ImageWrapper->SetRaw(CombinedAtlas.GetData(), CombinedAtlas.GetAllocatedSize(), SphericalAtlasWidth, SphericalAtlasHeight * 2, ERGBFormat::BGRA, 8);
 
             const TArray& PNGData = ImageWrapper->GetCompressed(100);
 
             // Generate name
 
             FString FrameString = FString::Printf(TEXT("Frame_%05d.jpg"), CurrentFrameCount);
 
             FString AtlasName = OutputDir / Timestamp / FrameString;
 
             FFileHelper::SaveArrayToFile(PNGData, *AtlasName);
 
             ImageWrapper.Reset();
 
         }
 
         //*NEW* - END
         // Dump out how long the process took
         FDateTime EndTime = FDateTime::UtcNow();
         FTimespan Duration = EndTime - StartTime;
         UE_LOG( LogStereoPanorama, Log, TEXT( "Duration: %g seconds for frame %d" ), Duration.GetTotalSeconds(), CurrentFrameCount );
         StartTime = EndTime;
 
         //NOTE: ikrimae: Since we can't synchronously finish a stereocapture, we have to notify the caller with a function pointer
         //Not sure this is the cleanest way but good enough for now
         StereoCaptureDoneDelegate.ExecuteIfBound(SphericalLeftEyeAtlas, SphericalRightEyeAtlas);
 
         // Construct log of saved atlases in csv format
         FrameDescriptors += FString::Printf( TEXT( "%d, %g, %g" LINE_TERMINATOR ), CurrentFrameCount, FApp::GetCurrentTime() - FApp::GetLastTime(), Duration.GetTotalSeconds() );
 
         CurrentFrameCount++;
         if( CurrentFrameCount <= EndFrame )
         {
             CurrentStep = 0;
             CaptureStep = ECaptureStep::Unpause;
         }
         else
         {
             CaptureGameMode->ClearPause();
             //GPauseRenderingRealtimeClock = false;
 
             FTimespan OverallDuration = FDateTime::UtcNow() - OverallStartTime;
 
             FrameDescriptors += FString::Printf(TEXT("Duration: %g minutes for frame range [%d,%d] "), OverallDuration.GetTotalMinutes(), StartFrame, EndFrame);;
             UE_LOG( LogStereoPanorama, Log, TEXT("Duration: %g minutes for frame range [%d,%d] "), OverallDuration.GetTotalMinutes(), StartFrame, EndFrame );
 
             FString FrameDescriptorName = OutputDir / Timestamp / TEXT( "Frames.txt" );
             FFileHelper::SaveStringToFile( FrameDescriptors, *FrameDescriptorName, FFileHelper::EEncodingOptions::ForceUTF8 );
 
             bIsTicking = false;
             FStereoPanoramaModule::Get()->Cleanup();
         }
     }
 }
 

Product Version: UE 4.12
Tags:
more ▼

asked Aug 01 '16 at 02:02 PM in VR

avatar image

fdslk
72 11 14 20

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

6 answers: sort voted first

Copy the original plugin folder from the engine directories into your project, create a Plugins folder there. Overwrite the uplugin's name so it won't collide with the original, then modify the mentioned cpp (YourProject\Plugins\StereoPanorama\Source\StereoPanorama\Private\SceneCapturer.cpp). Then start your project, right click in the Content browser, create a new C++ class (doesn't matter what it is), close the project. Right click your project file, generate visual studio files, then open the Visual Studio Solution next to it and in VS press Build/Build Solution. Worked like a charm for me.

more ▼

answered Sep 05 '16 at 03:16 PM

avatar image

Fisher007
108 14 18 25

avatar image druzhban Feb 07 '17 at 02:56 PM

Hi. I did everything according to your advice. UE4 found a new plug-in, but the call SP.PanoramicScreenshot again creates two images in PGN format. Please give the changed file SceneCapturer.cpp

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

Hi guys! I can't render a post process blendable, is there a way to call the blendable in the code? thanks!

more ▼

answered Jan 16 '17 at 11:27 PM

avatar image

Alvaro Vazquez
26 4 7 12

avatar image Eldarele Jan 19 '17 at 09:28 AM

I had the same issue you have. After digging around I found this link: https://answers.unrealengine.com/questions/407216/scenecapturecomponent-getviewstate-return-null-the.html It seems that when the bCaptureEveryFrame is set to false, which is the case when capturing stereoscope, it sets the viewstate to null. This disables the post processing volume. When changing the code in SceneCaptureComponent.cpp to:

  FSceneViewStateInterface* USceneCaptureComponent::GetViewState()
      {
          FSceneViewStateInterface* ViewStateInterface = ViewState.GetReference();
          if (ViewStateInterface == NULL)
          {
              ViewState.Allocate();
              ViewStateInterface = ViewState.GetReference();
          }
          return ViewStateInterface;
      }

You now force the viewstate to never be NULL, so now the post processing volume will work normally, including the blendable array.

avatar image Alvaro Vazquez Jan 19 '17 at 07:47 PM

Hi! thanks for response. I modified the SceneCaptureComponent.cpp (in ue4-releases+4.10\engine\source\runtime\engine\private\components\scenecapturecomponent.cpp right?) , but still didn't work. Maybe i'm doing something wrong? Sorry but i'm not a programmer and i'm struggling hard to make this work. Thanks a lot!

avatar image Eldarele Jan 20 '17 at 09:35 AM

Yes that's the correct file. Maybe an obvious question, but did you build the UE4 project and then restarted the editor? Else the changes won't be compiled. Did you also add all the other code pieces from the article? Are you sure the post processing material works just in the regular editor? Maybe its a version issue. I am personally on 4.14, I see you are on 4.10. Maybe try updating the project? Let me hear back from you if any if this worked. Cheers!

avatar image Alvaro Vazquez Feb 16 '17 at 07:23 PM

Hi! i tried everything and i couldn't make it work. Can you possibly share the SceneCaptureComponent.cpp file so i can try? Many thanks! cheers

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

same problem here.

more ▼

answered Aug 02 '16 at 07:06 PM

avatar image

Grafíx
71 5 11 16

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

found a solution, it´s not really a solution but it looks great.

Just turn of Shadows in your scene or bake them in. It looks amazing without shadows, the 3d effect is awesome, a huge difference to the wobbeling one eyed shadow effect. try it out, hopefully it´ll look great in your rendering too.

more ▼

answered Aug 06 '16 at 09:20 PM

avatar image

Grafíx
71 5 11 16

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

I had to download the source and rewrote nearly the whole code.

more ▼

answered Sep 20 '16 at 09:45 PM

avatar image

fdslk
72 11 14 20

avatar image Bimbachi Oct 07 '16 at 06:59 PM

can you share how to ? is it possible to be used by novice? regards

(comments are locked)
10|2000 characters needed characters left
Viewable by all users

Plugin Source

I've been working out the bugs in their changes. See the attached source above. Don't forget you'll need to create a shortcut to your built version of the engine that has the appropriate startup flags:

 .../UE4Editor.exe -usefixedtimestep -fps=60 -notexturestreaming

I've added several quality settings that you can use based on their numbers.

 2kpreview | 2kaverage | 2kimproved
 4kpreview | 4kaverage | 4kimproved
 8kpreview | 8kaverage | 8kimproved
 16kpreview | 16kaverage | 16kimproved

I'm just setting the quality in my level blueprint, and executing after I begin the sequence

alt text

I'm going to try submitting my changes on github, hopefully they'll accept it, but I imagine in the next version a new crop of rendering issues will pop up that will cause some of this to fail.

EDIT: Oh and if you have a sky sphere, make sure that you don't have a directional light attached to it, that causes a bloom artifact.

nodes.png (127.8 kB)
stereopanorama.zip (22.5 kB)
more ▼

answered Oct 07 '16 at 07:27 PM

avatar image

MadisonAster
140 6 7 12

avatar image KreatiVR Feb 05 '17 at 09:30 AM

Hi. Thank you for sharing your zip file. Is it enough to just copy the files to the unreal 4.14 Plugins Source folder and overwrite the files, or is there more to do to use it? Do i have to rebuild the levels or even compile with c++? Thanks for your help and hopefully it is easy and we just have to copy it over. ;-) Martin.

avatar image MadisonAster Mar 20 '17 at 06:56 PM

I haven't had time to do much work or testing on this lately, but my final code is posted here:

https://github.com/EpicGames/UnrealEngine/pull/2846

The tricky part is you have to compile the code along with the entire engine, but the master branch is always inherently unstable. It's easier if you checkout the current release branch and then cherry-pick my commits onto the release branch.

It's a pain to maintain, but until they accept my pull request that's the only way.

(comments are locked)
10|2000 characters needed characters left
Viewable by all users
Your answer
toggle preview:

Up to 5 attachments (including images) can be used with a maximum of 5.2 MB each and 5.2 MB total.

Follow this question

Once you sign in you will be able to subscribe for any updates here

Answers to this question