Skip to content

Managed Scene Export and Capture ​

The managed scene API builds an in-memory Simplygon scene in stages. A common order is:

  1. Create a scene context.
  2. Export static mesh component geometry into the context.
  3. Optionally create and append landscape clipping geometry.
  4. Generate or provide capture cameras.
  5. Capture material channels and append them to the same context.
  6. Save the completed scene to an .sgscene file.

See the C++ API references for USimplygonSceneContext and FSimplygonSceneManager for the native lifetime and transaction APIs.

The export, capture and save operations are asynchronous Simplygon Subsystem jobs. Keep the scene context and source components referenced until the workflow finishes, bind the matching completion delegate before starting each job, and compare the result's job ID with the ID returned by the function.

Capture or other compatible resources may be appended before component export. Component export preserves existing scene contents, but the caller is responsible for ensuring that those resources are compatible with the selected export settings. Each context accepts only one component-export attempt. Failures or cancellations after the export transaction begins consume the attempt because the scene may contain partial changes. Cancellation while waiting for the transaction to begin leaves the context reusable.

Export only the required mesh data ​

Capture-driven material baking often needs positions and triangles but does not need Unreal materials, texture coordinates, normals, tangents or vertex colors in the exported geometry.

cpp
FSgSceneExportSettings ExportSettings;
ExportSettings.MeshExportSettings.MaterialExportMode = ESgMaterialExportMode::None;
ExportSettings.MeshExportSettings.GeometryExportSettings.ExportTexCoords = false;
ExportSettings.MeshExportSettings.GeometryExportSettings.ExportNormals = false;
ExportSettings.MeshExportSettings.GeometryExportSettings.ExportTangents = false;
ExportSettings.MeshExportSettings.GeometryExportSettings.ExportVertexColors = false;
ExportSettings.ComponentExportSettings.ApplyVertexColorsFromComponent = false;

MaterialExportMode=None omits materials and per-triangle material IDs. Disabling texture coordinates also disables Unreal material flattening because flattened materials require generated texture coordinates. Geometry caching compares the data that is actually exported, so excluding attributes can allow more components to reuse identical geometry.

Append landscape clipping geometry ​

Native builders and subsystem workflows can extract landscape geometry independently of the managed scene, then append the reusable descriptions as Simplygon scene-mesh nodes. Set SelectionSetName to the selection set referenced by the processing pipeline's clipping-geometry settings.

Only landscape proxies with Unreal's Use Landscape for Culling Invisible HLOD Vertices (bUseLandscapeForCullingInvisibleHLODVertices) setting enabled contribute clipping geometry.

The synchronous native APIs are declared in Scene/SimplygonSceneGeometry.h:

cpp
SG::FTaskResult<TArray<FSgSceneMeshDescription>> CreateLandscapeSceneMeshDescriptions(
	UWorld* InWorld,
	const FSgLandscapeGeometryExportSettings& InSettings,
	SG::Job::FWorkContext* InWorkContext = nullptr,
	float InTotalWorkProgress = 1.0f);

SG::FTaskResult<void> AppendSceneMeshDescriptionsToSceneContext(
	USimplygonSceneContext* InSceneContext,
	const TArray<FSgSceneMeshDescription>& InSceneMeshDescriptions,
	const FSgAppendSceneGeometrySettings& InSettings,
	SG::Job::FWorkContext* InWorkContext = nullptr,
	float InTotalWorkProgress = 1.0f);
cpp
FSgLandscapeGeometryExportSettings LandscapeSettings;
LandscapeSettings.WorldBounds = SourceBounds.GetBox();
LandscapeSettings.LandscapeCullingPrecision = ELandscapeCullingPrecision::Medium;

auto LandscapeResult =
	SG::SceneGeometry::CreateLandscapeSceneMeshDescriptions(World, LandscapeSettings);
if (LandscapeResult.HasError())
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *LandscapeResult.GetError().Message);
	return;
}

FSgAppendSceneGeometrySettings AppendSettings;
AppendSettings.SelectionSetName = TEXT("LandscapeClippingGeometry");
AppendSettings.GeometryExportSettings.ExportTexCoords = false;
AppendSettings.GeometryExportSettings.ExportNormals = false;
AppendSettings.GeometryExportSettings.ExportTangents = false;
AppendSettings.GeometryExportSettings.ExportVertexColors = false;

auto AppendResult = SG::SceneGeometry::AppendSceneMeshDescriptionsToSceneContext(
	SceneContext.Get(),
	LandscapeResult.GetValue(),
	AppendSettings);
if (AppendResult.HasError())
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *AppendResult.GetError().Message);
	return;
}

Both synchronous functions must be called off the game thread. Landscape extraction internally schedules Unreal's required landscape discovery and mesh extraction on the game thread and waits for completion. Native callers choose how to schedule the synchronous operations; the subsystem performs the outer operation as a Job Manager job.

Subsystem jobs participate in normal Job Manager cancellation. Landscape extraction checks for cancellation during World Partition actor discovery and mesh conversion. Append checks while waiting for the scene transaction, between mesh conversions and immediately before scene mutation, so a canceled append does not leave a partially appended batch. Native integrations can pass an SG::Job::FWorkContext to participate in cancellation and progress reporting. InTotalWorkProgress specifies the fraction of the parent job assigned to the operation, matching the export and import APIs.

Each FSgSceneMeshDescription contains shared immutable mesh data, a stable node name and a LocalToWorld transform. The native mesh payload is not exposed as a reflected property. Copying the value shares the native payload rather than duplicating the complete mesh. The type is declared in Scene/SimplygonSceneMeshDescription.h.

Append honors the selected geometry attributes but always omits material IDs. The complete batch is validated and converted before the scene is mutated, and operations targeting the same scene context are serialized through one scene transaction. An empty selection-set name appends only scene nodes. When a selection set is requested, configure the processing pipeline to enable clipping geometry and reference the exact same selection-set name. Each scene node uses its description's LocalToWorld transform; see World Offset for translating component export, appended geometry and scene-capture cameras consistently.

Generate OBB cameras ​

GenerateOBBCamerasForComponents covers the aggregate world-space bounds of the supplied components. A single component transform or single valid instanced-mesh instance retains its oriented bounds. Multiple component or instance transforms use one world-aligned aggregate bound containing every oriented box.

cpp
FSCOBBCameraGenerationSettings CameraSettings;
CameraSettings.ViewDistance = 1000.0;
CameraSettings.ViewportResolution = FIntPoint(1920, 1080);
CameraSettings.ViewportFOVs = FVector2D(90.0, 90.0);
CameraSettings.CaptureCameraFOVs = FVector2D(30.0, 30.0);
CameraSettings.CaptureImageDimensions = FIntPoint(1024, 1024);
CameraSettings.MaximumCameraCount = 128;
CameraSettings.CameraNamePrefix = TEXT("BakeCamera");

const FSubsystemCameraResult Cameras =
	SimplygonSubsystem->GenerateOBBCamerasForComponents(Components, CameraSettings);
if (Cameras.bHasError)
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *Cameras.ErrorMessage);
	return;
}

Use Cameras.Cameras and Cameras.CaptureImageDimensions in the capture request. Camera ViewName values are stable identities and must be unique within the request and destination selection set.

Managed capture channels ​

Managed capture accepts these channels:

Capture bufferSimplygon material channelCapture resultManaged-scene textureColor interpretation
BaseColorBaseColorBGRA8 FColorRGBA8sRGB when reported by the capture
RoughnessRoughnessBGRA8 FColorL8Linear
MetallicMetallicBGRA8 FColorL8Linear
SpecularSpecularBGRA8 FColorL8Linear
EmissiveEmissiveLinear FLinearColorRGB32FLinear HDR raw material emissive
AmbientOcclusionAmbientOcclusionBGRA8 FColorL8Linear authored material AO
WorldNormalWorldNormalBGRA8 FColorRGBA8Linear Unreal world-space normal

