エンジン改造をせずにC++プラグインとGlobalShaderでPostProcess的なことをできないか調べてみた。
結論としては
- UE4は動的解像度関係で複雑なUV計算などを隠蔽しているのでそのあたりを再現しないと正常に動かない
- UI描画と同じフル解像度に対して処理するので動的解像度の恩恵を受けられない
まぁ、ダメでしたね。
概要
基本的にゲームスレッドからの描画命令は ENQUEUE_RENDER_COMMANDマクロによってキューに積み込んで描画スレッドが処理する。
描画スレッドはキューに積まれた順番に処理をしていく。
ActorのTickで積み込むとScene描画(ベースパスやPostProcess)の前に積み込まれるので、PostProcessでよく使うSceneColorテクスチャなどがまだ出来上がっていない。
ActorにはUI描画の直前(PostProcess後)のタイミングでコールされる関数を設定できるのでそれを利用すればいい感じのタイミングに描画コマンドを差し込めるのでは?
結果
というわけでActorのPostRenderFor()を利用して描画コマンドを積んでみたところをRenderDocで確認してみた。
一応いい感じの場所で自前の描画コマンドが実行されていることが確認できる。
ただし以下の実行結果のようにSceneColorの領域とRendetTargetのViewportのマッチング計算がうまくいっていなかったりしていろいろ問題がある。
(ウィンドウサイズを変更すると描画領域が変わっておかしくなる)
UE4側でToneMap後にHookできるようになったり描画領域に応じたUV補正計算の自動化がサポートされないだろうか…
関連コード
シェーダ
#include "/Engine/Public/Platform.ush" Texture2D SceneColor; SamplerState SceneColorSampler; // フルスクリーンVS void MainVS( in uint GlobalVertexId : SV_VertexID, out float2 OutUV : TEXCOORD0, out float4 OutPosition : SV_POSITION ) { uint VertexId = GlobalVertexId; float2 CellVertexUV = float2(0x1 & ((VertexId + 1) / 3), VertexId & 0x1); // Output vertex position. OutPosition = float4(CellVertexUV * 2 - 1, 0, 1); // Output top left originated UV of the vertex. OutUV = float2(CellVertexUV.x, 1 - CellVertexUV.y); } // フレームバッファ使用PS void MainPS( in noperspective float2 UV : TEXCOORD0, in float4 SvPosition : SV_POSITION, out float4 OutColor : SV_Target0 ) { float4 c = SceneColor.SampleLevel(SceneColorSampler, UV, 0); OutColor = float4(c.x, UV.x, UV.y, 1); }
Actor(.h)
UCLASS() class ANglSampleRenderSystem : public AActor { GENERATED_BODY() public: void BeginPlay(); static void RenderPostProcess(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FRenderTarget* viewRenderTarget); void PostRenderFor(class APlayerController* PC, class UCanvas* Canvas, FVector CameraPosition, FVector CameraDir); private: };
Acotr(.cpp)
class FSamplePostprocessShader : public FGlobalShader { public: static bool ShouldCache(EShaderPlatform Platform) { return IsFeatureLevelSupported(Platform, ERHIFeatureLevel::SM4); } static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) { FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); } static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) { // Useful when adding a permutation of a particular shader return true; } FSamplePostprocessShader() {} // シェーダパラメータと変数をバインド FSamplePostprocessShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) :FGlobalShader(Initializer) { // シェーダパラメータと変数をバインド SceneColor_.Bind(Initializer.ParameterMap, TEXT("SceneColor")); SceneColorSampler_.Bind(Initializer.ParameterMap, TEXT("SceneColorSampler")); } // シリアライズメソッド virtual bool Serialize(FArchive& Ar) override { // シェーダパラメータ変数のシリアライズ bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); Ar << SceneColor_ << SceneColorSampler_; return bShaderHasOutdatedParameters; } // シェーダパラメータ設定をする固有メソッド template<typename TShaderRHIParamRef> void SetParameters( FRHICommandList& RHICmdList, const TShaderRHIParamRef ShaderRHI, FTextureRHIParamRef inTexture ) { //SetTextureParameter(RHICmdList, ShaderRHI, SceneColor_, inTexture); SetTextureParameter( RHICmdList, ShaderRHI, SceneColor_, SceneColorSampler_, TStaticSamplerState<SF_Bilinear, AM_Clamp, AM_Clamp, AM_Clamp>::GetRHI(), inTexture); } private: // シェーダパラメータ変数群 FShaderResourceParameter SceneColor_; FShaderResourceParameter SceneColorSampler_; }; class FSamplePostprocessShaderVS : public FSamplePostprocessShader { DECLARE_SHADER_TYPE(FSamplePostprocessShaderVS, Global); public: /** Default constructor. */ FSamplePostprocessShaderVS() {} /** Initialization constructor. */ FSamplePostprocessShaderVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) :FSamplePostprocessShader(Initializer) { } }; class FSamplePostprocessShaderPS : public FSamplePostprocessShader { DECLARE_SHADER_TYPE(FSamplePostprocessShaderPS, Global); public: /** Default constructor. */ FSamplePostprocessShaderPS() {} /** Initialization constructor. */ FSamplePostprocessShaderPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) :FSamplePostprocessShader(Initializer) { } }; // シェーダコードとGlobalShaderクラスを関連付け IMPLEMENT_SHADER_TYPE(, FSamplePostprocessShaderVS, TEXT("/Plugin/ShaderPlugin/Private/sample_postprocess.usf"), TEXT("MainVS"), SF_Vertex) IMPLEMENT_SHADER_TYPE(, FSamplePostprocessShaderPS, TEXT("/Plugin/ShaderPlugin/Private/sample_postprocess.usf"), TEXT("MainPS"), SF_Pixel) void ANglSampleRenderSystem::BeginPlay() { Super::BeginPlay(); AGameModeBase* Mode = UGameplayStatics::GetGameMode(this); APlayerController*PlayerController = UGameplayStatics::GetPlayerController(this, 0); AHUD* HUD = PlayerController->GetHUD(); // このActorのPostRenderForをコールバック登録 HUD->AddPostRenderedActor(this); // HUDオーバーレイを有効化 HUD->bShowOverlays = true; } void ANglSampleRenderSystem::RenderPostProcess(FRHICommandList& RHICmdList, ERHIFeatureLevel::Type FeatureLevel, const FRenderTarget* viewRenderTarget) { SCOPED_DRAW_EVENTF(RHICmdList, SceneCapture, TEXT("ANglSampleRenderSystem::RenderTest")); auto finalRenderTarget = viewRenderTarget; if (!finalRenderTarget) return; auto ShaderMap = GetGlobalShaderMap(FeatureLevel); TShaderMapRef<FSamplePostprocessShaderVS> vs(ShaderMap); TShaderMapRef<FSamplePostprocessShaderPS> ps(ShaderMap); // PostProcessシステムからバッファを取得 FSceneRenderTargets& SceneContext = FSceneRenderTargets::Get(RHICmdList); auto sceneColor = SceneContext.GetSceneColorTexture(); auto finalRenderTargetSize = finalRenderTarget->GetSizeXY(); // CanvasのViewFamilyから取得したターゲットへ書き込んでみる auto rt_action = ERenderTargetActions::DontLoad_Store; FRHIRenderPassInfo passInfo(finalRenderTarget->GetRenderTargetTexture(), rt_action, nullptr); RHICmdList.BeginRenderPass(passInfo, TEXT("NglSampleRender")); FIntPoint viewportSize(finalRenderTargetSize.X, finalRenderTargetSize.Y); // ターゲットに対してフルスクリーンビューポート RHICmdList.SetViewport( 0, 0, 0.f, viewportSize.X, viewportSize.Y, 1.f); // PSO FGraphicsPipelineStateInitializer GraphicsPSOInit; RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); GraphicsPSOInit.PrimitiveType = PT_TriangleList; GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4(); GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*vs); GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*ps); SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit); // シェーダパラメータ更新 vs->SetParameters(RHICmdList, vs->GetVertexShader(), nullptr); ps->SetParameters(RHICmdList, ps->GetPixelShader(), sceneColor.GetReference());// SceneColorをシェーダリソース利用 // 矩形描画 RHICmdList.DrawPrimitive(0, 2, 1); RHICmdList.EndRenderPass(); } void ANglSampleRenderSystem::PostRenderFor(class APlayerController* PC, class UCanvas* Canvas, FVector CameraPosition, FVector CameraDir) { Super::PostRenderFor(PC, Canvas, CameraPosition, CameraDir); ERHIFeatureLevel::Type FeatureLevel = GetWorld()->Scene->GetFeatureLevel(); Canvas->SizeX; Canvas->SizeY; auto viewRenderTarget = Canvas->SceneView->Family->RenderTarget; ENQUEUE_RENDER_COMMAND(CaptureCommand)( [this, FeatureLevel, viewRenderTarget](FRHICommandListImmediate& RHICmdList) { RenderPostProcess(RHICmdList, FeatureLevel, viewRenderTarget); } ); }