LoginSignup
8
6

[UE5]SceneViewExtensionが楽しい

Posted at

この記事は Unreal Engine (UE) Advent Calendar 2023 の記事です

はじめに

SceneViewExtensionでエフェクトを作ったら楽しかったので紹介します。

エフェクトの作例
ScreenShot0.png

SceneViewExtensionとは

SceneViewExtensionとはエンジンを改造せずに独自のレンダリングパスを追加できる仕組みです。
独自のアセットの描画や複雑なポストプロセスを実現できます。
上の例では

  • ライン判定時の複数のピクセルへの書き込み
  • GBufferに対するラインの描画
  • スクリーンカラーの縮小バッファの作成
  • 畳み込みを使用した高速なブラー処理
  • Summed Area Tableを使用したKuwaharaフィルター

など通常のマテリアルでは難しい処理を行っています。

SceneViewExtensionの作り方

SceneViewExtensionを含むプラグインの作成の流れを紹介します。
詳しいプラグインの作り方は公式のチュートリアル
完全なソースコードはエンジンに含まれるColor Correct Regions pluginのソースや上記のリポジトリなどを確認してください。

プラグインを作成しローディングフェーズを変更する

MySceneViewExtensionPlugin.uplugin
"LoadingPhase": "PostConfigInit"

シェダーのディレクトリを登録する

MySceneViewExtensionPlugin.cpp
void FMySceneViewExtensionPluginModule::StartupModule()
{
    FString PluginShaderDir = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("MySceneViewExtensionPlugin"))->GetBaseDir(), TEXT("Shaders"));
    AddShaderSourceDirectoryMapping(TEXT("/MySceneViewExtensionShaders"), PluginShaderDir);
}

エフェクトの設定を保持するアクターを作成する

MySceneViewExtensionSettings.h
UCLASS()
class MYSCENEVIEWEXTENSIONPLUGIN_API AMySceneViewExtensionSettings : public AActor
{
    ...
    
	UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "My Scene View Extension")
	float SomeParameter = 0.0f;
};
MySceneViewExtensionSettings.cpp
void AMySceneViewExtensionSettings::BeginPlay()
{
	Super::BeginPlay();

    UMySceneViewExtensionWorldSubsystem* WorldSubsystem = World->GetSubsystem<UMySceneViewExtensionWorldSubsystem>();
    WorldSubsystem->OnActorSpawned(this);
}

SceneViewExtensionを保持するWorldSubsystemを作成する

MySceneViewExtensionWorldSubsystem
struct FMySceneViewExtensionSettingsRenderProxy 
{
    float SomeParameter;
    ...
}

UCLASS()
class MYSCENEVIEWEXTENSIONPLUGIN_API UMySceneViewExtensionWorldSubsystem : public UTickableWorldSubsystem
{
    ...

	TSharedPtr<class FMySceneViewExtension, ESPMode::ThreadSafe> MySceneViewExtension;
	AMySceneViewExtensionSettings* SettingsActor;

    // GameスレッドとRenderスレッドの両方からアクセスするのでミューテックスが必要です
	mutable FCriticalSection Mutex;
	FMySceneViewExtensionSettingsRenderProxy Settings{};
}

SceneViewExtensionを生成する

MySceneViewExtensionWorldSubsystem.cpp
void UMySceneViewExtensionWorldSubsystem::Initialize(FSubsystemCollectionBase& Collection)
{
	Super::Initialize(Collection);
	MySceneViewExtension = FSceneViewExtensions::NewExtension<FMySceneViewExtension>(this);
}

設定を更新する

MySceneViewExtensionWorldSubsystem.cpp
void UMySceneViewExtensionWorldSubsystem::Tick(float DeltaTime)
{
    FMySceneViewExtensionSettingsRenderProxy TempSettings;
    TempSettings.Parameter = SettingsActor->Parameter;
    ...

    FScopeLock Lock(&Mutex);
    Settings = TempSettings;
}

FSceneViewExtensionBaseを継承しレンダリングパスを追加する

MySceneViewExtension.h
class FMySceneViewExtension : public FSceneViewExtensionBase
{
    ... 

	FMySceneViewExtension(const FAutoRegister& AutoRegister, UMySceneViewExtensionWorldSubsystem* WorldSubsystem);
    