WorldNormal pixels remain in Unreal's world-space axis convention. Appending captured data ensures that the normals material channel exists. Scene geometry and camera coordinates are converted to Simplygon coordinates when serialized; this does not imply an axis conversion for material texture values.

When importing a processed scene with the standard Simplygon Unreal material parameters, map the WorldNormal material channel to the NormalTexture parameter.

Direct C++ flow ​

Native tools and builders can use the direct C++ API without creating subsystem jobs. Create a manager-owned context, run long operations off the game thread, and release the context deterministically when the workflow is complete:

cpp
auto SceneManager = FSimplygonSceneManager::Acquire();
if (!SceneManager)
{
	return;
}

auto CreateResult = SceneManager->CreateSceneContext();
if (CreateResult.HasError())
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *CreateResult.GetError().Message);
	return;
}

TStrongObjectPtr<USimplygonSceneContext> SceneContext = CreateResult.StealValue();
ON_SCOPE_EXIT
{
	SceneContext->Release();
};

auto ExportResult =
	SG::Export::ExportStaticMeshComponentsToSceneContext(SceneContext.Get(), Components, ExportSettings);
if (ExportResult.HasError())
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *ExportResult.GetError().Message);
	return;
}

auto CaptureResult =
	SG::SceneCapture::CaptureComponentsToSceneContext(SceneContext.Get(), Components, CaptureSettings);
if (CaptureResult.HasError())
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *CaptureResult.GetError().Message);
	return;
}

auto SaveResult = SceneContext->SaveToFile(OutputPath);
if (SaveResult.HasError())
{
	UE_LOG(LogTemp, Error, TEXT("%s"), *SaveResult.GetError().Message);
}

CreateSceneContext() may be called from any thread. Its optional InOuter defaults to the transient package; the outer controls Unreal object containment, while the scene manager owns the SDK scene. SaveToFile() and LoadFromFile() are synchronous and must run off the game thread. Their SaveToFileAsync() and LoadFromFileAsync() variants may be called from any thread.

Operations targeting the same context are serialized. Operations on different contexts may overlap, while individual Simplygon SDK accesses remain serialized. Release() unregisters the scene deterministically; garbage collection also releases it through BeginDestroy(), but callers should not rely on GC when prompt cleanup is required. IsValid() returns false after release or manager shutdown, and repeated Release() calls are safe. If an operation already owns the context transaction, release prevents new operations while allowing the active transaction to finish before the SDK scene is destroyed.

Subsystem save jobs observe cancellation while waiting for the context transaction. Once Simplygon SDK serialization begins, the SDK does not provide a cancellation mechanism, so the serializer runs to completion before the job reports its result.

World Offset ​

If component export uses ComponentExportSettings.UseWorldOffset, copy the same values to the managed capture settings:

cpp
ExportSettings.ComponentExportSettings.UseWorldOffset = true;
ExportSettings.ComponentExportSettings.WorldOffset = WorldOffset;

CaptureSettings.UseWorldOffset = true;
CaptureSettings.WorldOffset = WorldOffset;

AppendSettings.UseWorldOffset = true;
AppendSettings.WorldOffset = WorldOffset;

Components are rendered, landscapes are discovered and OBB cameras are generated in their original Unreal world coordinates. The offset is applied when component transforms, appended geometry transforms and camera positions/targets are serialized into the Simplygon scene. The camera up vector is not translated.

A common World Partition workflow uses the negative source-bounds origin so every tile is processed near the Simplygon scene origin. The output placement must restore the inverse translation when the processed assets are placed back into the Unreal world; the scene import settings do not currently apply this inverse world offset automatically.

C++ subsystem flow ​

The context, source components, delegate handles and active job ID should be members of the object that owns the workflow. The following callbacks show the important sequencing:

cpp
UPROPERTY(Transient)
TObjectPtr<USimplygonSubsystem> Subsystem;

UPROPERTY(Transient)
TObjectPtr<USimplygonSceneContext> SceneContext;

UPROPERTY(Transient)
TArray<UStaticMeshComponent*> Components;

