├── Resources └── Icon128.png ├── .gitignore ├── Source └── StereoPano │ ├── Private │ ├── StereoPanoPrivatePCH.h │ ├── StereoPano.cpp │ ├── StereoPanoManager.h │ ├── SceneCapturer.h │ ├── StereoPanoManager.cpp │ └── SceneCapturer.cpp │ ├── Public │ └── StereoPano.h │ └── StereoPano.Build.cs ├── StereoPano.uplugin └── README.md /Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rnixik/StereoPano/HEAD/Resources/Icon128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.obj 2 | *.dll 3 | *.pdb 4 | *.generated.h 5 | Intermediate/ 6 | Binaries/ 7 | *.Template.cs -------------------------------------------------------------------------------- /Source/StereoPano/Private/StereoPanoPrivatePCH.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #include "Engine.h" 4 | #include "ModuleManager.h" 5 | #include "Tickable.h" 6 | #include "Runtime/ImageWrapper/Public/IImageWrapper.h" 7 | #include "Runtime/ImageWrapper/Public/IImageWrapperModule.h" 8 | #include "StereoPanoManager.h" 9 | #include "StereoPano.h" 10 | #include "SceneCapturer.h" 11 | 12 | -------------------------------------------------------------------------------- /Source/StereoPano/Public/StereoPano.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #pragma once 4 | 5 | class FStereoPanoModule : public IModuleInterface 6 | { 7 | private: 8 | /** IModuleInterface - initialize the module */ 9 | virtual void StartupModule() override; 10 | 11 | /** IModuleInterface - shutdown the module */ 12 | virtual void ShutdownModule() override; 13 | 14 | public: 15 | static TSharedPtr Get(); 16 | }; -------------------------------------------------------------------------------- /Source/StereoPano/StereoPano.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | namespace UnrealBuildTool.Rules 6 | { 7 | public class StereoPano : ModuleRules 8 | { 9 | public StereoPano(ReadOnlyTargetRules Target) : base(Target) 10 | { 11 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; 12 | 13 | PrivateIncludePaths.Add("Runtime/StereoPano/Private"); 14 | 15 | PrivateDependencyModuleNames.AddRange( 16 | new string[] { 17 | "Core", 18 | "CoreUObject", 19 | "Engine", 20 | "ImageWrapper", 21 | "InputCore", 22 | "RenderCore", 23 | "ShaderCore", 24 | "RHI", 25 | } 26 | ); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /StereoPano.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion" : 3, 3 | "Version" : 1, 4 | "VersionName" : "Alpha", 5 | "FriendlyName" : "Stereo Panoramic Capture", 6 | "Description" : "A plugin to capture panoramic images", 7 | "Category" : "Recording", 8 | "CreatedBy" : "Kite & Lightning, Roman Nix", 9 | "CreatedByURL" : "http://kiteandlightning.la/", 10 | "DocsURL" : "", 11 | "MarketplaceURL" : "", 12 | "SupportURL" : "", 13 | "EnabledByDefault" : false, 14 | "CanContainContent" : false, 15 | "IsBetaVersion" : false, 16 | "Installed" : false, 17 | "Modules" : 18 | [ 19 | { 20 | "Name" : "StereoPano", 21 | "Type" : "RuntimeNoCommandlet", 22 | "LoadingPhase" : "Default", 23 | "WhitelistPlatforms" : 24 | [ 25 | "Win64" 26 | ] 27 | } 28 | ] 29 | } -------------------------------------------------------------------------------- /Source/StereoPano/Private/StereoPano.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #include "StereoPano.h" 4 | #include "StereoPanoPrivatePCH.h" 5 | 6 | TSharedPtr StereoPanoManager; 7 | 8 | /** IModuleInterface - initialize the module */ 9 | void FStereoPanoModule::StartupModule() 10 | { 11 | StereoPanoManager = MakeShareable( new FStereoPanoManager() ); 12 | } 13 | 14 | /** IModuleInterface - shutdown the module */ 15 | void FStereoPanoModule::ShutdownModule() 16 | { 17 | if( StereoPanoManager.IsValid() ) 18 | { 19 | StereoPanoManager.Reset(); 20 | } 21 | } 22 | 23 | TSharedPtr FStereoPanoModule::Get() 24 | { 25 | check( StereoPanoManager.IsValid() ); 26 | return StereoPanoManager; 27 | } 28 | 29 | IMPLEMENT_MODULE( FStereoPanoModule, StereoPano ) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # StereoPano plugin for Unreal Engine 4 2 | 3 | This is fixed clone of Experimental StereoPanorama plugin for UE 4.11. 4 | Provides console command to capture 360 Stereo Panorama in spherical format. 5 | Result is two png images. 6 | 7 | This version works with UE 4.10 and supports fading stereo effect for vertical top and bottom. 8 | 9 | ## Compile 10 | 01. Copy plugin into Plugins folder under you new C++ project or existing. 11 | 02. Compile project 12 | 13 | ## Workflow 14 | 01. Copy compiled plugin into Plugins forlder under you Project folder (BP or C++) 15 | 02. Run project 16 | 03. Ensure that you have PlayerController. Move it to point where you want to capture. 17 | 04. Start project in Editor with Play 18 | 05. Open Output Window in Editor. Options are available with prefix `SP.`, main command is `SP.PanoramicScreenshot` 19 | 06. Wait (with one degree step for horizontal and verticcal can took about 2 hours) 20 | 07. Find Left_00000.png and Right_00000.png in %PROJECT_PATH%/Saved/Screenshots/Windows/%DATE%/ 21 | 22 | 23 | ##Authors 24 | Authors of original plugin are `Kite & Lightning` -------------------------------------------------------------------------------- /Source/StereoPano/Private/StereoPanoManager.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #pragma once 4 | #include "SceneCapturer.h" 5 | 6 | class FStereoPanoManager 7 | { 8 | private: 9 | FAutoConsoleCommand PanoramicScreenshotCommand; 10 | FAutoConsoleCommand PanoramicMovieCommand; 11 | FAutoConsoleCommand PanoramicQualityCommand; 12 | FAutoConsoleCommand PanoramicPauseCommand; 13 | 14 | public: 15 | void PanoramicScreenshot( const TArray& Args ); 16 | void PanoramicScreenshot( const int32 InStartFrame, const int32 InEndFrame, FStereoCaptureDoneDelegate& InStereoCaptureDoneDelegate); 17 | void PanoramicMovie( const TArray& Args ); 18 | void PanoramicQuality( const TArray& Args ); 19 | void PanoramicTogglePause(const TArray& Args); 20 | 21 | static IConsoleVariable* HorizontalAngularIncrement; 22 | static IConsoleVariable* VerticalAngularIncrement; 23 | static IConsoleVariable* StepCaptureWidth; 24 | static IConsoleVariable* EyeSeparation; 25 | static IConsoleVariable* ForceAlpha; 26 | static IConsoleVariable* GenerateDebugImages; 27 | static IConsoleVariable* ConcurrentCaptures; 28 | static IConsoleVariable* CaptureHorizontalFOV; 29 | static IConsoleVariable* CaptureSlicePixelWidth; 30 | static IConsoleVariable* EnableBilerp; 31 | static IConsoleVariable* SuperSamplingMethod; 32 | static IConsoleVariable* OutputDir; 33 | static IConsoleVariable* ShouldOverrideInitialYaw; 34 | static IConsoleVariable* ForcedInitialYaw; 35 | // static IConsoleVariable* FadeStereoToZeroAtSides; 36 | static IConsoleVariable* FadeStereoToZeroByVertical; 37 | 38 | FStereoPanoManager() 39 | : PanoramicScreenshotCommand( 40 | TEXT( "SP.PanoramicScreenshot" ), 41 | *NSLOCTEXT( "StereoPano", "CommandText_ScreenShot", "Takes a panoramic screenshot" ).ToString(), 42 | FConsoleCommandWithArgsDelegate::CreateRaw( this, &FStereoPanoManager::PanoramicScreenshot ) ) 43 | , PanoramicMovieCommand( 44 | TEXT( "SP.PanoramicMovie" ), 45 | *NSLOCTEXT( "StereoPano", "CommandText_MovieCapture", "Takes a sequence of panoramic screenshots" ).ToString(), 46 | FConsoleCommandWithArgsDelegate::CreateRaw( this, &FStereoPanoManager::PanoramicMovie ) ) 47 | , PanoramicQualityCommand( 48 | TEXT( "SP.PanoramicQuality" ), 49 | *NSLOCTEXT( "StereoPano", "CommandText_Quality", "Sets the quality of the panoramic screenshot to 'preview | average | improved'" ).ToString(), 50 | FConsoleCommandWithArgsDelegate::CreateRaw( this, &FStereoPanoManager::PanoramicQuality ) ) 51 | , PanoramicPauseCommand( 52 | TEXT( "SP.TogglePause" ), 53 | *NSLOCTEXT( "StereoPano", "CommandText_PauseGame", "Toggles Pausing/Unpausing of the game through StereoPano Plugin" ).ToString(), 54 | FConsoleCommandWithArgsDelegate::CreateRaw( this, &FStereoPanoManager::PanoramicTogglePause ) ) 55 | , SceneCapturer( NULL ) 56 | { 57 | } 58 | 59 | void Cleanup(); 60 | 61 | UPROPERTY() 62 | class USceneCapturer* SceneCapturer; 63 | }; -------------------------------------------------------------------------------- /Source/StereoPano/Private/SceneCapturer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #pragma once 4 | 5 | #include "SceneCapturer.generated.h" 6 | 7 | 8 | DECLARE_LOG_CATEGORY_EXTERN( LogStereoPano, Log, All ); 9 | 10 | DECLARE_STATS_GROUP( TEXT( "SP" ), STATGROUP_SP, STATCAT_Advanced ); 11 | 12 | DECLARE_CYCLE_STAT( TEXT( "SavePNG" ), STAT_SPSavePNG, STATGROUP_SP ); 13 | DECLARE_CYCLE_STAT( TEXT( "SampleSpherical" ), STAT_SPSampleSpherical, STATGROUP_SP ); 14 | DECLARE_CYCLE_STAT( TEXT( "ReadStrip" ), STAT_SPReadStrip, STATGROUP_SP ); 15 | DECLARE_CYCLE_STAT( TEXT( "FillAlpha" ), STAT_SPFillAlpha, STATGROUP_SP ); 16 | 17 | UENUM() 18 | namespace ECaptureStep 19 | { 20 | enum Type 21 | { 22 | Reset, 23 | SetStartPosition, 24 | SetPosition, 25 | Read, 26 | Pause, 27 | Unpause, 28 | }; 29 | } 30 | 31 | DECLARE_DELEGATE_TwoParams(FStereoCaptureDoneDelegate, const TArray&, const TArray&); 32 | 33 | UCLASS() 34 | class USceneCapturer 35 | : public UObject 36 | , public FTickableGameObject 37 | { 38 | GENERATED_BODY() 39 | 40 | public: 41 | USceneCapturer(); 42 | 43 | //NOTE: ikrimae: Adding this ctor hack to fix the 4.8p2 problem with hot reload macros calling empty constructors 44 | // https://answers.unrealengine.com/questions/228042/48p2-compile-fails-on-class-with-non-default-const.html 45 | USceneCapturer(FVTableHelper& Helper); 46 | 47 | virtual void Tick( float DeltaTime ) override; 48 | 49 | virtual bool IsTickable() const 50 | { 51 | return true; 52 | } 53 | 54 | virtual bool IsTickableWhenPaused() const 55 | { 56 | return bIsTicking; 57 | } 58 | 59 | virtual TStatId GetStatId() const 60 | { 61 | RETURN_QUICK_DECLARE_CYCLE_STAT( USceneCapturer, STATGROUP_Tickables ); 62 | } 63 | 64 | void InitCaptureComponent( USceneCaptureComponent2D* CaptureComponent, float HFov, float VFov, EStereoscopicPass InStereoPass ); 65 | 66 | void CaptureComponent( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, FString Folder, USceneCaptureComponent2D* CaptureComponent, TArray& Atlas ); 67 | 68 | void CopyToUnprojAtlas( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, TArray& Atlas, TArray& SurfaceData ); 69 | 70 | TArray SaveAtlas( FString Folder, const TArray& SurfaceData ); 71 | 72 | void SetPositionAndRotation( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, int32 CaptureIndex ); 73 | 74 | void ValidateParameters(); 75 | 76 | void SetInitialState( int32 InStartFrame, int32 InEndFrame, FStereoCaptureDoneDelegate& InStereoCaptureDoneDelegate ); 77 | 78 | void Reset(); 79 | 80 | IImageWrapperModule& ImageWrapperModule; 81 | 82 | bool bIsTicking; 83 | FDateTime OverallStartTime; 84 | FDateTime StartTime; 85 | 86 | FVector StartLocation; 87 | FRotator StartRotation; 88 | FString Timestamp; 89 | int32 StartFrame; 90 | int32 EndFrame; 91 | 92 | ECaptureStep::Type CaptureStep; 93 | int32 CurrentFrameCount; 94 | 95 | int32 CaptureWidth; 96 | int32 CaptureHeight; 97 | 98 | int32 StripWidth; 99 | int32 StripHeight; 100 | 101 | class APlayerController* CapturePlayerController; 102 | class AGameModeBase* CaptureGameMode; 103 | 104 | TArray LeftEyeCaptureComponents; 105 | TArray RightEyeCaptureComponents; 106 | 107 | bool GetComponentSteps( int32 Step, int32& CurrentHorizontalStep, int32& CurrentVerticalStep ) 108 | { 109 | if( Step < TotalSteps ) 110 | { 111 | CurrentHorizontalStep = Step / NumberOfVerticalSteps; 112 | CurrentVerticalStep = Step - ( CurrentHorizontalStep * NumberOfVerticalSteps ); 113 | return true; 114 | } 115 | 116 | return false; 117 | } 118 | 119 | private: 120 | const float hAngIncrement; 121 | const float vAngIncrement; 122 | const float eyeSeparation; 123 | 124 | float sliceHFov; 125 | float sliceVFov; 126 | 127 | const int32 NumberOfHorizontalSteps; 128 | const int32 NumberOfVerticalSteps; 129 | 130 | int32 UnprojectedAtlasWidth; 131 | int32 UnprojectedAtlasHeight; 132 | 133 | const int32 SphericalAtlasWidth; 134 | const int32 SphericalAtlasHeight; 135 | 136 | int32 CurrentStep; 137 | int32 TotalSteps; 138 | int32 LastPercent; 139 | 140 | TArray UnprojectedLeftEyeAtlas; 141 | TArray UnprojectedRightEyeAtlas; 142 | 143 | const bool bForceAlpha; 144 | 145 | const bool bEnableBilerp; 146 | const int32 SSMethod; 147 | const bool bOverrideInitialYaw; 148 | const float ForcedInitialYaw; 149 | const FString OutputDir; 150 | 151 | bool dbgMatchCaptureSliceFovToAtlasSliceFov; 152 | bool dbgDisableOffsetRotation; 153 | bool fadeStereoToZeroByVertical; 154 | FString FrameDescriptors; 155 | 156 | FStereoCaptureDoneDelegate StereoCaptureDoneDelegate; 157 | }; -------------------------------------------------------------------------------- /Source/StereoPano/Private/StereoPanoManager.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #include "StereoPanoManager.h" 4 | #include "StereoPanoPrivatePCH.h" 5 | 6 | 7 | //Slice Controls 8 | IConsoleVariable* FStereoPanoManager::HorizontalAngularIncrement = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.HorizontalAngularIncrement" ), 1.0f, TEXT( "The number of degrees per horizontal step. Must be a factor of 360." ), ECVF_Default ); 9 | IConsoleVariable* FStereoPanoManager::VerticalAngularIncrement = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.VerticalAngularIncrement" ), 1.0f, TEXT( "The number of degrees per vertical step. Must be a factor of 180." ), ECVF_Default ); 10 | IConsoleVariable* FStereoPanoManager::CaptureHorizontalFOV = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.CaptureHorizontalFOV"), 90.0f, TEXT("Horizontal FOV for scene capture component. Must be larger than SP.HorizontalAngularIncrement"), ECVF_Default ); 11 | 12 | //Atlas Controls 13 | IConsoleVariable* FStereoPanoManager::StepCaptureWidth = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.StepCaptureWidth" ), 4096, TEXT( "The final spherical atlas width" ), ECVF_Default ); 14 | IConsoleVariable* FStereoPanoManager::EyeSeparation = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.EyeSeparation" ), 6.4f, TEXT( "The separation of the stereo cameras" ), ECVF_Default ); 15 | IConsoleVariable* FStereoPanoManager::ForceAlpha = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.ForceAlpha" ), false, TEXT( "Force the alpha value to completely opaque" ), ECVF_Default ); 16 | 17 | //Sampling Controls 18 | IConsoleVariable* FStereoPanoManager::CaptureSlicePixelWidth = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.CaptureSlicePixelWidth"), 2048, TEXT(" Capture Slice Pixel Dimension"), ECVF_Default); 19 | IConsoleVariable* FStereoPanoManager::EnableBilerp = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.EnableBilerp"), true, TEXT("0 - No Filtering 1- Bilinear Filter slice samples"), ECVF_Default); 20 | IConsoleVariable* FStereoPanoManager::SuperSamplingMethod = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.SuperSamplingMethod"), 1, TEXT(" 0 - No Supersampling, 1 - Rotated Grid SS"), ECVF_Default); 21 | 22 | //Debug Controls 23 | //IConsoleVariable* FStereoPanoManager::ConcurrentCaptures = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.ConcurrentCaptures" ), 15, TEXT("The number of scene captures to capture at the same time"), ECVF_Default); 24 | IConsoleVariable* FStereoPanoManager::ConcurrentCaptures = IConsoleManager::Get().RegisterConsoleVariable( TEXT("SP.ConcurrentCaptures"), 30, TEXT("The number of scene captures to capture at the same time"), ECVF_Default); 25 | IConsoleVariable* FStereoPanoManager::GenerateDebugImages = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.GenerateDebugImages" ), 0, TEXT("0 - No Debug Images\n1 - Save out each strip as it is generated\n2 - Save each entire slice"), ECVF_Default); 26 | IConsoleVariable* FStereoPanoManager::OutputDir = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.OutputDir" ), FPaths::ScreenShotDir(), TEXT("Output directory"), ECVF_Default); 27 | IConsoleVariable* FStereoPanoManager::ShouldOverrideInitialYaw = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.ShouldOverrideInitialYaw" ), false, TEXT("Override Initial Camera Yaw. Set to true if you don't want to use PlayerController View Dir"), ECVF_Default); 28 | IConsoleVariable* FStereoPanoManager::ForcedInitialYaw = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.ForcedInitialYaw" ), 90.0f, TEXT("Yaw value for initial Camera view direction. Set ShouldOverrideInitialYaw to true to use this value"), ECVF_Default); 29 | //IConsoleVariable* FStereoPanoManager::FadeStereoToZeroAtSides = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.FadeStereoToZeroAtSides"), false, TEXT("Fade stereo effect between left/right eye to zero at 90 degrees of horizontal rotation."), ECVF_Default); 30 | IConsoleVariable* FStereoPanoManager::FadeStereoToZeroByVertical = IConsoleManager::Get().RegisterConsoleVariable( TEXT( "SP.FadeStereoToZeroByVertical"), true, TEXT("Fade stereo effect between left/right eye to zero at 90 degrees of vertical rotation."), ECVF_Default); 31 | 32 | 33 | void FStereoPanoManager::PanoramicScreenshot(const TArray& Args) 34 | { 35 | FStereoCaptureDoneDelegate emptyDelegate; 36 | FStereoPanoManager::PanoramicScreenshot(0, 0, emptyDelegate); 37 | } 38 | 39 | void FStereoPanoManager::PanoramicScreenshot(const int32 InStartFrame, const int32 InEndFrame, FStereoCaptureDoneDelegate& InStereoCaptureDoneDelegate) 40 | { 41 | // Construct a capturer that has stereo USceneCaptureComponent2D components 42 | SceneCapturer = NewObject(USceneCapturer::StaticClass()); 43 | SceneCapturer->AddToRoot(); 44 | 45 | // Rotation is ignored; always start from a yaw of zero 46 | SceneCapturer->SetInitialState( InStartFrame, InEndFrame, InStereoCaptureDoneDelegate ); 47 | } 48 | 49 | void FStereoPanoManager::Cleanup() 50 | { 51 | if( SceneCapturer != NULL ) 52 | { 53 | SceneCapturer->Reset(); 54 | SceneCapturer->RemoveFromRoot(); 55 | 56 | // Let GC handle the deletion 57 | SceneCapturer = NULL; 58 | } 59 | } 60 | 61 | void FStereoPanoManager::PanoramicMovie( const TArray& Args ) 62 | { 63 | int32 StartFrame = 0; 64 | int32 EndFrame = 0; 65 | 66 | if( Args.Num() == 1 ) 67 | { 68 | StartFrame = 0; 69 | EndFrame = FCString::Atoi( *Args[0] ) - 1; //Frame Range is inclusive so -1 to find the last frame 70 | } 71 | else if (Args.Num() == 2) 72 | { 73 | StartFrame = FCString::Atoi( *Args[0] ); 74 | EndFrame = FCString::Atoi( *Args[1] ); 75 | EndFrame = FMath::Max(StartFrame, EndFrame); 76 | } 77 | 78 | FStereoCaptureDoneDelegate emptyDelegate; 79 | FStereoPanoManager::PanoramicScreenshot(StartFrame, EndFrame, emptyDelegate); 80 | } 81 | 82 | void FStereoPanoManager::PanoramicQuality( const TArray& Args ) 83 | { 84 | if( Args.Contains( TEXT( "preview" ) ) ) 85 | { 86 | UE_LOG( LogStereoPano, Display, TEXT( " ... setting 'preview' quality" ) ); 87 | 88 | FStereoPanoManager::HorizontalAngularIncrement->Set( TEXT( "5" ) ); 89 | FStereoPanoManager::VerticalAngularIncrement->Set( TEXT( "60" ) ); 90 | FStereoPanoManager::CaptureHorizontalFOV->Set( TEXT( "60" ) ); 91 | FStereoPanoManager::StepCaptureWidth->Set( TEXT( "720" ) ); 92 | } 93 | else if( Args.Contains( TEXT( "average" ) ) ) 94 | { 95 | UE_LOG( LogStereoPano, Display, TEXT( " ... setting 'average' quality" ) ); 96 | 97 | FStereoPanoManager::HorizontalAngularIncrement->Set( TEXT( "2" ) ); 98 | FStereoPanoManager::VerticalAngularIncrement->Set( TEXT( "30" ) ); 99 | FStereoPanoManager::CaptureHorizontalFOV->Set( TEXT( "30" ) ); 100 | FStereoPanoManager::StepCaptureWidth->Set( TEXT( "1440" ) ); 101 | } 102 | else if( Args.Contains( TEXT( "improved" ) ) ) 103 | { 104 | UE_LOG( LogStereoPano, Display, TEXT( " ... setting 'improved' quality" ) ); 105 | 106 | FStereoPanoManager::HorizontalAngularIncrement->Set( TEXT( "0.5" ) ); 107 | FStereoPanoManager::VerticalAngularIncrement->Set( TEXT( "22.5" ) ); 108 | FStereoPanoManager::CaptureHorizontalFOV->Set( TEXT( "22.5" ) ); 109 | FStereoPanoManager::StepCaptureWidth->Set( TEXT( "1440" ) ); 110 | } 111 | else 112 | { 113 | UE_LOG( LogStereoPano, Warning, TEXT( "No quality setting found; options are 'preview | average | improved'" ) ); 114 | } 115 | } 116 | 117 | void FStereoPanoManager::PanoramicTogglePause(const TArray& Args) 118 | { 119 | auto CapturePlayerController = UGameplayStatics::GetPlayerController(GWorld, 0); 120 | auto CaptureGameMode = UGameplayStatics::GetGameMode(GWorld); 121 | 122 | if (CaptureGameMode == NULL || CapturePlayerController == NULL) 123 | { 124 | UE_LOG(LogStereoPano, Warning, TEXT("Missing GameMode or PlayerController")); 125 | return; 126 | } 127 | 128 | if (GWorld->IsPaused()) 129 | { 130 | CaptureGameMode->ClearPause(); 131 | } 132 | else 133 | { 134 | CaptureGameMode->SetPause(CapturePlayerController); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /Source/StereoPano/Private/SceneCapturer.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2015 Kite & Lightning. All rights reserved. 2 | 3 | #include "SceneCapturer.h" 4 | #include "StereoPanoPrivatePCH.h" 5 | 6 | DEFINE_LOG_CATEGORY( LogStereoPano ); 7 | 8 | //Rotated Grid Supersampling 9 | const int32 maxNumSamples = 16; 10 | struct SamplingPattern 11 | { 12 | int numSamples; 13 | FVector2D ssOffsets[maxNumSamples]; 14 | }; 15 | const SamplingPattern g_ssPatterns[] = 16 | { 17 | { 18 | 1, 19 | { 20 | FVector2D(0, 0), 21 | } 22 | }, 23 | { 24 | 4, 25 | { 26 | FVector2D(0.125f, 0.625f), 27 | FVector2D(0.375f, 0.125f), 28 | FVector2D(0.625f, 0.875f), 29 | FVector2D(0.875f, 0.375f), 30 | } 31 | }, 32 | { 33 | 16, 34 | { 35 | FVector2D(0.125f, 0.125f), 36 | FVector2D(0.125f, 0.375f), 37 | FVector2D(0.125f, 0.625f), 38 | FVector2D(0.125f, 0.875f), 39 | FVector2D(0.375f, 0.125f), 40 | FVector2D(0.375f, 0.375f), 41 | FVector2D(0.375f, 0.625f), 42 | FVector2D(0.375f, 0.875f), 43 | FVector2D(0.625f, 0.125f), 44 | FVector2D(0.625f, 0.375f), 45 | FVector2D(0.625f, 0.625f), 46 | FVector2D(0.625f, 0.875f), 47 | FVector2D(0.875f, 0.125f), 48 | FVector2D(0.875f, 0.375f), 49 | FVector2D(0.875f, 0.625f), 50 | FVector2D(0.875f, 0.875f), 51 | } 52 | }, 53 | 54 | }; 55 | 56 | 57 | void USceneCapturer::InitCaptureComponent( USceneCaptureComponent2D* CaptureComponent, float HFov, float VFov, EStereoscopicPass InStereoPass ) 58 | { 59 | CaptureComponent->SetVisibility( true ); 60 | CaptureComponent->SetHiddenInGame( false ); 61 | 62 | //CaptureComponent->CaptureStereoPass = InStereoPass; 63 | CaptureComponent->FOVAngle = FMath::Max( HFov, VFov ); 64 | CaptureComponent->bCaptureEveryFrame = false; 65 | CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; 66 | 67 | //CaptureComponent->TextureTarget = NewObject(this); 68 | float rnd = float(rand()) / float(RAND_MAX); 69 | float rnd2 = float(rand()) / float(RAND_MAX); 70 | FString tname = FString::Printf(TEXT("TextTarg%f_%f"), rnd, rnd2); 71 | CaptureComponent->TextureTarget = CreateDefaultSubobject(FName(*tname)); 72 | //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 73 | // on top of it which causes degredation. 74 | CaptureComponent->TextureTarget->InitCustomFormat(CaptureWidth, CaptureHeight, PF_A16B16G16R16, false); 75 | //CaptureComponent->TextureTarget->InitAutoFormat(CaptureWidth, CaptureHeight); 76 | //CaptureComponent->TextureTarget->bForceLinearGamma = false; 77 | //CaptureComponent->TextureTarget->bHDR = false; 78 | 79 | FPostProcessSettings pps; 80 | pps.VignetteIntensity = 0; 81 | pps.bOverride_VignetteIntensity = true; 82 | pps.LensFlareIntensity = 0; 83 | pps.bOverride_LensFlareIntensity = true; 84 | pps.SceneFringeIntensity = 0; 85 | pps.bOverride_SceneFringeIntensity = true; 86 | pps.BloomIntensity = 0; 87 | pps.bOverride_BloomIntensity = true; 88 | //pps.AutoExposureBias = 1.0; //fix brightness 89 | //pps.bOverride_AutoExposureBias = true; 90 | 91 | 92 | CaptureComponent->PostProcessSettings = pps; 93 | 94 | 95 | CaptureComponent->RegisterComponentWithWorld( GWorld ); 96 | 97 | // UE4 cannot serialize an array of subobject pointers, so add these objects to the root 98 | CaptureComponent->AddToRoot(); 99 | } 100 | 101 | USceneCapturer::USceneCapturer(FVTableHelper& Helper) 102 | : Super(Helper) 103 | , ImageWrapperModule(FModuleManager::LoadModuleChecked(FName("ImageWrapper"))) 104 | , bIsTicking(false) 105 | , CapturePlayerController(NULL) 106 | , CaptureGameMode(NULL) 107 | , hAngIncrement(FStereoPanoManager::HorizontalAngularIncrement->GetFloat()) 108 | , vAngIncrement(FStereoPanoManager::VerticalAngularIncrement->GetFloat()) 109 | , eyeSeparation(FStereoPanoManager::EyeSeparation->GetFloat()) 110 | , NumberOfHorizontalSteps((int32)(360.0f / hAngIncrement)) 111 | , NumberOfVerticalSteps((int32)(180.0f / vAngIncrement) + 1) /* Need an extra b/c we only grab half of the top & bottom slices */ 112 | , SphericalAtlasWidth(FStereoPanoManager::StepCaptureWidth->GetInt()) 113 | , SphericalAtlasHeight(SphericalAtlasWidth / 2) 114 | , bForceAlpha(FStereoPanoManager::ForceAlpha->GetInt() != 0) 115 | , bEnableBilerp(FStereoPanoManager::EnableBilerp->GetInt() != 0) 116 | , SSMethod(FMath::Clamp(FStereoPanoManager::SuperSamplingMethod->GetInt(), 0, ARRAY_COUNT(g_ssPatterns))) 117 | , bOverrideInitialYaw(FStereoPanoManager::ShouldOverrideInitialYaw->GetInt() != 0) 118 | , ForcedInitialYaw(FRotator::ClampAxis(FStereoPanoManager::ForcedInitialYaw->GetFloat())) 119 | , OutputDir(FStereoPanoManager::OutputDir->GetString().IsEmpty() ? FPaths::GameSavedDir() / TEXT("StereoPano") : FStereoPanoManager::OutputDir->GetString()) 120 | //, dbgDisableOffsetRotation(FStereoPanoManager::FadeStereoToZeroAtSides->GetInt() != 0) 121 | , fadeStereoToZeroByVertical( FStereoPanoManager::FadeStereoToZeroByVertical->GetInt() != 0) 122 | {} 123 | 124 | USceneCapturer::USceneCapturer() 125 | : ImageWrapperModule( FModuleManager::LoadModuleChecked( FName( "ImageWrapper" ) ) ) 126 | , bIsTicking( false ) 127 | , CapturePlayerController( NULL ) 128 | , CaptureGameMode( NULL ) 129 | , hAngIncrement( FStereoPanoManager::HorizontalAngularIncrement->GetFloat() ) 130 | , vAngIncrement( FStereoPanoManager::VerticalAngularIncrement->GetFloat() ) 131 | , eyeSeparation( FStereoPanoManager::EyeSeparation->GetFloat() ) 132 | , NumberOfHorizontalSteps( ( int32 )( 360.0f / hAngIncrement ) ) 133 | , NumberOfVerticalSteps( ( int32 )( 180.0f / vAngIncrement ) + 1 ) /* Need an extra b/c we only grab half of the top & bottom slices */ 134 | , SphericalAtlasWidth( FStereoPanoManager::StepCaptureWidth->GetInt() ) 135 | , SphericalAtlasHeight( SphericalAtlasWidth / 2) 136 | , bForceAlpha( FStereoPanoManager::ForceAlpha->GetInt() != 0 ) 137 | , bEnableBilerp( FStereoPanoManager::EnableBilerp->GetInt() != 0 ) 138 | , SSMethod( FMath::Clamp(FStereoPanoManager::SuperSamplingMethod->GetInt(), 0, ARRAY_COUNT(g_ssPatterns)) ) 139 | , bOverrideInitialYaw( FStereoPanoManager::ShouldOverrideInitialYaw->GetInt() != 0 ) 140 | , ForcedInitialYaw( FRotator::ClampAxis(FStereoPanoManager::ForcedInitialYaw->GetFloat()) ) 141 | , OutputDir( FStereoPanoManager::OutputDir->GetString().IsEmpty() ? FPaths::GameSavedDir() / TEXT("StereoPano") : FStereoPanoManager::OutputDir->GetString() ) 142 | //, dbgDisableOffsetRotation(FStereoPanoManager::FadeStereoToZeroAtSides->GetInt() != 0) 143 | , fadeStereoToZeroByVertical( FStereoPanoManager::FadeStereoToZeroByVertical->GetInt() != 0) 144 | { 145 | //NOTE: ikrimae: Keeping the old sampling mechanism just until we're sure the new way is always better 146 | dbgMatchCaptureSliceFovToAtlasSliceFov = false; 147 | 148 | float captureHFov = 0, captureVFov = 0; 149 | 150 | if (dbgMatchCaptureSliceFovToAtlasSliceFov) 151 | { 152 | //Slicing Technique 1: Match Capture Slice StripWidth to match the pixel dimensions of AtlasWidth/NumHorizSteps & s.t. stripwidth/stripheight fovs match hAngIncr & vAngIncr 153 | // Legacy technique but allows setting the strip width to match atlas slice width 154 | // Pretty wasteful and will break if CaptureHFov & hangIncr/vAngIncr diverge greatly b/c resultant texture will exceed GPU bounds 155 | // StripHeight is computed based on solving CpxV = CpxH * SpxV / SpxH 156 | // CpxV = CV * SpxV / SV 157 | // captureVfov = 2 * atan( tan(captureHfov / 2) * (SpxV / SpxH) ) 158 | sliceHFov = hAngIncrement; 159 | sliceVFov = vAngIncrement; 160 | 161 | //TODO: ikrimae: Also do a quick test to see if there are issues with setting fov to something really small ( < 1 degree) 162 | // And it does. Current noted issues: screen space effects like SSAO, AA, SSR are all off 163 | // local eyeadaptation also causes problems. Should probably turn off all PostProcess effects 164 | // small fovs cause floating point errors in the sampling function (probably a bug b/c no thought put towards that) 165 | captureHFov = FStereoPanoManager::CaptureHorizontalFOV->GetFloat(); 166 | 167 | ensure(captureHFov >= hAngIncrement); 168 | 169 | //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 170 | // and then sample the resulting plane accordingly. Remember when updating to this to recheck the math in resample function. Might 171 | // have made assumptions about capture slice dimensions matching the sample strips 172 | StripWidth = SphericalAtlasWidth / NumberOfHorizontalSteps; 173 | //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 174 | StripHeight = StripWidth * FMath::Tan(FMath::DegreesToRadians(vAngIncrement / 2.0f)) / FMath::Tan(FMath::DegreesToRadians(hAngIncrement / 2.0f)); 175 | 176 | const FVector2D slicePlaneDim = FVector2D( 177 | 2.0f * FMath::Tan(FMath::DegreesToRadians(hAngIncrement) / 2.0f), 178 | 2.0f * FMath::Tan(FMath::DegreesToRadians(vAngIncrement) / 2.0f)); 179 | 180 | const float capturePlaneWidth = 2.0f * FMath::Tan(FMath::DegreesToRadians(captureHFov) / 2.0f); 181 | 182 | //TODO: ikrimae: This is just to let the rest of the existing code work. Sampling rate of the slice can be whatever. 183 | // Ex: To match the highest sampling frequency of the spherical atlas, it should match the area of differential patch 184 | // at ray direction of pixel(0,1) in the atlas 185 | 186 | //Need stripwidth/slicePlaneDim.X = capturewidth / capturePlaneDim.X 187 | CaptureWidth = capturePlaneWidth * StripWidth / slicePlaneDim.X; 188 | CaptureHeight = CaptureWidth * StripHeight / StripWidth; 189 | 190 | captureVFov = FMath::RadiansToDegrees(2 * FMath::Atan(FMath::Tan(FMath::DegreesToRadians(captureHFov / 2.0f)) * CaptureHeight / CaptureWidth)); 191 | 192 | //float dbgCapturePlaneDimY = 2.0f * FMath::Tan(FMath::DegreesToRadians(captureVFov) / 2.0f); 193 | //float dbgCaptureHeight = dbgCapturePlaneDimY * StripHeight / slicePlaneDim.Y; 194 | } 195 | else 196 | { 197 | //Slicing Technique 2: Each slice is a determined square FOV at a configured preset resolution. 198 | // Strip Width/Strip Height is determined based on hAngIncrement & vAngIncrement 199 | // Just make sure pixels/captureHFov >= pixels/hAngIncr && pixels/vAngIncr 200 | 201 | captureVFov = captureHFov = FStereoPanoManager::CaptureHorizontalFOV->GetFloat(); 202 | sliceVFov = sliceHFov = captureHFov; 203 | 204 | ensure(captureHFov >= FMath::Max(hAngIncrement, vAngIncrement)); 205 | 206 | //TODO: ikrimae: Re-do for floating point accuracy 207 | const FVector2D slicePlaneDim = FVector2D( 208 | 2.0f * FMath::Tan(FMath::DegreesToRadians(hAngIncrement) / 2.0f), 209 | 2.0f * FMath::Tan(FMath::DegreesToRadians(vAngIncrement) / 2.0f)); 210 | 211 | const FVector2D capturePlaneDim = FVector2D( 212 | 2.0f * FMath::Tan(FMath::DegreesToRadians(captureHFov) / 2.0f), 213 | 2.0f * FMath::Tan(FMath::DegreesToRadians(captureVFov) / 2.0f)); 214 | 215 | CaptureHeight = CaptureWidth = FStereoPanoManager::CaptureSlicePixelWidth->GetInt(); 216 | 217 | StripWidth = CaptureWidth * slicePlaneDim.X / capturePlaneDim.X; 218 | StripHeight = CaptureHeight * slicePlaneDim.Y / capturePlaneDim.Y; 219 | 220 | //TODO: ikrimae: Come back and check for the actual right sampling rate 221 | check(StripWidth >= (SphericalAtlasWidth / NumberOfHorizontalSteps) && 222 | StripHeight >= (SphericalAtlasHeight / NumberOfVerticalSteps)); 223 | 224 | //Ensure Width/Height is always even 225 | StripWidth += StripWidth & 1; 226 | StripHeight += StripHeight & 1; 227 | 228 | } 229 | 230 | UnprojectedAtlasWidth = NumberOfHorizontalSteps * StripWidth; 231 | UnprojectedAtlasHeight = NumberOfVerticalSteps * StripHeight; 232 | 233 | //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 234 | // when the scenecapture component > current window render target. https://answers.unrealengine.com/questions/80531/scene-capture-2d-max-resolution.html 235 | //TODO: ikrimae: Ensure that r.SceneRenderTargetResizeMethod=2 236 | FSystemResolution::RequestResolutionChange(CaptureWidth, CaptureHeight, EWindowMode::Windowed); 237 | 238 | 239 | 240 | for( int CaptureIndex = 0; CaptureIndex < FStereoPanoManager::ConcurrentCaptures->GetInt(); CaptureIndex++ ) 241 | { 242 | FString LeftCounter = FString::Printf( TEXT( "LeftEyeCaptureComponent_%04d" ), CaptureIndex ); 243 | USceneCaptureComponent2D* LeftEyeCaptureComponent = CreateDefaultSubobject( *LeftCounter ); 244 | InitCaptureComponent( LeftEyeCaptureComponent, captureHFov, captureVFov, EStereoscopicPass::eSSP_LEFT_EYE ); 245 | LeftEyeCaptureComponents.Add( LeftEyeCaptureComponent ); 246 | 247 | FString RightCounter = FString::Printf( TEXT( "RightEyeCaptureComponent_%04d" ), CaptureIndex ); 248 | USceneCaptureComponent2D* RightEyeCaptureComponent = CreateDefaultSubobject( *RightCounter ); 249 | InitCaptureComponent( RightEyeCaptureComponent, captureHFov, captureVFov, EStereoscopicPass::eSSP_RIGHT_EYE ); 250 | RightEyeCaptureComponents.Add( RightEyeCaptureComponent ); 251 | } 252 | 253 | CurrentStep = 0; 254 | TotalSteps = 0; 255 | FrameDescriptors = TEXT( "FrameNumber, GameClock, TimeTaken(s)" LINE_TERMINATOR ); 256 | 257 | CaptureStep = ECaptureStep::Reset; 258 | } 259 | 260 | void USceneCapturer::Reset() 261 | { 262 | for( int CaptureIndex = 0; CaptureIndex < FStereoPanoManager::ConcurrentCaptures->GetInt(); CaptureIndex++ ) 263 | { 264 | USceneCaptureComponent2D* LeftEyeCaptureComponent = LeftEyeCaptureComponents[CaptureIndex]; 265 | USceneCaptureComponent2D* RightEyeCaptureComponent = RightEyeCaptureComponents[CaptureIndex]; 266 | 267 | LeftEyeCaptureComponent->SetVisibility( false ); 268 | LeftEyeCaptureComponent->SetHiddenInGame( true ); 269 | 270 | // UE4 cannot serialize an array of subobject pointers, so work around the GC problems 271 | LeftEyeCaptureComponent->RemoveFromRoot(); 272 | 273 | RightEyeCaptureComponent->SetVisibility( false ); 274 | RightEyeCaptureComponent->SetHiddenInGame( true ); 275 | 276 | // UE4 cannot serialize an array of subobject pointers, so work around the GC problems 277 | RightEyeCaptureComponent->RemoveFromRoot(); 278 | } 279 | 280 | UnprojectedLeftEyeAtlas.Empty(); 281 | UnprojectedRightEyeAtlas.Empty(); 282 | } 283 | 284 | void USceneCapturer::SetPositionAndRotation( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, int32 CaptureIndex ) 285 | { 286 | FRotator Rotation = StartRotation; 287 | Rotation.Yaw += CurrentHorizontalStep * hAngIncrement; 288 | Rotation.Pitch -= CurrentVerticalStep * vAngIncrement; 289 | 290 | Rotation = Rotation.Clamp(); 291 | 292 | FVector Offset( 0.0f, eyeSeparation / 2.0f, 0.0f ); 293 | 294 | /* 295 | if (dbgDisableOffsetRotation) 296 | { 297 | //For rendering near field objects, we don't rotate the capture components around the stereo pivot, but instead 298 | //around each capture component 299 | const auto rotAngleOffset = FRotator::ClampAxis(Rotation.Yaw - StartRotation.Yaw); 300 | float eyeSeparationDampeningFactor = 1.0f; 301 | if (rotAngleOffset <= 90.0f) 302 | { 303 | eyeSeparationDampeningFactor = FMath::Lerp(1.0f, 0.0f, rotAngleOffset / 90.0f); 304 | } 305 | else if (rotAngleOffset <= 270.0f) 306 | { 307 | eyeSeparationDampeningFactor = 0.0f; 308 | } 309 | else 310 | { 311 | eyeSeparationDampeningFactor = FMath::Lerp(0.0f, 1.0f, (rotAngleOffset - 270.0f) / 90.0f); 312 | } 313 | 314 | Offset = StartRotation.RotateVector(Offset * eyeSeparationDampeningFactor); 315 | } 316 | else 317 | { 318 | Offset = Rotation.RotateVector(Offset); 319 | } 320 | */ 321 | 322 | //decrease offset for top and bottom 323 | if (fadeStereoToZeroByVertical) 324 | { 325 | float eyeSeparationDampeningFactor = 1.0f; 326 | if (Rotation.Pitch <= 180.0f) 327 | { 328 | eyeSeparationDampeningFactor = FMath::Lerp(0.0f, 1.0f, fabs(Rotation.Pitch - 90.0f) / 90.0f); 329 | } 330 | else 331 | { 332 | eyeSeparationDampeningFactor = FMath::Lerp(0.0f, 1.0f, fabs(Rotation.Pitch - 270.0f) / 90.0f); 333 | } 334 | 335 | Offset = Rotation.RotateVector(Offset * eyeSeparationDampeningFactor); 336 | } 337 | else 338 | { 339 | Offset = Rotation.RotateVector(Offset); 340 | } 341 | 342 | 343 | 344 | 345 | LeftEyeCaptureComponents[CaptureIndex]->SetWorldLocationAndRotation( StartLocation - Offset, Rotation ); 346 | LeftEyeCaptureComponents[CaptureIndex]->UpdateContent(); 347 | RightEyeCaptureComponents[CaptureIndex]->SetWorldLocationAndRotation( StartLocation + Offset, Rotation ); 348 | RightEyeCaptureComponents[CaptureIndex]->UpdateContent(); 349 | } 350 | 351 | void USceneCapturer::ValidateParameters() 352 | { 353 | // Angular increment needs to be a factor of 360 to avoid seams i.e. 360 / angular increment needs to be a whole number 354 | if( ( int32 )( NumberOfHorizontalSteps * hAngIncrement ) != 360 ) 355 | { 356 | UE_LOG( LogStereoPano, 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 ); 357 | } 358 | 359 | if( ( int32 )( (NumberOfVerticalSteps - 1) * vAngIncrement ) != 180 ) 360 | { 361 | UE_LOG( LogStereoPano, 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 ); 362 | } 363 | 364 | TotalSteps = NumberOfHorizontalSteps * NumberOfVerticalSteps; 365 | if( ( SphericalAtlasWidth & 1 ) != 0) 366 | { 367 | UE_LOG(LogStereoPano, Warning, TEXT("The Atlas Width (%d) must be even! Otherwise the Atlas height will not divide evenly."), SphericalAtlasWidth); 368 | } 369 | 370 | 371 | // The strip width needs to be an even number and a factor of the number of steps 372 | if( ( StripWidth & 1 ) != 0 ) 373 | { 374 | UE_LOG( LogStereoPano, Warning, TEXT( "Strip width (%d) needs to be even to avoid bad offsets" ), StripWidth ); 375 | } 376 | 377 | if( StripWidth * NumberOfHorizontalSteps != SphericalAtlasWidth ) 378 | { 379 | UE_LOG( LogStereoPano, Warning, TEXT( "The number of horizontal steps (%d) needs to be a factor of the atlas width (%d)" ), NumberOfHorizontalSteps, SphericalAtlasWidth ); 380 | } 381 | 382 | if ((StripHeight & 1) != 0) 383 | { 384 | UE_LOG(LogStereoPano, Warning, TEXT("Strip height (%d) needs to be even to avoid bad offsets"), StripHeight); 385 | } 386 | 387 | if (StripHeight * (NumberOfVerticalSteps - 1) != SphericalAtlasHeight) 388 | { 389 | UE_LOG(LogStereoPano, Warning, TEXT("The number of vertical steps (%d) needs to be a factor of the atlas height (%d)"), NumberOfVerticalSteps, SphericalAtlasHeight); 390 | } 391 | 392 | //TODO: ikrimae: Validate capturewidth & captureheight. Need to be even 393 | 394 | UE_LOG( LogStereoPano, Display, TEXT( "Stereo panoramic screenshot parameters" ) ); 395 | UE_LOG( LogStereoPano, Display, TEXT( " ... capture size: %d x %d" ), CaptureWidth, CaptureHeight ); 396 | UE_LOG( LogStereoPano, Display, TEXT( " ... spherical atlas size: %d x %d" ), SphericalAtlasWidth, SphericalAtlasHeight ); 397 | UE_LOG( LogStereoPano, Display, TEXT( " ... intermediate atlas size: %d x %d" ), UnprojectedAtlasWidth, UnprojectedAtlasHeight ); 398 | UE_LOG( LogStereoPano, Display, TEXT( " ... strip size: %d x %d" ), StripWidth, StripHeight ); 399 | UE_LOG( LogStereoPano, Display, TEXT( " ... horizontal steps: %d at %g degrees" ), NumberOfHorizontalSteps, hAngIncrement ); 400 | UE_LOG( LogStereoPano, Display, TEXT( " ... vertical steps: %d at %g degrees" ), NumberOfVerticalSteps, vAngIncrement ); 401 | } 402 | 403 | void USceneCapturer::SetInitialState( int32 InStartFrame, int32 InEndFrame, FStereoCaptureDoneDelegate& InStereoCaptureDoneDelegate ) 404 | { 405 | if( bIsTicking ) 406 | { 407 | UE_LOG( LogStereoPano, Warning, TEXT( "Already capturing a scene; concurrent captures are not allowed" ) ); 408 | return; 409 | } 410 | 411 | CapturePlayerController = UGameplayStatics::GetPlayerController( GWorld, 0 ); 412 | CaptureGameMode = UGameplayStatics::GetGameMode( GWorld ); 413 | 414 | if( CaptureGameMode == NULL || CapturePlayerController == NULL ) 415 | { 416 | UE_LOG( LogStereoPano, Warning, TEXT( "Missing GameMode or PlayerController" ) ); 417 | return; 418 | } 419 | 420 | // Calculate the steps and validate they will produce good results 421 | ValidateParameters(); 422 | 423 | // Setup starting criteria 424 | StartFrame = InStartFrame; 425 | EndFrame = InEndFrame; 426 | CurrentFrameCount = 0; 427 | CurrentStep = 0; 428 | CaptureStep = ECaptureStep::Unpause; 429 | 430 | Timestamp = FString::Printf( TEXT( "%s" ), *FDateTime::Now().ToString() ); 431 | 432 | //SetStartPosition(); 433 | 434 | // Create storage for atlas textures 435 | check( UnprojectedAtlasWidth * UnprojectedAtlasHeight <= MAX_int32 ); 436 | UnprojectedLeftEyeAtlas.AddUninitialized( UnprojectedAtlasWidth * UnprojectedAtlasHeight ); 437 | UnprojectedRightEyeAtlas.AddUninitialized( UnprojectedAtlasWidth * UnprojectedAtlasHeight ); 438 | 439 | StartTime = FDateTime::UtcNow(); 440 | OverallStartTime = StartTime; 441 | bIsTicking = true; 442 | 443 | StereoCaptureDoneDelegate = InStereoCaptureDoneDelegate; 444 | } 445 | 446 | void USceneCapturer::CopyToUnprojAtlas( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, TArray& Atlas, TArray& SurfaceData ) 447 | { 448 | int32 XOffset = StripWidth * CurrentHorizontalStep; 449 | int32 YOffset = StripHeight * CurrentVerticalStep; 450 | 451 | int32 StripSize = StripWidth * sizeof( FColor ); 452 | for (int32 Y = 0; Y < StripHeight; Y++) 453 | { 454 | void* Destination = &Atlas[( ( Y + YOffset ) * UnprojectedAtlasWidth ) + XOffset]; 455 | void* Source = &SurfaceData[StripWidth * Y]; 456 | FMemory::Memcpy( Destination, Source, StripSize ); 457 | } 458 | } 459 | 460 | TArray USceneCapturer::SaveAtlas(FString Folder, const TArray& SurfaceData) 461 | { 462 | SCOPE_CYCLE_COUNTER( STAT_SPSavePNG ); 463 | 464 | TArray SphericalAtlas; 465 | SphericalAtlas.AddZeroed(SphericalAtlasWidth * SphericalAtlasHeight); 466 | 467 | const FVector2D slicePlaneDim = FVector2D( 468 | 2.0f * FMath::Tan(FMath::DegreesToRadians(sliceHFov) / 2.0f), 469 | 2.0f * FMath::Tan(FMath::DegreesToRadians(sliceVFov) / 2.0f)); 470 | 471 | //For each direction, 472 | // Find corresponding slice 473 | // Calculate intersection of slice plane 474 | // Calculate intersection UVs by projecting onto plane tangents 475 | // Supersample that UV coordinate from the unprojected atlas 476 | { 477 | SCOPE_CYCLE_COUNTER(STAT_SPSampleSpherical); 478 | // Dump out how long the process took 479 | const FDateTime SamplingStartTime = FDateTime::UtcNow(); 480 | UE_LOG(LogStereoPano, Log, TEXT("Sampling atlas...")); 481 | 482 | for (int32 y = 0; y < SphericalAtlasHeight; y++) 483 | { 484 | for (int32 x = 0; x < SphericalAtlasWidth; x++) 485 | { 486 | FLinearColor samplePixelAccum = FLinearColor(0, 0, 0, 0); 487 | 488 | //TODO: ikrimae: Seems that bilinear filtering sans supersampling is good enough. Supersampling sans bilerp seems best. 489 | // After more tests, come back to optimize by folding supersampling in and remove this outer sampling loop. 490 | const auto& ssPattern = g_ssPatterns[SSMethod]; 491 | 492 | for (int32 i = 0; i < ssPattern.numSamples; i++) 493 | { 494 | const float sampleU = ((float)x + ssPattern.ssOffsets[i].X) / SphericalAtlasWidth; 495 | const float sampleV = ((float)y + ssPattern.ssOffsets[i].Y) / SphericalAtlasHeight; 496 | 497 | const float sampleTheta = sampleU * 360.0f; 498 | const float samplePhi = sampleV * 180.0f; 499 | 500 | const FVector sampleDir = FVector( 501 | FMath::Sin(FMath::DegreesToRadians(samplePhi)) * FMath::Cos(FMath::DegreesToRadians(sampleTheta)), 502 | FMath::Sin(FMath::DegreesToRadians(samplePhi)) * FMath::Sin(FMath::DegreesToRadians(sampleTheta)), 503 | FMath::Cos(FMath::DegreesToRadians(samplePhi))); 504 | 505 | 506 | //TODO: ikrimae: ugh, ugly. 507 | const int32 sliceXIndex = FMath::TruncToInt(FRotator::ClampAxis(sampleTheta + hAngIncrement / 2.0f) / hAngIncrement); 508 | int32 sliceYIndex = 0; 509 | 510 | //Slice Selection = slice with max{sampleDir dot sliceNormal } 511 | { 512 | float largestCosAngle = 0; 513 | for (int i = 0; i < NumberOfVerticalSteps; i++) 514 | { 515 | const FVector2D sliceCenterThetaPhi = FVector2D( 516 | hAngIncrement * sliceXIndex, 517 | vAngIncrement * i); 518 | 519 | //TODO: ikrimae: There has got to be a faster way. Rethink reparametrization later 520 | const FVector sliceDir = FVector( 521 | FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 522 | FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 523 | FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))); 524 | 525 | const float cosAngle = sampleDir | sliceDir; 526 | 527 | if (cosAngle > largestCosAngle) 528 | { 529 | largestCosAngle = cosAngle; 530 | sliceYIndex = i; 531 | } 532 | } 533 | } 534 | 535 | 536 | const FVector2D sliceCenterThetaPhi = FVector2D( 537 | hAngIncrement * sliceXIndex, 538 | vAngIncrement * sliceYIndex); 539 | 540 | //TODO: ikrimae: Reparameterize with an inverse mapping (e.g. project from slice pixels onto final u,v coordinates. 541 | // Should make code simpler and faster b/c reduces to handful of sin/cos calcs per slice. 542 | // Supersampling will be more difficult though. 543 | 544 | const FVector sliceDir = FVector( 545 | FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 546 | FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 547 | FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))); 548 | 549 | const FPlane slicePlane = FPlane(sliceDir, -sliceDir); 550 | 551 | //Tangents from partial derivatives of sphere equation 552 | const FVector slicePlanePhiTangent = FVector( 553 | FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 554 | FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 555 | -FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y))).GetSafeNormal(); 556 | 557 | //Should be reconstructed to get around discontinuity of theta tangent at nodal points 558 | const FVector slicePlaneThetaTangent = (sliceDir ^ slicePlanePhiTangent).GetSafeNormal(); 559 | //const FVector slicePlaneThetaTangent = FVector( 560 | // -FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 561 | // FMath::Sin(FMath::DegreesToRadians(sliceCenterThetaPhi.Y)) * FMath::Cos(FMath::DegreesToRadians(sliceCenterThetaPhi.X)), 562 | // 0).SafeNormal(); 563 | 564 | check(!slicePlaneThetaTangent.IsZero() && !slicePlanePhiTangent.IsZero()); 565 | 566 | const double t = (double)-slicePlane.W / (sampleDir | sliceDir); 567 | const FVector sliceIntersection = FVector(t * sampleDir.X, t * sampleDir.Y, t * sampleDir.Z); 568 | 569 | //Calculate scalar projection of sliceIntersection onto tangent vectors. a dot b / |b| = a dot b when tangent vectors are normalized 570 | //Then reparameterize to U,V of the sliceplane based on slice plane dimensions 571 | const float sliceU = (sliceIntersection | slicePlaneThetaTangent) / slicePlaneDim.X; 572 | const float sliceV = (sliceIntersection | slicePlanePhiTangent) / slicePlaneDim.Y; 573 | 574 | check(sliceU >= -(0.5f + KINDA_SMALL_NUMBER) && 575 | sliceU <= (0.5f + KINDA_SMALL_NUMBER)); 576 | 577 | check(sliceV >= -(0.5f + KINDA_SMALL_NUMBER) && 578 | sliceV <= (0.5f + KINDA_SMALL_NUMBER)); 579 | 580 | //TODO: ikrimae: Supersample/bilinear filter 581 | const int32 slicePixelX = FMath::TruncToInt(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceU * StripWidth : sliceU * CaptureWidth); 582 | const int32 slicePixelY = FMath::TruncToInt(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceV * StripHeight : sliceV * CaptureHeight); 583 | 584 | FLinearColor slicePixelSample; 585 | 586 | if (bEnableBilerp) 587 | { 588 | //TODO: ikrimae: Clean up later; too tired now 589 | const int32 sliceCenterPixelX = (sliceXIndex + 0.5f) * StripWidth; 590 | const int32 sliceCenterPixelY = (sliceYIndex + 0.5f) * StripHeight; 591 | 592 | const FIntPoint atlasSampleTL(sliceCenterPixelX + FMath::Clamp(slicePixelX , -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY , -StripHeight/2, StripHeight/2)); 593 | const FIntPoint atlasSampleTR(sliceCenterPixelX + FMath::Clamp(slicePixelX + 1, -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY , -StripHeight/2, StripHeight/2)); 594 | const FIntPoint atlasSampleBL(sliceCenterPixelX + FMath::Clamp(slicePixelX , -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY + 1, -StripHeight/2, StripHeight/2)); 595 | const FIntPoint atlasSampleBR(sliceCenterPixelX + FMath::Clamp(slicePixelX + 1, -StripWidth/2, StripWidth/2), sliceCenterPixelY + FMath::Clamp(slicePixelY + 1, -StripHeight/2, StripHeight/2)); 596 | 597 | const FColor pixelColorTL = SurfaceData[atlasSampleTL.Y * UnprojectedAtlasWidth + atlasSampleTL.X]; 598 | const FColor pixelColorTR = SurfaceData[atlasSampleTR.Y * UnprojectedAtlasWidth + atlasSampleTR.X]; 599 | const FColor pixelColorBL = SurfaceData[atlasSampleBL.Y * UnprojectedAtlasWidth + atlasSampleBL.X]; 600 | const FColor pixelColorBR = SurfaceData[atlasSampleBR.Y * UnprojectedAtlasWidth + atlasSampleBR.X]; 601 | 602 | const float fracX = FMath::Frac(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceU * StripWidth : sliceU * CaptureWidth); 603 | const float fracY = FMath::Frac(dbgMatchCaptureSliceFovToAtlasSliceFov ? sliceV * StripHeight : sliceV * CaptureHeight); 604 | 605 | //Reinterpret as linear (a.k.a dont apply srgb inversion) 606 | slicePixelSample = FMath::BiLerp( 607 | pixelColorTL.ReinterpretAsLinear(), pixelColorTR.ReinterpretAsLinear(), 608 | pixelColorBL.ReinterpretAsLinear(), pixelColorBR.ReinterpretAsLinear(), 609 | fracX, fracY); 610 | } 611 | else 612 | { 613 | const int32 sliceCenterPixelX = (sliceXIndex + 0.5f) * StripWidth; 614 | const int32 sliceCenterPixelY = (sliceYIndex + 0.5f) * StripHeight; 615 | 616 | const int32 atlasSampleX = sliceCenterPixelX + slicePixelX; 617 | const int32 atlasSampleY = sliceCenterPixelY + slicePixelY; 618 | 619 | 620 | slicePixelSample = SurfaceData[atlasSampleY * UnprojectedAtlasWidth + atlasSampleX].ReinterpretAsLinear(); 621 | } 622 | 623 | samplePixelAccum += slicePixelSample; 624 | 625 | ////Output color map of projections 626 | //const FColor debugEquiColors[12] = { 627 | // FColor(205, 180, 76), 628 | // FColor(190, 88, 202), 629 | // FColor(127, 185, 194), 630 | // FColor(90, 54, 47), 631 | // FColor(197, 88, 53), 632 | // FColor(197, 75, 124), 633 | // FColor(130, 208, 72), 634 | // FColor(136, 211, 153), 635 | // FColor(126, 130, 207), 636 | // FColor(83, 107, 59), 637 | // FColor(200, 160, 157), 638 | // FColor(80, 66, 106) 639 | //}; 640 | 641 | //samplePixelAccum = ssPattern.numSamples * debugEquiColors[sliceYIndex * 4 + sliceXIndex]; 642 | } 643 | 644 | SphericalAtlas[y * SphericalAtlasWidth + x] = (samplePixelAccum / ssPattern.numSamples).Quantize(); 645 | 646 | // Force alpha value 647 | if (bForceAlpha) 648 | { 649 | SphericalAtlas[y * SphericalAtlasWidth + x].A = 255; 650 | } 651 | } 652 | } 653 | 654 | //Blit the first column into the last column to make the stereo image seamless at theta=360 655 | for (int32 y = 0; y < SphericalAtlasHeight; y++) 656 | { 657 | SphericalAtlas[y * SphericalAtlasWidth + (SphericalAtlasWidth - 1)] = SphericalAtlas[y * SphericalAtlasWidth + 0]; 658 | } 659 | 660 | const FTimespan SamplingDuration = FDateTime::UtcNow() - SamplingStartTime; 661 | UE_LOG(LogStereoPano, Log, TEXT("...done! Duration: %g seconds"), SamplingDuration.GetTotalSeconds()); 662 | } 663 | 664 | // Generate name 665 | FString FrameString = FString::Printf( TEXT( "%s_%05d.png" ), *Folder, CurrentFrameCount ); 666 | FString AtlasName = OutputDir / Timestamp / FrameString; 667 | 668 | UE_LOG( LogStereoPano, Log, TEXT( "Writing atlas: %s" ), *AtlasName ); 669 | 670 | IImageWrapperModule* ImageWrapperModule = FModuleManager::LoadModulePtr("ImageWrapper"); 671 | ensure(ImageWrapperModule != nullptr); 672 | TSharedPtr ImageWrapper = ImageWrapperModule->CreateImageWrapper( EImageFormat::PNG ); 673 | 674 | 675 | 676 | // Write out PNG 677 | //TODO: ikrimae: Use threads to write out the images for performance 678 | ImageWrapper->SetRaw(SphericalAtlas.GetData(), SphericalAtlas.GetAllocatedSize(), SphericalAtlasWidth, SphericalAtlasHeight, ERGBFormat::BGRA, 8); 679 | const TArray& PNGData = ImageWrapper->GetCompressed(100); 680 | FFileHelper::SaveArrayToFile( PNGData, *AtlasName ); 681 | 682 | if (FStereoPanoManager::GenerateDebugImages->GetInt() != 0) 683 | { 684 | FString FrameStringUnprojected = FString::Printf(TEXT("%s_%05d_Unprojected.png"), *Folder, CurrentFrameCount); 685 | FString AtlasNameUnprojected = OutputDir / Timestamp / FrameStringUnprojected; 686 | 687 | ImageWrapper->SetRaw(SurfaceData.GetData(), SurfaceData.GetAllocatedSize(), UnprojectedAtlasWidth, UnprojectedAtlasHeight, ERGBFormat::BGRA, 8); 688 | const TArray& PNGDataUnprojected = ImageWrapper->GetCompressed(100); 689 | FFileHelper::SaveArrayToFile(PNGData, *AtlasNameUnprojected); 690 | } 691 | ImageWrapper.Reset(); 692 | 693 | UE_LOG( LogStereoPano, Log, TEXT( " ... done!" ), *AtlasName ); 694 | 695 | return SphericalAtlas; 696 | } 697 | 698 | void USceneCapturer::CaptureComponent( int32 CurrentHorizontalStep, int32 CurrentVerticalStep, FString Folder, USceneCaptureComponent2D* CaptureComponent, TArray& Atlas ) 699 | { 700 | TArray SurfaceData; 701 | 702 | { 703 | SCOPE_CYCLE_COUNTER( STAT_SPReadStrip ); 704 | FTextureRenderTargetResource* RenderTarget = CaptureComponent->TextureTarget->GameThread_GetRenderTargetResource(); 705 | 706 | //TODO: ikrimae: Might need to validate that this divides evenly. Might not matter 707 | int32 CenterX = CaptureWidth / 2; 708 | int32 CenterY = CaptureHeight / 2; 709 | 710 | SurfaceData.AddUninitialized( StripWidth * StripHeight ); 711 | 712 | // Read pixels 713 | FIntRect Area( CenterX - ( StripWidth / 2 ), CenterY - ( StripHeight / 2 ), CenterX + ( StripWidth / 2 ), CenterY + ( StripHeight / 2) ); 714 | auto readSurfaceDataFlags = FReadSurfaceDataFlags(); 715 | readSurfaceDataFlags.SetLinearToGamma(false); 716 | RenderTarget->ReadPixelsPtr( SurfaceData.GetData(), readSurfaceDataFlags, Area ); 717 | } 718 | 719 | // Copy off strip to atlas texture 720 | CopyToUnprojAtlas( CurrentHorizontalStep, CurrentVerticalStep, Atlas, SurfaceData ); 721 | 722 | if( FStereoPanoManager::GenerateDebugImages->GetInt() != 0 ) 723 | { 724 | SCOPE_CYCLE_COUNTER( STAT_SPSavePNG ); 725 | 726 | // Generate name 727 | FString TickString = FString::Printf( TEXT( "_%05d_%04d_%04d" ), CurrentFrameCount, CurrentHorizontalStep, CurrentVerticalStep ); 728 | FString CaptureName = OutputDir / Timestamp / Folder / TickString + TEXT( ".png" ); 729 | UE_LOG( LogStereoPano, Log, TEXT( "Writing snapshot: %s" ), *CaptureName ); 730 | 731 | // Write out PNG 732 | if (FStereoPanoManager::GenerateDebugImages->GetInt() == 2) 733 | { 734 | //Read Whole Capture Buffer 735 | IImageWrapperModule* ImageWrapperModule = FModuleManager::LoadModulePtr("ImageWrapper"); 736 | ensure(ImageWrapperModule != nullptr); 737 | TSharedPtr ImageWrapper = ImageWrapperModule->CreateImageWrapper(EImageFormat::PNG); 738 | 739 | TArray SurfaceDataWhole; 740 | SurfaceDataWhole.AddUninitialized(CaptureWidth * CaptureHeight); 741 | // Read pixels 742 | FTextureRenderTargetResource* RenderTarget = CaptureComponent->TextureTarget->GameThread_GetRenderTargetResource(); 743 | RenderTarget->ReadPixelsPtr(SurfaceDataWhole.GetData(), FReadSurfaceDataFlags()); 744 | 745 | // Force alpha value 746 | if (bForceAlpha) 747 | { 748 | for (FColor& Color : SurfaceDataWhole) 749 | { 750 | Color.A = 255; 751 | } 752 | } 753 | 754 | ImageWrapper->SetRaw(SurfaceDataWhole.GetData(), SurfaceDataWhole.GetAllocatedSize(), CaptureWidth, CaptureHeight, ERGBFormat::BGRA, 8); 755 | const TArray& PNGData = ImageWrapper->GetCompressed(100); 756 | 757 | FFileHelper::SaveArrayToFile(PNGData, *CaptureName); 758 | ImageWrapper.Reset(); 759 | } 760 | else 761 | { 762 | if (bForceAlpha) 763 | { 764 | for (FColor& Color : SurfaceData) 765 | { 766 | Color.A = 255; 767 | } 768 | } 769 | 770 | IImageWrapperModule* ImageWrapperModule = FModuleManager::LoadModulePtr("ImageWrapper"); 771 | ensure(ImageWrapperModule != nullptr); 772 | TSharedPtr ImageWrapper = ImageWrapperModule->CreateImageWrapper(EImageFormat::PNG); 773 | 774 | ImageWrapper->SetRaw(SurfaceData.GetData(), SurfaceData.GetAllocatedSize(), StripWidth, StripHeight, ERGBFormat::BGRA, 8); 775 | const TArray& PNGData = ImageWrapper->GetCompressed(100); 776 | 777 | FFileHelper::SaveArrayToFile( PNGData, *CaptureName ); 778 | ImageWrapper.Reset(); 779 | } 780 | } 781 | } 782 | 783 | //TODO: ikrimae: This is a cluster fuck. 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 784 | // step (pause, unpause, setposition) for the next frame. FlushRenderingCommands() added haphazardly to test but didn't want to remove them so close to delivery. 785 | // Think through when we actually need to flush and document. 786 | void USceneCapturer::Tick( float DeltaTime ) 787 | { 788 | if( !bIsTicking ) 789 | { 790 | return; 791 | } 792 | 793 | if ( CurrentFrameCount < StartFrame ) 794 | { 795 | //Skip until we're at the frame we want to render 796 | CurrentFrameCount++; 797 | CaptureStep = ECaptureStep::Pause; 798 | } 799 | else if( CurrentStep < TotalSteps ) 800 | { 801 | if (CaptureStep == ECaptureStep::Unpause) 802 | { 803 | FlushRenderingCommands(); 804 | //CaptureGameMode->ClearPause(); 805 | //GPauseRenderingRealtimeClock = false; 806 | CaptureStep = ECaptureStep::Pause; 807 | FlushRenderingCommands(); 808 | } 809 | else if (CaptureStep == ECaptureStep::Pause) 810 | { 811 | FlushRenderingCommands(); 812 | //CaptureGameMode->SetPause(CapturePlayerController); 813 | //GPauseRenderingRealtimeClock = true; 814 | CaptureStep = ECaptureStep::SetStartPosition; 815 | FlushRenderingCommands(); 816 | } 817 | else if (CaptureStep == ECaptureStep::SetStartPosition) 818 | { 819 | //SetStartPosition(); 820 | /* 821 | ENQUEUE_UNIQUE_RENDER_COMMAND( 822 | SceneCapturer_HeartbeatTickTickables, 823 | { 824 | TickRenderingTickables(); 825 | }); 826 | */ 827 | 828 | FlushRenderingCommands(); 829 | 830 | FRotator Rotation; 831 | CapturePlayerController->GetPlayerViewPoint(StartLocation, Rotation); 832 | 833 | Rotation.Roll = 0.0f; 834 | Rotation.Yaw = (bOverrideInitialYaw) ? ForcedInitialYaw : Rotation.Yaw; 835 | Rotation.Pitch = 90.0f; 836 | StartRotation = Rotation; 837 | CaptureStep = ECaptureStep::SetPosition; 838 | FlushRenderingCommands(); 839 | } 840 | else if (CaptureStep == ECaptureStep::SetPosition) 841 | { 842 | FlushRenderingCommands(); 843 | for (int32 CaptureIndex = 0; CaptureIndex < FStereoPanoManager::ConcurrentCaptures->GetInt(); CaptureIndex++) 844 | { 845 | int32 CurrentHorizontalStep; 846 | int32 CurrentVerticalStep; 847 | if (GetComponentSteps(CurrentStep + CaptureIndex, CurrentHorizontalStep, CurrentVerticalStep)) 848 | { 849 | SetPositionAndRotation(CurrentHorizontalStep, CurrentVerticalStep, CaptureIndex); 850 | } 851 | } 852 | 853 | CaptureStep = ECaptureStep::Read; 854 | FlushRenderingCommands(); 855 | } 856 | else if (CaptureStep == ECaptureStep::Read) 857 | { 858 | FlushRenderingCommands(); 859 | for (int32 CaptureIndex = 0; CaptureIndex < FStereoPanoManager::ConcurrentCaptures->GetInt(); CaptureIndex++) 860 | { 861 | int32 CurrentHorizontalStep; 862 | int32 CurrentVerticalStep; 863 | if (GetComponentSteps(CurrentStep, CurrentHorizontalStep, CurrentVerticalStep)) 864 | { 865 | CaptureComponent(CurrentHorizontalStep, CurrentVerticalStep, TEXT("Left"), LeftEyeCaptureComponents[CaptureIndex], UnprojectedLeftEyeAtlas); 866 | CaptureComponent(CurrentHorizontalStep, CurrentVerticalStep, TEXT("Right"), RightEyeCaptureComponents[CaptureIndex], UnprojectedRightEyeAtlas); 867 | 868 | CurrentStep++; 869 | } 870 | } 871 | 872 | 873 | 874 | 875 | CaptureStep = ECaptureStep::SetPosition; 876 | FlushRenderingCommands(); 877 | } 878 | else 879 | { 880 | //ECaptureStep::Reset: 881 | } 882 | 883 | int32 currentPercent = int32(CurrentStep * 1000 / TotalSteps); 884 | if (currentPercent > LastPercent) { 885 | UE_LOG( LogStereoPano, Display, TEXT( "Progress: %.1f %%" ), currentPercent / 10.0f ); 886 | LastPercent = currentPercent; 887 | } 888 | 889 | 890 | } 891 | else 892 | { 893 | TArray SphericalLeftEyeAtlas = SaveAtlas( TEXT( "Left" ), UnprojectedLeftEyeAtlas ); 894 | TArray SphericalRightEyeAtlas = SaveAtlas(TEXT("Right"), UnprojectedRightEyeAtlas); 895 | 896 | // Dump out how long the process took 897 | FDateTime EndTime = FDateTime::UtcNow(); 898 | FTimespan Duration = EndTime - StartTime; 899 | UE_LOG( LogStereoPano, Log, TEXT( "Duration: %g seconds for frame %d" ), Duration.GetTotalSeconds(), CurrentFrameCount ); 900 | StartTime = EndTime; 901 | 902 | //NOTE: ikrimae: Since we can't synchronously finish a stereocapture, we have to notify the caller with a function pointer 903 | //Not sure this is the cleanest way but good enough for now 904 | StereoCaptureDoneDelegate.ExecuteIfBound(SphericalLeftEyeAtlas, SphericalRightEyeAtlas); 905 | 906 | // Construct log of saved atlases in csv format 907 | FrameDescriptors += FString::Printf( TEXT( "%d, %g, %g" LINE_TERMINATOR ), CurrentFrameCount, FApp::GetCurrentTime() - FApp::GetLastTime(), Duration.GetTotalSeconds() ); 908 | 909 | CurrentFrameCount++; 910 | if( CurrentFrameCount <= EndFrame ) 911 | { 912 | CurrentStep = 0; 913 | CaptureStep = ECaptureStep::Unpause; 914 | } 915 | else 916 | { 917 | CaptureGameMode->ClearPause(); 918 | //GPauseRenderingRealtimeClock = false; 919 | 920 | FTimespan OverallDuration = FDateTime::UtcNow() - OverallStartTime; 921 | 922 | FrameDescriptors += FString::Printf(TEXT("Duration: %g minutes for frame range [%d,%d] "), OverallDuration.GetTotalMinutes(), StartFrame, EndFrame);; 923 | UE_LOG( LogStereoPano, Log, TEXT("Duration: %g minutes for frame range [%d,%d] "), OverallDuration.GetTotalMinutes(), StartFrame, EndFrame ); 924 | 925 | FString FrameDescriptorName = OutputDir / Timestamp / TEXT( "Frames.txt" ); 926 | FFileHelper::SaveStringToFile( FrameDescriptors, *FrameDescriptorName, FFileHelper::EEncodingOptions::ForceUTF8 ); 927 | 928 | bIsTicking = false; 929 | FStereoPanoModule::Get()->Cleanup(); 930 | } 931 | } 932 | } 933 | --------------------------------------------------------------------------------