├── CONTRIBUTING.md ├── Engine └── Plugins │ └── Seurat │ ├── Seurat.uplugin │ └── Source │ └── Seurat │ ├── Private │ ├── JsonManifest.h │ ├── SceneCaptureSeurat.cpp │ ├── SceneCaptureSeurat.h │ ├── SceneCaptureSeuratDetail.cpp │ ├── SceneCaptureSeuratDetail.h │ ├── Seurat.cpp │ ├── SeuratConfigWindow.cpp │ ├── SeuratConfigWindow.h │ ├── SeuratPrivatePCH.h │ └── SeuratStyle.cpp │ ├── Public │ ├── Seurat.h │ ├── SeuratCommands.cpp │ ├── SeuratCommands.h │ └── SeuratStyle.h │ └── Seurat.Build.cs ├── LICENSE ├── README.md └── images └── unreal_01.png /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution, 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Seurat.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "Seurat", 6 | "Description": "Seurat Scene Capture.", 7 | "Category": "Other", 8 | "CreatedBy": "Google, Inc.", 9 | "CreatedByURL": "https://www.google.com/", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "EnabledByDefault": false, 14 | "CanContainContent": false, 15 | "IsBetaVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "Seurat", 20 | "Type": "Developer", 21 | "LoadingPhase": "Default", 22 | "WhitelistPlatforms" : [ "Win64", "Win32" ] 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/JsonManifest.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "CoreMinimal.h" 19 | #include "Dom/JsonObject.h" 20 | #include "Dom/JsonValue.h" 21 | 22 | static TArray> MatrixToJsonArray(FMatrix Matrix) 23 | { 24 | TArray> Ret; 25 | for (int32 i = 0; i<4; i++) 26 | for (int32 j = 0; j < 4; j++) 27 | { 28 | Ret.Add(MakeShareable(new FJsonValueNumber(Matrix.M[j][i]))); 29 | } 30 | return Ret; 31 | } 32 | 33 | class ProjectiveCamera { 34 | public: 35 | int32 ImageWidth; 36 | int32 ImageHeight; 37 | FMatrix ClipFromEyeMatrix; 38 | FMatrix WorldFromEyeMatrix; 39 | FString DepthType; 40 | TSharedPtr ToJson() 41 | { 42 | TSharedPtr Ret = MakeShareable(new FJsonObject()); 43 | Ret->SetNumberField("image_width", ImageWidth); 44 | Ret->SetNumberField("image_height", ImageHeight); 45 | Ret->SetArrayField("clip_from_eye_matrix", MatrixToJsonArray(ClipFromEyeMatrix)); 46 | Ret->SetArrayField("world_from_eye_matrix", MatrixToJsonArray(WorldFromEyeMatrix)); 47 | Ret->SetStringField("depth_type", DepthType); 48 | return Ret; 49 | } 50 | }; 51 | 52 | class Image4File { 53 | public: 54 | FString Path; 55 | FString Channel0; 56 | FString Channel1; 57 | FString Channel2; 58 | FString ChannelAlpha; 59 | TSharedPtr ToJson() 60 | { 61 | TSharedPtr Ret = MakeShareable(new FJsonObject()); 62 | Ret->SetStringField("path", Path); 63 | Ret->SetStringField("channel_0", Channel0); 64 | Ret->SetStringField("channel_1", Channel1); 65 | Ret->SetStringField("channel_2", Channel2); 66 | Ret->SetStringField("channel_alpha", ChannelAlpha); 67 | return Ret; 68 | } 69 | }; 70 | 71 | class Image1File { 72 | public: 73 | FString Path; 74 | FString Channel0; 75 | TSharedPtr ToJson() 76 | { 77 | TSharedPtr Ret = MakeShareable(new FJsonObject()); 78 | Ret->SetStringField("path", Path); 79 | Ret->SetStringField("channel_0", Channel0); 80 | return Ret; 81 | } 82 | }; 83 | 84 | class DepthImageFile { 85 | public: 86 | Image4File Color; 87 | Image1File Depth; 88 | TSharedPtr ToJson() 89 | { 90 | TSharedPtr Ret = MakeShareable(new FJsonObject()); 91 | Ret->SetObjectField("color", Color.ToJson()); 92 | Ret->SetObjectField("depth", Depth.ToJson()); 93 | return Ret; 94 | } 95 | }; 96 | 97 | class SeuratView { 98 | public: 99 | ProjectiveCamera ProjectiveCamera; 100 | DepthImageFile DepthImageFile; 101 | TSharedPtr ToJson() 102 | { 103 | TSharedPtr Ret = MakeShareable(new FJsonObject()); 104 | Ret->SetObjectField("projective_camera", ProjectiveCamera.ToJson()); 105 | Ret->SetObjectField("depth_image_file", DepthImageFile.ToJson()); 106 | return Ret; 107 | } 108 | }; 109 | 110 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SceneCaptureSeurat.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "SceneCaptureSeurat.h" 17 | #include "Seurat.h" 18 | #include "Components/SceneCaptureComponent2D.h" 19 | 20 | 21 | ASceneCaptureSeurat::ASceneCaptureSeurat(const FObjectInitializer& ObjectInitializer) 22 | : Super(ObjectInitializer) 23 | { 24 | // Initialize capturing parameters. 25 | Resolution = ECaptureResolution::K1024; 26 | SamplesPerFace = EPositionSampleCount::K8; 27 | HeadboxSize = FVector(100, 100, 100); 28 | GetCaptureComponent2D()->bCaptureEveryFrame = false; 29 | GetCaptureComponent2D()->bCaptureOnMovement = false; 30 | PrimaryActorTick.bCanEverTick = true; 31 | } 32 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SceneCaptureSeurat.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | #include "CoreMinimal.h" 18 | #include "Engine/SceneCapture2D.h" 19 | #include "GameFramework/Actor.h" 20 | #include "SceneCaptureSeurat.generated.h" 21 | 22 | UENUM() 23 | enum class EPositionSampleCount : uint8 24 | { 25 | K2 = 1, 26 | K4 = 2, 27 | K8 = 3, 28 | K16 = 4, 29 | K32 = 5, 30 | K64 = 6, 31 | K128 = 7, 32 | K256 = 8, 33 | }; 34 | 35 | UENUM() 36 | enum class ECaptureResolution : uint8 37 | { 38 | K512 = 9, 39 | K1024 = 10, 40 | K2048 = 11, 41 | K4096 = 12, 42 | K1536 = 13, 43 | }; 44 | 45 | UCLASS(hidecategories = (Collision, Material, Attachment, Actor), MinimalAPI) 46 | class ASceneCaptureSeurat : public ASceneCapture2D 47 | { 48 | GENERATED_UCLASS_BODY() 49 | 50 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SeuratSettings) 51 | FVector HeadboxSize; 52 | 53 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SeuratSettings, meta = (DisplayName = "Samples Per Face")) 54 | EPositionSampleCount SamplesPerFace; 55 | 56 | UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = SeuratSettings, meta = (DisplayName = "Resolution")) 57 | ECaptureResolution Resolution; 58 | }; 59 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SceneCaptureSeuratDetail.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "SceneCaptureSeuratDetail.h" 17 | #include "SeuratConfigWindow.h" 18 | #include "PropertyEditorModule.h" 19 | #include "DetailCategoryBuilder.h" 20 | #include "DetailLayoutBuilder.h" 21 | #include "DetailWidgetRow.h" 22 | #include "SlateOptMacros.h" 23 | 24 | #define LOCTEXT_NAMESPACE "SceneCaptureSeuratDetails" 25 | 26 | TSharedRef FSceneCaptureSeuratDetail::MakeInstance() 27 | { 28 | return MakeShareable(new FSceneCaptureSeuratDetail); 29 | } 30 | 31 | FSceneCaptureSeuratDetail::~FSceneCaptureSeuratDetail() 32 | { 33 | } 34 | 35 | BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION 36 | void FSceneCaptureSeuratDetail::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) 37 | { 38 | TArray< TWeakObjectPtr > OutObjects; 39 | DetailLayout.GetObjectsBeingCustomized(OutObjects); 40 | 41 | if (OutObjects.Num() == 0) 42 | { 43 | return; 44 | } 45 | 46 | ASceneCaptureSeurat* CaptureCameraActor = Cast(OutObjects[0].Get()); 47 | 48 | if (CaptureCameraActor == nullptr) 49 | { 50 | UE_LOG(Seurat, Error, TEXT("Detail panel cannot find the corresponding actor of SceneCaptureSeurat.")); 51 | return; 52 | } 53 | 54 | IDetailCategoryBuilder& BrushBuilderCategory = DetailLayout.EditCategory("SeuratSettings", FText::GetEmpty(), ECategoryPriority::Important); 55 | BrushBuilderCategory.AddCustomRow(FText::GetEmpty(), true) 56 | [ 57 | SNew(SHorizontalBox) 58 | + SHorizontalBox::Slot() 59 | .FillWidth(1) 60 | .Padding(1.0f) 61 | [ 62 | SNew(SSeuratConfigWindow, CaptureCameraActor) 63 | ] 64 | ]; 65 | } 66 | END_SLATE_FUNCTION_BUILD_OPTIMIZATION 67 | 68 | #undef LOCTEXT_NAMESPACE -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SceneCaptureSeuratDetail.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "CoreMinimal.h" 19 | #include "IDetailCustomization.h" 20 | 21 | class FSceneCaptureSeuratDetail : public IDetailCustomization 22 | { 23 | public: 24 | /** Makes a new instance of this detail layout class for a specific detail view requesting it */ 25 | static TSharedRef MakeInstance(); 26 | 27 | ~FSceneCaptureSeuratDetail(); 28 | 29 | /** IDetailCustomization interface */ 30 | virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override; 31 | }; -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/Seurat.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "Seurat.h" 17 | #include "SeuratConfigWindow.h" 18 | #include "SceneCaptureSeuratDetail.h" 19 | #include "JsonManifest.h" 20 | 21 | #include "Framework/SlateDelegates.h" 22 | #include "Engine/TextureRenderTarget2D.h" 23 | #include "Engine/TextureRenderTargetCube.h" 24 | #include "Misc/FileHelper.h" 25 | 26 | #include "SeuratStyle.h" 27 | #include "SeuratCommands.h" 28 | 29 | #if WITH_EDITOR 30 | #include "Editor.h" 31 | #include "Editor/EditorPerProjectUserSettings.h" 32 | #include "Editor/EditorPerformanceSettings.h" 33 | #include "LevelEditor.h" 34 | #include "Kismet/GameplayStatics.h" 35 | #endif // WITH_EDITOR 36 | 37 | static const FString kSeuratOutputDir = FPaths::ConvertRelativePathToFull(FPaths::GameIntermediateDir() / "SeuratCapture"); 38 | static const int32 kTimerExpirationsPerCapture = 4; 39 | 40 | #define LOCTEXT_NAMESPACE "FSeuratModule" 41 | 42 | FSeuratModule::FSeuratModule() : InitialPosition(FVector::ZeroVector), 43 | InitialRotation(FRotator::ZeroRotator), bNeedRestoreRealtime(false), 44 | bNeedRestoreGamePaused(false), bNeedRestoreMonitorEditorPerformance(false), 45 | WorldFromReferenceCameraMatrixSeurat(FMatrix::Identity) 46 | { 47 | } 48 | 49 | // Pause the time flow before capture, since Seurat only works with static scenes. 50 | bool FSeuratModule::PauseTimeFlow(ASceneCaptureSeurat* InCaptureCamera) 51 | { 52 | UWorld* World = InCaptureCamera->GetWorld(); 53 | 54 | if (World->WorldType == EWorldType::Editor) 55 | { 56 | FEditorViewportClient* EditorViewportClient = static_cast(GEditor->GetActiveViewport()->GetClient()); 57 | 58 | bool bRealTime = EditorViewportClient->IsRealtime(); 59 | 60 | if (bRealTime) 61 | { 62 | bNeedRestoreRealtime = true; 63 | EditorViewportClient->SetRealtime(false); 64 | } 65 | return true; 66 | } 67 | else if (World->WorldType == EWorldType::PIE) 68 | { 69 | if (!UGameplayStatics::IsGamePaused(World)) 70 | { 71 | bNeedRestoreGamePaused = true; 72 | if (!UGameplayStatics::SetGamePaused(World, true)) 73 | { 74 | UE_LOG(Seurat, Error, TEXT("Capture process cannot pause the game. Please pause the game manually before capture.")); 75 | } 76 | } 77 | return true; 78 | } 79 | // Seurat plugin only support Editor or PIE mode; 80 | else 81 | { 82 | return false; 83 | } 84 | } 85 | 86 | // Restore time flow to the initial state before capture. 87 | void FSeuratModule::RestoreTimeFlow(ASceneCaptureSeurat* InCaptureCamera) 88 | { 89 | UWorld* World = InCaptureCamera->GetWorld(); 90 | if (bNeedRestoreRealtime && World->WorldType == EWorldType::Editor) 91 | { 92 | FEditorViewportClient* EditorViewportClient = static_cast(GEditor->GetActiveViewport()->GetClient()); 93 | EditorViewportClient->SetRealtime(true); 94 | } 95 | else if (bNeedRestoreGamePaused && World->WorldType == EWorldType::PIE) 96 | { 97 | UGameplayStatics::SetGamePaused(World, false); 98 | } 99 | } 100 | 101 | // Computes the radical inverse base |DigitBase| of the given value |A|. 102 | static float RadicalInverse(uint64 A, uint64 DigitBase) { 103 | float InvBase = 1.0f / DigitBase; 104 | uint64 ReversedDigits = 0; 105 | float InvBaseN = 1.0f; 106 | // Compute the reversed digits in the base entirely in integer arithmetic. 107 | while (A != 0) { 108 | uint64 Next = A / DigitBase; 109 | uint64 Digit = A - Next * DigitBase; 110 | ReversedDigits = ReversedDigits * DigitBase + Digit; 111 | InvBaseN *= InvBase; 112 | A = Next; 113 | } 114 | // Only when done are the reversed digits divided by base^n. 115 | return FMath::Min(ReversedDigits * InvBaseN, 1.0f); 116 | } 117 | 118 | void FSeuratModule::StartupModule() 119 | { 120 | //Register the tick function here. 121 | FWorldDelegates::OnWorldTickStart.AddRaw(this, &FSeuratModule::Tick); 122 | 123 | // Initialize capturing parameters. 124 | CurrentSide = 0; 125 | CurrentSample = -1; 126 | 127 | // Bind the delegate for SceneCaptureCamera UI customization. 128 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 129 | PropertyModule.RegisterCustomClassLayout("SceneCaptureSeurat", FOnGetDetailCustomizationInstance::CreateStatic(&FSceneCaptureSeuratDetail::MakeInstance)); 130 | 131 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 132 | FSeuratStyle::Initialize(); 133 | FSeuratStyle::ReloadTextures(); 134 | 135 | FSeuratCommands::Register(); 136 | 137 | PluginCommands = MakeShareable(new FUICommandList); 138 | 139 | FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); 140 | 141 | { 142 | TSharedPtr MenuExtender = MakeShareable(new FExtender()); 143 | MenuExtender->AddMenuExtension("WindowLayout", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FSeuratModule::AddMenuExtension)); 144 | 145 | LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); 146 | } 147 | 148 | { 149 | TSharedPtr ToolbarExtender = MakeShareable(new FExtender); 150 | ToolbarExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FSeuratModule::AddToolbarExtension)); 151 | 152 | LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); 153 | } 154 | } 155 | 156 | void FSeuratModule::ShutdownModule() 157 | { 158 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 159 | // we call this function before unloading the module. 160 | FSeuratStyle::Shutdown(); 161 | 162 | FSeuratCommands::Unregister(); 163 | 164 | // Unbind the delegate for SceneCaptureCamera UI customization. 165 | FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); 166 | PropertyModule.UnregisterCustomClassLayout("SceneCaptureSeurat"); 167 | } 168 | 169 | void FSeuratModule::AddMenuExtension(FMenuBuilder& Builder) 170 | { 171 | } 172 | 173 | void FSeuratModule::AddToolbarExtension(FToolBarBuilder& Builder) 174 | { 175 | } 176 | 177 | void FSeuratModule::Tick(ELevelTick TickType, float DeltaSeconds) 178 | { 179 | if (CurrentSample < 0) 180 | { 181 | return; 182 | } 183 | 184 | // Lose Camera reference. End the capture. 185 | if (ColorCameraActor == nullptr || ColorCameraActor->IsPendingKill()) 186 | { 187 | CancelCapture(); 188 | return; 189 | } 190 | 191 | --CaptureTimer; 192 | if (CaptureTimer==0) 193 | { 194 | // Write out color data. 195 | FString ColorImageName = BaseImageName + "_ColorDepth.exr"; 196 | WriteImage(ColorCamera->TextureTarget, kSeuratOutputDir / ColorImageName, true); 197 | 198 | if (CurrentSample == Samples.Num()) 199 | { 200 | EndCapture(); 201 | } 202 | 203 | CaptureTimer = kTimerExpirationsPerCapture; 204 | } 205 | else if (CaptureTimer == kTimerExpirationsPerCapture - 1) 206 | { 207 | CaptureSeurat(); 208 | } 209 | } 210 | 211 | // Convert a transformation matrix in Unreal coordinate system to Seurat's 212 | // coordinates. 213 | FMatrix SeuratMatrixFromUnrealMatrix(FMatrix UnrealTransform) { 214 | // Use a change of basis to transform the matrix from Unreal's coordinate 215 | // system into Seurat's. The matrix SeuratFromUnrealCoordinates encodes 216 | // the following change of coordinates: 217 | // 218 | // Unreal +X is forward and maps to Seurat -Z. 219 | // Unreal +Y is right and maps to Seurat +X. 220 | // Unreal +Z is up and maps to Seurat +Y. 221 | const FMatrix SeuratFromUnrealCoordinates( 222 | FPlane(0, 1, 0, 0), 223 | FPlane(0, 0, 1, 0), 224 | FPlane(-1, 0, 0, 0), 225 | FPlane(0, 0, 0, 1)); 226 | 227 | // Apply the change of basis, S = P * U * P^-1, to convert Unreal matrix U to 228 | // Seurat matrix S. Note that some authors define the change of basis with 229 | // the form S = P^-1 * U * P, and in this case P would be the transformation 230 | // from Seurat to Unreal coordinates. 231 | return SeuratFromUnrealCoordinates * UnrealTransform * SeuratFromUnrealCoordinates.Inverse(); 232 | } 233 | 234 | void FSeuratModule::BeginCapture(ASceneCaptureSeurat* InCaptureCamera) 235 | { 236 | // Disable Monitor Editor Performance before capture, so it won't reduce graphic settings and ruin the capture. 237 | UEditorPerformanceSettings* EditorPerformanceSettings = GetMutableDefault(); 238 | bNeedRestoreMonitorEditorPerformance = EditorPerformanceSettings->bMonitorEditorPerformance; 239 | EditorPerformanceSettings->bMonitorEditorPerformance = false; 240 | 241 | UEditorPerProjectUserSettings* EditorUserSettings = GetMutableDefault(); 242 | EditorUserSettings->PostEditChange(); 243 | EditorUserSettings->SaveConfig(); 244 | 245 | // Pause the time since Seurat only works with static scenes. 246 | if (!PauseTimeFlow(InCaptureCamera)) 247 | { 248 | UE_LOG(Seurat, Error, TEXT("Seurat plugin only runs in Editor or PIE mode!")); 249 | return; 250 | } 251 | 252 | ColorCameraActor = InCaptureCamera; 253 | 254 | // The Capture already began, do nothing. 255 | if (CurrentSample >= 0) 256 | { 257 | FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Capture in Progress", "Please wait for current capture progress before start another!")); 258 | return; 259 | } 260 | 261 | // Save initial camera state. 262 | InitialPosition = ColorCameraActor->GetActorLocation(); 263 | InitialRotation = ColorCameraActor->GetActorRotation(); 264 | 265 | // Calculate this matrix before changing capture camera postion. 266 | WorldFromReferenceCameraMatrixSeurat = SeuratMatrixFromUnrealMatrix( 267 | ColorCameraActor->GetTransform().ToMatrixNoScale()); 268 | 269 | ColorCamera = ColorCameraActor->GetCaptureComponent2D(); 270 | ColorCamera->TextureTarget = NewObject(); 271 | int32 InResolution = static_cast(ColorCameraActor->Resolution); 272 | int32 Resolution = InResolution == 13 ? 1536 : FGenericPlatformMath::Pow(2, InResolution); 273 | ColorCamera->CaptureSource = ESceneCaptureSource::SCS_SceneColorSceneDepth; 274 | ColorCamera->TextureTarget->InitCustomFormat(Resolution, Resolution, PF_FloatRGBA, true); 275 | 276 | Samples.Empty(); 277 | FVector HeadboxSize = ColorCameraActor->HeadboxSize; 278 | int32 SamplesPerFace = FGenericPlatformMath::Pow(2, static_cast(ColorCameraActor->SamplesPerFace)); 279 | // Use Hammersly sampling for reproduciblity. 280 | for (int32 SampleIndex = 0; SampleIndex < SamplesPerFace; ++SampleIndex) 281 | { 282 | FVector HeadboxPosition = FVector( 283 | (float)SampleIndex / (float)(SamplesPerFace - 1), 284 | RadicalInverse((uint64)SampleIndex, 2), 285 | RadicalInverse((uint64)SampleIndex, 3)); 286 | HeadboxPosition.X *= HeadboxSize.X; 287 | HeadboxPosition.Y *= HeadboxSize.Y; 288 | HeadboxPosition.Z *= HeadboxSize.Z; 289 | HeadboxPosition -= HeadboxSize * 0.5f; 290 | // Headbox samples are in camera space; transform to world space. 291 | HeadboxPosition = ColorCameraActor->GetTransform().TransformPosition(HeadboxPosition); 292 | Samples.Add(HeadboxPosition); 293 | } 294 | 295 | FVector CameraLocation = ColorCameraActor->GetActorLocation(); 296 | 297 | // Sort samples by distance from center of the headbox. 298 | Samples.Sort([&CameraLocation](const FVector& V1, const FVector& V2) { 299 | return (V1 - CameraLocation).Size() < (V2 - CameraLocation).Size(); 300 | }); 301 | 302 | // Replace the sample closest to the center of the headbox with a sample at 303 | // exactly the center. This is important because Seurat requires 304 | // sampling information at the center of the headbox. 305 | Samples[0] = CameraLocation; 306 | 307 | ViewGroups.Empty(); 308 | CurrentSample = 0; 309 | CurrentSide = 0; 310 | CaptureTimer = kTimerExpirationsPerCapture; 311 | } 312 | 313 | void FSeuratModule::EndCapture() 314 | { 315 | // From Json array to a string. 316 | TSharedPtr SeuratManifest = MakeShareable(new FJsonObject()); 317 | SeuratManifest->SetArrayField("view_groups", ViewGroups); 318 | GenerateJson(SeuratManifest, kSeuratOutputDir); 319 | Samples.Empty(); 320 | ViewGroups.Empty(); 321 | CurrentSample = -1; 322 | 323 | // Restore camera state. 324 | ColorCameraActor->SetActorLocation(InitialPosition); 325 | ColorCameraActor->SetActorRotation(InitialRotation); 326 | 327 | RestoreTimeFlow(ColorCameraActor.Get()); 328 | 329 | // Restore Monitor Editor Performance as it is before the capture. 330 | UEditorPerformanceSettings* EditorPerformanceSettings = GetMutableDefault(); 331 | EditorPerformanceSettings->bMonitorEditorPerformance = bNeedRestoreMonitorEditorPerformance; 332 | 333 | UEditorPerProjectUserSettings* EditorUserSettings = GetMutableDefault(); 334 | EditorUserSettings->PostEditChange(); 335 | EditorUserSettings->SaveConfig(); 336 | 337 | ColorCamera->TextureTarget = nullptr; 338 | ColorCamera = nullptr; 339 | ColorCameraActor = nullptr; 340 | 341 | const EAppReturnType::Type Choice = FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Scene Captured!", "Scene Captured!")); 342 | } 343 | 344 | void FSeuratModule::CancelCapture() 345 | { 346 | Samples.Empty(); 347 | ViewGroups.Empty(); 348 | CurrentSample = -1; 349 | 350 | ColorCamera = nullptr; 351 | ColorCameraActor = nullptr; 352 | 353 | UE_LOG(Seurat, Error, TEXT("Lost Capture Camera reference. Don't modify the scene while capturing.")); 354 | } 355 | 356 | void FSeuratModule::CaptureSeurat() 357 | { 358 | FString BaseName = "Cube"; 359 | 360 | TArray Sides = { 361 | "Front", 362 | "Back", 363 | "Right", 364 | "Left", 365 | "Top", 366 | "Bottom", 367 | }; 368 | 369 | if (CurrentSide == 0) 370 | { 371 | ViewGroup = MakeShareable(new FJsonObject()); 372 | Views.Empty(); 373 | } 374 | 375 | int32 NumSides = Sides.Num(); 376 | int32 Side = CurrentSide; 377 | FString SideName = Sides[Side]; 378 | FRotator FaceRotation = FRotator::ZeroRotator; 379 | switch (Side) 380 | { 381 | case 0: // Front 382 | break; 383 | case 1: // Back 384 | FaceRotation.Yaw += 180.0f; 385 | break; 386 | case 2: // Right 387 | FaceRotation.Yaw += 90.0f; 388 | break; 389 | case 3: // Left 390 | FaceRotation.Yaw += 270.0f; 391 | break; 392 | case 4: // Top 393 | FaceRotation.Pitch += 90.0f; 394 | break; 395 | case 5: // Bottom 396 | FaceRotation.Pitch += 270.0f; 397 | break; 398 | default: 399 | break; 400 | } 401 | 402 | BaseImageName = BaseName + "_" + SideName + "_" + FString::FromInt(CurrentSample); 403 | TSharedPtr View = Capture(FaceRotation, Samples[CurrentSample]); 404 | Views.Add(MakeShareable(new FJsonValueObject(View.ToSharedRef()))); 405 | 406 | ++CurrentSide; 407 | if (CurrentSide == NumSides) 408 | { 409 | CurrentSide = 0; 410 | ++CurrentSample; 411 | ViewGroup->SetArrayField("views", Views); 412 | ViewGroups.Add(MakeShareable(new FJsonValueObject(ViewGroup))); 413 | ViewGroup.Reset(); 414 | } 415 | } 416 | 417 | TSharedPtr FSeuratModule::Capture(FRotator Orientation, FVector Position) 418 | { 419 | // Setup the camera. 420 | ColorCameraActor->SetActorLocation(Position); 421 | ColorCameraActor->SetActorRotation(Orientation); 422 | 423 | // Note that if bCaptureEveryFrame is true and the game is not paused by any means, 424 | // then this function call is redundant. However this is intentional since there are 425 | // several ways by which you can pause the game time, thus "Capture Every Frame" won't 426 | // work and it would rely on these calls to capture properly. Also these calls are 427 | // considered thread safe since they would resolve any CaptureSceneDeferred() before 428 | // enqueue this CaptureScene() command. 429 | ColorCamera->CaptureScene(); 430 | 431 | int32 InResolution = static_cast(ColorCameraActor->Resolution); 432 | int32 Resolution = InResolution == 13 ? 1536 : FGenericPlatformMath::Pow(2, InResolution); 433 | 434 | // Note, this matrix won't necessarily match Unreal's projection matrix. It 435 | // doesn't matter in this case, because the Unreal plug captures eye space Z, 436 | // so it doesn't need to decode from window space. However, the matrix informs 437 | // Seurat of the near clip plane, and that the far clip is infinite. This also 438 | // allows Seurat to do some window space operations on the captured points. 439 | // This matrix is for a 90 degree frustum, infinite Z. 440 | float C = -1.0f; 441 | float D = -2 * GNearClippingPlane; 442 | FMatrix ClipFromEye = FMatrix( 443 | FPlane{ 1, 0, 0, 0 }, 444 | FPlane{ 0, 1, 0, 0 }, 445 | FPlane{ 0, 0, C, C }, 446 | FPlane{ 0, 0, D, 0 }); 447 | // This camera matrix stores this sample location's transformation, as opposed 448 | // to the reference camera transform stored in 449 | // WorldFromReferenceCameraMatrixSeurat. 450 | FMatrix WorldFromEyeSampleCameraUnreal = ColorCameraActor->GetTransform().ToMatrixNoScale(); 451 | 452 | const FMatrix WorldFromEyeSeurat = SeuratMatrixFromUnrealMatrix( 453 | WorldFromEyeSampleCameraUnreal); 454 | // This line has two operations. First, inverting the world-from-eye matrix 455 | // converts the Seurat camera pose matrix to the eye-from-world matrix. 456 | // 457 | // Second, the pre-concatentation of WorldFromReferenceCameraMatrixSeurat's 458 | // matrix shifts all the camera sample matrices to have the eye coordinates of 459 | // the reference camera at the origin. That is, points in world coordinates 460 | // (GetTransform returns actor transforms in world coordinates) near the 461 | // origin transform to around the reference camera position under this matrix. 462 | // 463 | // Finally, regarding the order of the matrices, note that Unreal's 464 | // math library orders transformations left-to-right, so the first 465 | // transformation is on the left, followed by the second transformation on the 466 | // right. 467 | const FMatrix EyeFromWorldSeurat = WorldFromReferenceCameraMatrixSeurat * WorldFromEyeSeurat.Inverse(); 468 | 469 | SeuratView MyView; 470 | 471 | MyView.ProjectiveCamera.ImageWidth = Resolution; 472 | MyView.ProjectiveCamera.ImageHeight = Resolution; 473 | MyView.ProjectiveCamera.ClipFromEyeMatrix = ClipFromEye; 474 | MyView.ProjectiveCamera.WorldFromEyeMatrix = EyeFromWorldSeurat.Inverse(); 475 | MyView.ProjectiveCamera.DepthType = "EYE_Z"; 476 | MyView.DepthImageFile.Color.Path = BaseImageName + "_ColorDepth.exr"; 477 | MyView.DepthImageFile.Color.Channel0 = "R"; 478 | MyView.DepthImageFile.Color.Channel1 = "G"; 479 | MyView.DepthImageFile.Color.Channel2 = "B"; 480 | MyView.DepthImageFile.Color.ChannelAlpha = "CONSTANT_ONE"; 481 | MyView.DepthImageFile.Depth.Path = BaseImageName + "_ColorDepth.exr"; 482 | MyView.DepthImageFile.Depth.Channel0 = "A"; 483 | 484 | return MyView.ToJson(); 485 | } 486 | 487 | void FSeuratModule::WriteImage(UTextureRenderTarget2D* InRenderTarget, FString Filename, bool bClearAlpha) 488 | { 489 | FTextureRenderTargetResource* RTResource = InRenderTarget->GameThread_GetRenderTargetResource(); 490 | 491 | // We're reading back depth in centimeters from alpha, and linear lighting. 492 | const ERangeCompressionMode kDontRangeCompress = RCM_MinMax; 493 | FReadSurfaceDataFlags ReadPixelFlags(kDontRangeCompress); 494 | // We always want linear output. 495 | ReadPixelFlags.SetLinearToGamma(false); 496 | 497 | TArray OutBMP; 498 | RTResource->ReadLinearColorPixels(OutBMP, ReadPixelFlags); 499 | 500 | FIntRect SourceRect; 501 | 502 | FIntPoint DestSize(InRenderTarget->GetSurfaceWidth(), InRenderTarget->GetSurfaceHeight()); 503 | 504 | FString ResultPath; 505 | FHighResScreenshotConfig& HighResScreenshotConfig = GetHighResScreenshotConfig(); 506 | HighResScreenshotConfig.bCaptureHDR = true; 507 | HighResScreenshotConfig.SaveImage(Filename, OutBMP, DestSize); 508 | } 509 | 510 | bool FSeuratModule::SaveStringTextToFile(FString SaveDirectory, FString FileName, FString SaveText, bool AllowOverWriting) 511 | { 512 | IFileManager* FileManager = &IFileManager::Get(); 513 | 514 | if (!FileManager) return false; 515 | 516 | // Dir Exists? 517 | if (!FileManager->DirectoryExists(*SaveDirectory)) 518 | { 519 | // Create directory if it not exist. 520 | FileManager->MakeDirectory(*SaveDirectory); 521 | 522 | // Still could not make directory? 523 | if (!FileManager->DirectoryExists(*SaveDirectory)) 524 | { 525 | // Could not make the specified directory. 526 | return false; 527 | } 528 | } 529 | 530 | // Get complete file path. 531 | SaveDirectory /= FileName; 532 | 533 | // No over-writing? 534 | if (!AllowOverWriting) 535 | { 536 | // Check if file exists already. 537 | if (FileManager->GetFileAgeSeconds(*SaveDirectory) > 0) 538 | { 539 | // No overwriting. 540 | return false; 541 | } 542 | } 543 | 544 | return FFileHelper::SaveStringToFile(SaveText, *SaveDirectory); 545 | } 546 | 547 | void FSeuratModule::GenerateJson(TSharedPtr JsonObject, FString ExportPath) 548 | { 549 | FString OutputString; 550 | TSharedRef< TJsonWriter< TCHAR, TPrettyJsonPrintPolicy< TCHAR > > > Writer = TJsonWriterFactory< TCHAR, TPrettyJsonPrintPolicy< TCHAR > >::Create(&OutputString); 551 | FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer); 552 | if (!SaveStringTextToFile(ExportPath, "manifest.json", OutputString, true)) 553 | UE_LOG(Seurat, Error, TEXT("Saving json to file failed")); 554 | } 555 | 556 | #undef LOCTEXT_NAMESPACE 557 | 558 | DEFINE_LOG_CATEGORY(Seurat); 559 | IMPLEMENT_MODULE(FSeuratModule, Seurat) -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SeuratConfigWindow.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "SeuratConfigWindow.h" 17 | 18 | #define LOCTEXT_NAMESPACE "FSeuratModule" 19 | 20 | float ProcessInput(const FText& InText, TSharedPtr TextBox, float PrevValue) 21 | { 22 | FString InputText = InText.ToString(); 23 | if (!InputText.IsNumeric()) 24 | { 25 | FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("Invalid input", "Input must be numeric")); 26 | TextBox->SetText(FText::AsNumber(PrevValue)); 27 | return PrevValue; 28 | } 29 | return FCString::Atof(*InputText);; 30 | } 31 | 32 | void SSeuratConfigWindow::Construct(const FArguments& InArgs, ASceneCaptureSeurat* InOwner) 33 | { 34 | Owner = InOwner; 35 | 36 | ChildSlot 37 | [ 38 | SNew(SVerticalBox) 39 | + SVerticalBox::Slot() 40 | .Padding(2.0f) 41 | .AutoHeight() 42 | [ 43 | SNew(SButton) 44 | .Text(LOCTEXT("Capture", "Capture")) 45 | .ToolTipText(LOCTEXT("Capture", "Capture")) 46 | .HAlign(HAlign_Center) 47 | .VAlign(VAlign_Center) 48 | .OnClicked(this, &SSeuratConfigWindow::Capture) 49 | ] 50 | ]; 51 | } 52 | 53 | FReply SSeuratConfigWindow::Capture() 54 | { 55 | FSeuratModule* SeuratModule = FModuleManager::GetModulePtr("Seurat"); 56 | if (SeuratModule != nullptr) 57 | { 58 | SeuratModule->BeginCapture(Owner); 59 | } 60 | return FReply::Handled(); 61 | } 62 | 63 | #undef LOCTEXT_NAMESPACE 64 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SeuratConfigWindow.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "CoreMinimal.h" 19 | #include "Widgets/SCompoundWidget.h" 20 | #include "Widgets/DeclarativeSyntaxSupport.h" 21 | #include "SceneCaptureSeurat.h" 22 | 23 | class SSeuratConfigWindow : public SCompoundWidget 24 | { 25 | public: 26 | 27 | SLATE_BEGIN_ARGS(SSeuratConfigWindow) {} 28 | SLATE_END_ARGS() 29 | 30 | ASceneCaptureSeurat *Owner; 31 | 32 | void Construct(const FArguments& InArgs, ASceneCaptureSeurat* InOwner); 33 | 34 | private: 35 | FReply Capture(); 36 | }; -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SeuratPrivatePCH.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "Seurat.h" 17 | 18 | // You should place include statements to your module's private header files here. You only need to 19 | // add includes for headers that are used in most of your module's source files though. -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Private/SeuratStyle.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "SeuratStyle.h" 17 | #include "SlateGameResources.h" 18 | #include "Styling/SlateStyleRegistry.h" 19 | #include "IPluginManager.h" 20 | 21 | TSharedPtr< FSlateStyleSet > FSeuratStyle::StyleInstance = NULL; 22 | 23 | void FSeuratStyle::Initialize() 24 | { 25 | if (!StyleInstance.IsValid()) 26 | { 27 | StyleInstance = Create(); 28 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); 29 | } 30 | } 31 | 32 | void FSeuratStyle::Shutdown() 33 | { 34 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); 35 | ensure(StyleInstance.IsUnique()); 36 | StyleInstance.Reset(); 37 | } 38 | 39 | FName FSeuratStyle::GetStyleSetName() 40 | { 41 | static FName StyleSetName(TEXT("SeuratStyle")); 42 | return StyleSetName; 43 | } 44 | 45 | #define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 46 | #define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 47 | #define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) 48 | #define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) 49 | #define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) 50 | 51 | const FVector2D Icon16x16(16.0f, 16.0f); 52 | const FVector2D Icon20x20(20.0f, 20.0f); 53 | const FVector2D Icon40x40(40.0f, 40.0f); 54 | 55 | TSharedRef< FSlateStyleSet > FSeuratStyle::Create() 56 | { 57 | TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("SeuratStyle")); 58 | Style->SetContentRoot(IPluginManager::Get().FindPlugin("Seurat")->GetBaseDir() / TEXT("Resources")); 59 | 60 | Style->Set("Seurat.OpenPluginWindow", new IMAGE_BRUSH(TEXT("ButtonIcon_40x"), Icon40x40)); 61 | 62 | return Style; 63 | } 64 | 65 | #undef IMAGE_BRUSH 66 | #undef BOX_BRUSH 67 | #undef BORDER_BRUSH 68 | #undef TTF_FONT 69 | #undef OTF_FONT 70 | 71 | void FSeuratStyle::ReloadTextures() 72 | { 73 | if (FSlateApplication::IsInitialized()) 74 | { 75 | FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); 76 | } 77 | } 78 | 79 | const ISlateStyle& FSeuratStyle::Get() 80 | { 81 | return *StyleInstance; 82 | } 83 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Public/Seurat.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "CoreMinimal.h" 19 | #include "Dom/JsonObject.h" 20 | #include "Engine/EngineBaseTypes.h" 21 | #include "Framework/Commands/UICommandList.h" 22 | #include "ModuleManager.h" 23 | #include "HighResScreenshot.h" 24 | #include "TextureResource.h" 25 | #include "SceneCaptureSeurat.h" 26 | 27 | class FToolBarBuilder; 28 | class FMenuBuilder; 29 | 30 | class FSeuratModule : public IModuleInterface 31 | { 32 | public: 33 | FSeuratModule(); 34 | 35 | /** IModuleInterface implementation */ 36 | virtual void StartupModule() override; 37 | virtual void ShutdownModule() override; 38 | 39 | void BeginCapture(ASceneCaptureSeurat* InCaptureCamera); 40 | void EndCapture(); 41 | void CancelCapture(); 42 | void Tick(ELevelTick TickType, float DeltaSeconds); 43 | 44 | // Fields related to capture process. 45 | TArray Samples; 46 | TArray> ViewGroups; 47 | TSharedPtr ViewGroup; 48 | TArray> Views; 49 | int32 CurrentSide; 50 | int32 CurrentSample; 51 | int32 CaptureTimer; 52 | 53 | private: 54 | void AddToolbarExtension(FToolBarBuilder& Builder); 55 | void AddMenuExtension(FMenuBuilder& Builder); 56 | 57 | bool PauseTimeFlow(ASceneCaptureSeurat* InCaptureCamera); 58 | void RestoreTimeFlow(ASceneCaptureSeurat* InCaptureCamera); 59 | 60 | private: 61 | TSharedPtr PluginCommands; 62 | TWeakObjectPtr ColorCameraActor; 63 | USceneCaptureComponent2D* ColorCamera; 64 | 65 | // Saves and restores camera actor transformation. 66 | FVector InitialPosition; 67 | FRotator InitialRotation; 68 | 69 | // Tracks editor and in-editor time update state to allow capture to operate 70 | // over multiple frames without time changing. 71 | bool bNeedRestoreRealtime; 72 | bool bNeedRestoreGamePaused; 73 | // Saves and restores editor performance monitoring feature as capture 74 | // automatically disables the feature to capture with precisely the rendering 75 | // features enabled at the start of capture. 76 | bool bNeedRestoreMonitorEditorPerformance; 77 | 78 | // Transforms capture camera in world space to origin. 79 | FMatrix WorldFromReferenceCameraMatrixSeurat; 80 | // Stores the prefix of all capture output files. 81 | FString BaseImageName; 82 | 83 | void WriteImage(UTextureRenderTarget2D* InRenderTarget, FString Filename, bool bClearAlpha); 84 | bool SaveStringTextToFile(FString SaveDirectory, FString FileName, FString SaveText, bool AllowOverWriting); 85 | void CaptureSeurat(); 86 | TSharedPtr Capture(FRotator Orientation, FVector Position); 87 | void GenerateJson(TSharedPtr JsonObject, FString ExportPath); 88 | }; 89 | 90 | DECLARE_LOG_CATEGORY_EXTERN(Seurat, Log, All); -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Public/SeuratCommands.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #include "SeuratCommands.h" 17 | 18 | #define LOCTEXT_NAMESPACE "FSeuratModule" 19 | 20 | void FSeuratCommands::RegisterCommands() 21 | { 22 | // Initialize Seurat editor commands here. 23 | } 24 | 25 | #undef LOCTEXT_NAMESPACE 26 | -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Public/SeuratCommands.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "CoreMinimal.h" 19 | #include "Commands/Commands.h" 20 | #include "SeuratStyle.h" 21 | 22 | class FSeuratCommands : public TCommands 23 | { 24 | public: 25 | 26 | FSeuratCommands() 27 | : TCommands(TEXT("Seurat"), NSLOCTEXT("Contexts", "Seurat", "Seurat Plugin"), NAME_None, FSeuratStyle::GetStyleSetName()) 28 | { 29 | } 30 | 31 | // TCommands<> interface 32 | virtual void RegisterCommands() override; 33 | 34 | public: 35 | }; -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Public/SeuratStyle.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | #pragma once 17 | 18 | #include "CoreMinimal.h" 19 | #include "Styling/SlateStyle.h" 20 | #include "UObject/NameTypes.h" 21 | 22 | /** */ 23 | class FSeuratStyle 24 | { 25 | public: 26 | 27 | static void Initialize(); 28 | 29 | static void Shutdown(); 30 | 31 | /** reloads textures used by slate renderer */ 32 | static void ReloadTextures(); 33 | 34 | /** @return The Slate style set for the Shooter game */ 35 | static const ISlateStyle& Get(); 36 | 37 | static FName GetStyleSetName(); 38 | 39 | private: 40 | 41 | static TSharedRef< class FSlateStyleSet > Create(); 42 | 43 | private: 44 | 45 | static TSharedPtr< class FSlateStyleSet > StyleInstance; 46 | }; -------------------------------------------------------------------------------- /Engine/Plugins/Seurat/Source/Seurat/Seurat.Build.cs: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 Google Inc. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | */ 15 | 16 | using UnrealBuildTool; 17 | 18 | public class Seurat : ModuleRules 19 | { 20 | public Seurat(ReadOnlyTargetRules Target) : base(Target) 21 | { 22 | PublicIncludePaths.AddRange( 23 | new string[] { 24 | "Seurat/Public" 25 | // ... add public include paths required here ... 26 | } 27 | ); 28 | 29 | 30 | PrivateIncludePaths.AddRange( 31 | new string[] { 32 | "Seurat/Private", 33 | // ... add other private include paths required here ... 34 | } 35 | ); 36 | 37 | 38 | PublicDependencyModuleNames.AddRange( 39 | new string[] 40 | { 41 | "Core", 42 | // ... add other public dependencies that you statically link with here ... 43 | } 44 | ); 45 | 46 | 47 | PrivateDependencyModuleNames.AddRange( 48 | new string[] 49 | { 50 | "Projects", 51 | "InputCore", 52 | "UnrealEd", 53 | "LevelEditor", 54 | "CoreUObject", 55 | "Engine", 56 | "Slate", 57 | "SlateCore", 58 | "Json", 59 | "PropertyEditor", 60 | // ... add private dependencies that you statically link with here ... 61 | } 62 | ); 63 | 64 | 65 | DynamicallyLoadedModuleNames.AddRange( 66 | new string[] 67 | { 68 | // ... add any modules that your module loads dynamically here ... 69 | } 70 | ); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Importing Seurat Meshes Into Unreal 2 | 3 | Seurat is a scene simplification technology designed to process very complex 3D scenes into a representation that renders efficiently on mobile 6DoF VR systems. 4 | 5 | This document covers how to import Seurat meshes into Unity. To learn more about the Seurat pipeline, visit the main [Seurat GitHub page](https://github.com/googlevr/seurat). 6 | 7 | ## Introduction 8 | This document describes the process to import the output of the Seurat pipeline 9 | (generated from any source capture) into Epic Games’ Unreal Engine. The 10 | document assumes some familiarity with the Unreal Engine, and is written against 11 | version 4.16. 12 | 13 | ## Import Instructions 14 | Follow these steps: 15 | 16 | ### Setup 17 | 1. Start the Unreal Editor. 18 | 2. Create a new blank project, with no starter content. 19 | 3. Locate the _Content Browser_ panel, typically at the bottom of the Editor 20 | main window. 21 | 4. Locate the folder containing the Seurat output .OBJ, .EXR files. Give them 22 | separate file names, as Unreal requires unique names for each asset. 23 | 5. Disable some rendering settings that interfere with Seurat rendering: 24 | 6. _Edit | Project Settings_ 25 | 7. Locate the _Translucency_ group 26 | 8. Disable _Separate Translucency_ 27 | 9. Locate the _Default Settings_ group 28 | 10. Disable _Bloom,_ _Ambient Occlusion,_ and _Auto Exposure_ 29 | 11. Prepare Light Source for Unlit 30 | 12. Locate the _Light_ group 31 | 13. Set _Intensity_ to 8.0 32 | 14. Disable _Cast Shadows_ 33 | 34 | ### Import the OBJ Model 35 | 1. Click the _Import_ button near the top left corner of the _Content Browser_ 36 | panel. 37 | 2. Navigate to the folder containing the Seurat .OBJ, .PNG, and .EXR file. 38 | 3. Select the .OBJ file and the .EXR file (.PNG import has some artifacts). 39 | 4. Click the _Open_ button. 40 | 5. The Editor displays a model import configuration dialog.\ 41 | ![Unreal import options](images/unreal_01.png) 42 | 6. Change the Import Rotation X axis to 90.0. 43 | 7. If the Seurat capture was processed in meters, then change the Import Uniform 44 | Scale to 100.0 to scale the geometry to centimeters. 45 | 8. Click the _Show Advanced_ rollout in the Mesh options group. 46 | 9. Disable _Build Adjacency Buffer._ 47 | 10. Disable _Generate Lightmap UVs._ 48 | 11. Click Import. 49 | 12. The Editor will import the model and show an icon for it in the _Content 50 | Browser._ 51 | 13. Double click the asset icon. 52 | 14. The Editor will display the Model configuration editor. 53 | 15. Locate the _Details_ panel. 54 | 16. Enable _Use Full Precision UVs_ (to prevent crack artifacts). 55 | 17. Locate the LOD0 options group. 56 | 18. Expand the Build Settings subgroup. 57 | 19. Check _Use Full Precision UVs._ 58 | 20. Finally, place the Seurat mesh into the scene by clicking the imported asset 59 | icon in the _Content Browser_ window and dragging it into the viewport. 60 | 61 | ### Import the Texture 62 | 1. Click the _Import_ button near the top left corner of the _Content Browser_ 63 | panel. 64 | 2. Navigate to the folder containing the Seurat .OBJ, .PNG, and .EXR file. 65 | 3. Select the .OBJ file and the .EXR file (.PNG import has some artifacts: [1][png_01],[2][png_02],[3][png_03]). 66 | 4. Click the _Open_ button. 67 | 5. The Editor will add the texture asset to the _Content Browser_ panel. 68 | 6. Double click the texture. 69 | 7. The Editor will display the Texture editor in a new window. 70 | 8. Locate the _Details_ panel. 71 | 9. If the input EXR is not HDR, change the compression type to RGBA or DXT1/5. 72 | 73 | ### Create the Material 74 | 1. Click the _Add New_ button near the top left corner of the _Content Browser_ 75 | panel. 76 | 2. Click _Material_ in the _Create Basic Asset_ group. 77 | 3. The Editor will add a material with the name _NewMaterial_ in the _Content 78 | Browser._ 79 | 4. Double-click _NewMaterial_ to open it in the Material editor. 80 | 5. Run the _File | Save As..._ command and give the material a meaningful name, 81 | e.g. _SeuratMesh._ 82 | 6. Click Save. 83 | 7. Locate the _Details_ panel. 84 | 8. In the Material options group, change the _Blend Mode_ to _Translucent._ 85 | 9. Configure _Shading Model_ to _Unlit._ 86 | 10. In the material graph viewport, add a TextureSample node. 87 | 11. Right click, open the Texture group, locate TextureSample and click it. 88 | 12. Connect the RGB (white circle) output from the TextureSample node to the 89 | _Emissive Color_ input in the _SeuratMesh_ node. 90 | 13. Connect the alpha (gray circle, near the bottom) output from the 91 | TextureSample node to the _Opacity_ input in the _SeuratMesh_ node. 92 | 14. Save the material. 93 | 94 | [png_01]: https://answers.unrealengine.com/questions/384955/texture-will-fill-empty-areas-with-lines.html 95 | [png_02]: https://forums.unrealengine.com/showthread.php?22982-Importing-any-PNG-with-alpha-channel-messes-up-the-image 96 | [png_03]: https://answers.unrealengine.com/questions/87474/while-importing-a-sprite-sheet-texture-unreal-is-a.html 97 | 98 | DISCLAIMER: This is not an officially supported Google product. 99 | -------------------------------------------------------------------------------- /images/unreal_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlevr/seurat-unreal-plugin/53b7921e5d3ee1f0000c9968f399ff9f0c0794a0/images/unreal_01.png --------------------------------------------------------------------------------