FString OutputPath;
FGuid ActiveJobID;
FDelegateHandle ExportHandle;
FDelegateHandle CaptureHandle;
FDelegateHandle SaveHandle;

void UMyManagedCapture::Finish()
{
	if (ExportHandle.IsValid())
	{
		Subsystem->GetNativeOnExportToSceneContextCompleted().Remove(ExportHandle);
		ExportHandle.Reset();
	}
	if (CaptureHandle.IsValid())
	{
		Subsystem->GetNativeOnCaptureToSceneContextCompleted().Remove(CaptureHandle);
		CaptureHandle.Reset();
	}
	if (SaveHandle.IsValid())
	{
		Subsystem->GetNativeOnSaveSceneContextCompleted().Remove(SaveHandle);
		SaveHandle.Reset();
	}
	if (SceneContext)
	{
		SceneContext->Release();
		SceneContext = nullptr;
	}
	Components.Empty();
}

void UMyManagedCapture::Start(
	USimplygonSubsystem* SimplygonSubsystem,
	const TArray<UStaticMeshComponent*>& InComponents,
	const FString& InOutputPath)
{
	Subsystem = SimplygonSubsystem;
	Components = InComponents;
	OutputPath = InOutputPath;
	SceneContext = Subsystem->CreateSceneContext();
	if (!SceneContext)
	{
		return;
	}

	ExportHandle = Subsystem->GetNativeOnExportToSceneContextCompleted().AddUObject(
		this, &UMyManagedCapture::OnExportCompleted);

	FSgSceneExportSettings Settings;
	Settings.MeshExportSettings.MaterialExportMode = ESgMaterialExportMode::None;
	Settings.MeshExportSettings.GeometryExportSettings.ExportTexCoords = false;
	Settings.MeshExportSettings.GeometryExportSettings.ExportNormals = false;
	Settings.MeshExportSettings.GeometryExportSettings.ExportTangents = false;
	Settings.MeshExportSettings.GeometryExportSettings.ExportVertexColors = false;
	Settings.ComponentExportSettings.ApplyVertexColorsFromComponent = false;
	ActiveJobID = Subsystem->ExportStaticMeshComponentsToSceneContext(SceneContext, Components, Settings);
}

void UMyManagedCapture::OnExportCompleted(const FExportToSceneContextResult& Result)
{
	if (Result.JobID != ActiveJobID)
	{
		return;
	}
	Subsystem->GetNativeOnExportToSceneContextCompleted().Remove(ExportHandle);
	ExportHandle.Reset();
	if (Result.bHasError)
	{
		UE_LOG(LogTemp, Error, TEXT("%s"), *Result.ErrorMessage);
		Finish();
		return;
	}

	const FSubsystemCameraResult Generated =
		Subsystem->GenerateOBBCamerasForComponents(Components, FSCOBBCameraGenerationSettings());
	if (Generated.bHasError)
	{
		UE_LOG(LogTemp, Error, TEXT("%s"), *Generated.ErrorMessage);
		Finish();
		return;
	}

	FSgCaptureComponentsToSceneSettings Settings;
	Settings.CameraSelectionSetName = TEXT("BakeCameras");
	Settings.CaptureRequestSettings.SceneSettings.bForceLOD0 = true;
	Settings.CaptureRequestSettings.CaptureSettings.Name = TEXT("ManagedBakeCapture");
	Settings.CaptureRequestSettings.CaptureSettings.Cameras = Generated.Cameras;
	Settings.CaptureRequestSettings.CaptureSettings.OutputImageDimensions = Generated.CaptureImageDimensions;
	Settings.CaptureRequestSettings.CaptureSettings.BufferTypes =
		static_cast<int32>(ESgBufferCaptureType::BaseColor)
		| static_cast<int32>(ESgBufferCaptureType::Roughness)
		| static_cast<int32>(ESgBufferCaptureType::Metallic)
		| static_cast<int32>(ESgBufferCaptureType::Specular)
		| static_cast<int32>(ESgBufferCaptureType::WorldNormal);

	CaptureHandle = Subsystem->GetNativeOnCaptureToSceneContextCompleted().AddUObject(
		this, &UMyManagedCapture::OnCaptureCompleted);
	ActiveJobID = Subsystem->CaptureComponentsToSceneContext(SceneContext, Components, Settings);
}

