├── 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 | 
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
--------------------------------------------------------------------------------