	UMySceneViewExtensionWorldSubsystem* WorldSubsystem{};
}
MySceneViewExtension.cpp
// SceneTexturesではなくRenderTargetsを使用します
void FMySceneViewExtension::PostRenderBasePassDeferred_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView, const FRenderTargetBindingSlots& RenderTargets, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTextures)
{
    FMyRenderingPassInputs PassInputs;
    PassInputs.Parameter = WorldSubsystem->GetSettings().SomeParameter;
    ...

    AddMyRenderingPass(GraphBuilder, (const FViewInfo&)InView, PassInputs);
}

// ライティングやフォグの描画の後に呼ばれます
void FMySceneViewExtension::PrePostProcessPass_RenderThread(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessingInputs& Inputs)
{
    ...

    AddSomeRenderingPass(...);
}

void FMySceneViewExtension::SubscribeToPostProcessingPass(EPostProcessingPass Pass, FAfterPassCallbackDelegateArray& InOutPassCallbacks, bool bIsPassEnabled)
{
	if (Pass == EPostProcessingPass::Tonemap)
	{
		InOutPassCallbacks.Add(FAfterPassCallbackDelegate::CreateRaw(this, &FMySceneViewExtension::AfterTonemapPass));
	}
}

FScreenPassTexture FMySceneViewExtension::AfterTonemapPass(FRDGBuilder& GraphBuilder, const FSceneView& InView, const FPostProcessMaterialInputs& Inputs)
{
    ...

	return AddSomePostProcessPass(...);
}

レンダリングパスを実装する

詳しくはUnreal Engine にグローバル シェーダを追加するを参考にしてください。
エンジンの Engine\Source\Runtime\Renderer に含まれるソースコードも非常に参考になります。

MyRenderingPass.cpp
// シェーダーの宣言
class FMyShaderPS : public FGlobalShader
{
public:
    DECLARE_GLOBAL_SHADER(FMyShaderPS);
    SHADER_USE_PARAMETER_STRUCT(FMyShaderPS, FGlobalShader);

    BEGIN_SHADER_PARAMETER_STRUCT(FParameters, )
        SHADER_PARAMETER(float, SomeParameter)
		RENDER_TARGET_BINDING_SLOTS()
    END_SHADER_PARAMETER_STRUCT()
};

IMPLEMENT_GLOBAL_SHADER(FMyShaderPS, "/MySceneViewExtensionShaders/Private/MyShader.usf", "MyShaderPS", SF_Pixel);

void AddMyRenderingPass(GraphBuilder& GraphBuilder, FViewInfo& View, FMyRenderingPassInputs& Inputs)
{
    // バッファを使用したり
	FRDGTextureRef Texture = GraphBuilder.CreateTexture(...);

    // コンピュートシェーダーだったり
    FComputeShaderUtils::AddPass(...);

    // ポストプロセスだったり
    FPixelShaderUtils::AddFullscreenPass(...);

    // 独自のアセットのレンダリングだったり
    GraphBuilder.AddPass(
        [...](FRHICommandList& RHICmdList)
        {
            SetGraphicsPipelineState(...);
			RHICmdList.DrawIndexedPrimitive(...);
        }
    )
}

シェーダーを書く

シェーダー開発を参考にしてください。
r.ShaderDevelopmentModeの設定を行ってください。

/Shaders/Private/MyShader.usf
float SomeParameter;

void MyShaderPS(float4 SvPosition : SV_POSITION, out float4 OutSceneColor : SV_Target0)
{
    OutSceneColor = float4(SomeParameter.xxx, 0);
}

プロジェクトをビルドする

.uprojectファイルを右クリックしGenerate Visual Studio project filesを実行してからプロジェクトをビルドしてください。

レベルにアクターを配置する

ScreenShot3.png

おまけ

エンジンを改造するとさらに好きなタイミングでレンダリングパスを追加できます。
とっても楽!

  • 宣言を追加して
Engine/Source/Runtime/Engine/Public/SceneViewExtension.h
// ISceneViewExtension
virtual void SomewhereInRenderingPass_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView, TRDGUniformBufferRef<FSceneTextureUniformParameters> SceneTextures) {}
  • 好きなタイミングでSceneViewExtensionを呼び出すだけ!
Engine/Source/Runtime/Renderer/Private/DeferredShadingRenderer.cpp
// FDeferredShadingSceneRenderer::Render
for (const TSharedRef<ISceneViewExtension>& ViewExtension : ViewFamily.ViewExtensions)
{
    for (FViewInfo& View : Views)
    {
        ViewExtension->SomewhereInRenderingPass_RenderThread(GraphBuilder, View, SceneTextures.UniformBuffer);
    }
}
		

参考

Using SceneViewExtension to Extend the Rendering System

UnrealEngine レンダリングシステムとその拡張

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6