void UMyManagedCapture::OnCaptureCompleted(const FCaptureToSceneContextResult& Result)
{
	if (Result.JobID != ActiveJobID)
	{
		return;
	}
	Subsystem->GetNativeOnCaptureToSceneContextCompleted().Remove(CaptureHandle);
	CaptureHandle.Reset();
	if (Result.bHasError)
	{
		UE_LOG(LogTemp, Error, TEXT("%s"), *Result.ErrorMessage);
		Finish();
		return;
	}

	SaveHandle = Subsystem->GetNativeOnSaveSceneContextCompleted().AddUObject(
		this, &UMyManagedCapture::OnSaveCompleted);
	ActiveJobID = Subsystem->SaveSceneContextToFile(SceneContext, OutputPath);
}

void UMyManagedCapture::OnSaveCompleted(const FSaveSceneContextResult& Result)
{
	if (Result.JobID != ActiveJobID)
	{
		return;
	}
	Subsystem->GetNativeOnSaveSceneContextCompleted().Remove(SaveHandle);
	SaveHandle.Reset();
	if (Result.bHasError)
	{
		UE_LOG(LogTemp, Error, TEXT("%s"), *Result.ErrorMessage);
		Finish();
		return;
	}

	UE_LOG(LogTemp, Display, TEXT("Saved managed scene to %s"), *Result.OutputFilePath);
	Finish();
}

Remove every delegate after its matching job completes and call the same cleanup path when aborting the owning object. Release() unregisters the manager-owned scene immediately instead of waiting for UObject garbage collection.

Blueprint flow ​

Use an Editor Utility Blueprint or Editor Utility Widget:

  1. Get the SimplygonSubsystem with Get Editor Subsystem.
  2. Store the result of Create Scene Context in a variable.
  3. Bind On Export To Scene Context Completed.
  4. Call Export Static Mesh Components To Scene Context and store its job ID.
  5. In the export callback, ignore results whose Job ID does not match. Check Has Error before continuing.
  6. Call Generate OBB Cameras For Components and check Has Error.
  7. Copy Cameras and Capture Image Dimensions into Sg Capture Components To Scene Settings.
  8. Bind On Capture To Scene Context Completed, start Capture Components To Scene Context, and store the new job ID.
  9. In the capture callback, filter by job ID and check Has Error.
  10. Bind On Save Scene Context Completed, call Save Scene Context To File, and handle the matching save result.
  11. Call Release Scene Context after the save completes, and also from any abort or error cleanup path.

Keep the scene context and component array in Blueprint variables until the save callback finishes. Do not use a blocking wait; the editor and render thread must continue running while capture jobs execute.

Python examples ​

Managed scene capture ​

The supported example is maintained in the plugin's simplygon_export_import_examples.py file. Select one or more actors with static mesh components and call managed_scene_capture_selected_components_example(). Pass include_landscape_clipping=True to calculate their combined bounds, extract intersecting landscape scene mesh descriptions and append them to ClippingGeometrySet before camera generation and capture.

python
# Export and capture the selected components.
managed_scene_capture_selected_components_example()

# Also append intersecting landscape clipping geometry before capture.
managed_scene_capture_selected_components_example(include_landscape_clipping=True)

The module-level workflow reference keeps Python callbacks, the scene context and source components alive until the save completes. Each callback filters the shared subsystem delegate by job ID. The example also enables Unreal Python keep-alive while the asynchronous jobs run, so it can be launched through -ExecutePythonScript as well as from an existing editor session.

When landscape clipping is enabled, landscape proxies must have Use Landscape for Culling Invisible HLOD Vertices (bUseLandscapeForCullingInvisibleHLODVertices) enabled. When processing the saved scene, enable clipping geometry and use the same ClippingGeometrySet selection-set name.