Image Alt

Geodesic:

UE4 Plugin for Scene Capture rendering to separate window

Unreal engine is a really powerful Engine, but when you try to work with custom rendering, multi-window, or your own shaders and GPU draw calls everything becomes more complicated.

The main reason for over-complication is the custom-made and HUGE UE4 rendering pipeline. Essentially UE4 made something called a “slate framework” as its own GUI, which works through 3D GPU acceleration but is meant for both 2D and 3D Graphics. I won’t say custom rendering is really hard in UE4, but a huge amount of rendering interfaces, modules, and dependencies makes it more complicated.

Today I want to describe and share with you the plugin code for MultiWindow ScreenCapture Rendering; https://github.com/Batname/UE4MultiWindow. I’ve had a challenge with building in custom rendering and custom window creation in the past and now I want to share my knowledge.

We made a small plugin which is focused only on rendering a custom SceneRenderingComponent texture to a Custom Window which works during editing and playing in UE4 Editor.

Essentially this is an Editor plugin which creates a new button in Editor UI, and when you click the Play Scene button opens a new custom window and renders a ScreenCapture texture from the scene to this window.

Alright, lets take a look into the code and go through the logic step by step.

> The plugin is called PlayScene and logic execution starts in PlayScene.cpp

[code language=”cpp”]
void FPlaySceneModule::StartupModule()
{
// Initialize play button ui style
FPlaySceneStyle::Initialize();
FPlaySceneStyle::ReloadTextures();

// Register play capture commands
FPlaySceneCommands::Register();
PluginCommands = MakeShareable(new FUICommandList);

// Add play capture button command
PluginCommands->MapAction(
FPlaySceneCommands::Get().OpenPluginWindow,
FExecuteAction::CreateRaw(this, &FPlaySceneModule::PluginButtonClicked),
FCanExecuteAction());

FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor");

// Add play scene button to editor
{
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FPlaySceneModule::AddToolbarExtension));

LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender);
}
}
[/code]

In StartupModule() we execute logic for Editor PlayScene button styles, register UI commands and all handlers

> When we click PlayScene Button it executes the function PluginButtonClicked()

[code language=”cpp”]
void FPlaySceneModule::PluginButtonClicked()
{
// Init layCapture Window
FPlaySceneSlate::Initialize();
}
[/code]

And it creates a window and starts rendering, let’s move to file PlaySceneSlate.cpp

> Here is how we create the window and assign a viewport

[code language=”cpp”]
FPlaySceneSlate::FPlaySceneSlate()
: PlaySceneWindowWidth(1280)
, PlaySceneWindowHeight(720)
{
// Create SWindow
PlaySceneWindow = SNew(SWindow)
.Title(FText::FromString("PlaySceneWindow"))
.ScreenPosition(FVector2D(0, 0))
.ClientSize(FVector2D(PlaySceneWindowWidth, PlaySceneWindowHeight))
.AutoCenter(EAutoCenter::PreferredWorkArea)
.UseOSWindowBorder(true)
.SaneWindowPlacement(false)
.SizingRule(ESizingRule::UserSized);

FSlateApplication::Get().AddWindow(PlaySceneWindow.ToSharedRef());

// Assign window events delegator
InDelegate.BindRaw(this, &FPlaySceneSlate::OnWindowClosed);
PlaySceneWindow->SetOnWindowClosed(InDelegate);

// Create and assign viewport to window
PlaySceneViewport = SNew(SPlaySceneViewport);
PlaySceneWindow->SetContent(PlaySceneViewport.ToSharedRef());
}
[/code]

> In our custom Viewport we need to create Viewport Client, Scene Viewport and SetViewportInterface to Slate Viewport

[code language=”cpp”]
void SPlaySceneViewport::Construct(const FArguments& InArgs)
{
// Create Viewport Widget
Viewport = SNew(SViewport)
.IsEnabled(true)
.EnableGammaCorrection(false)
.ShowEffectWhenDisabled(false)
.EnableBlending(true)
.ToolTip(SNew(SToolTip).Text(FText::FromString("SPlaySceneViewport")));

// Create Viewport Client
PlaySceneViewportClient = MakeShareable(new FPlaySceneViewportClient());

// Create Scene Viewport
SceneViewport = MakeShareable(new FSceneViewport(PlaySceneViewportClient.Get(), Viewport));

// Assign SceneViewport to Viewport widget. It needed for rendering
Viewport->SetViewportInterface(SceneViewport.ToSharedRef());

// Assing Viewport widget for our custom PlayScene Viewport
this->ChildSlot
[
Viewport.ToSharedRef()
];
}
[/code]

> And the last step is Draw function inside FPlaySceneViewportClient, which is executed each frame and issues the draw call for our Texture from SceneCaptureComponent

[code language=”cpp”]
void FPlaySceneViewportClient::Draw(FViewport * Viewport, FCanvas * Canvas)
{
// Clear entire canvas
Canvas->Clear(FLinearColor::Black);

// Draw SceneCaptureComponent texture to entire canvas
auto TextRenderTarget2D = IPlayScene::Get().GetTextureRenderTarget2D();
if (TextRenderTarget2D.IsValid() && TextRenderTarget2D->Resource != nullptr)
{
FCanvasTileItem TileItem(FVector2D(0, 0), TextRenderTarget2D->Resource,
FVector2D(Viewport->GetRenderTargetTexture()->GetSizeX(), Viewport->GetRenderTargetTexture()->GetSizeY()),
FLinearColor::White);
TileItem.BlendMode = ESimpleElementBlendMode::SE_BLEND_Opaque;
Canvas->DrawItem(TileItem);
}
}
[/code]

Full code with the example can be found at GitHub https://github.com/Batname/UE4MultiWindow

%d bloggers like this: