Gregory Igehy

Dancing at hemisphere coordinate

Notes of RenderToTexture in UE4

UE4 Console variables

static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VSync"));
// Limit framerate on console if VSYNC is enabled to avoid jumps from 30 to 60 and back.
if( CVar->GetValueOnGameThread() != 0 )
{
   // do something
}

----------------------------------------------------
// Use CommandLine command
// optionally set the vsync console variable
if( FParse::Param(FCommandLine::Get(), TEXT("vsync")) )
{
	new(GEngine->DeferredCommands) FString(TEXT("r.vsync 1"));
}

UE4.18 ソースのノート

FDeferredShadingSceneRenderer::Render( ) をざっくり

// DeferredShadingRenderer.cpp

// プリパスの前の処理 : DepthRendering.cpp
RenderPrepass()

// 半透明ライティング用のライトグリッド : LightGridInjection.cpp
ComputeLightGrid()

// オクルージョンクエリ
RenderOcclusion()
FinishOcclusion()

// デプスシャドウ : ShadowDepthRendering.cpp
RenderShadowDepthMaps()

// ボリューメトリックフォグ : VolumetricFog.cpp
ComputeVolumetricFog()

if ( forward )
{
  // フォワードシャドウ投影 : 
  RenderForwardShadingShadowProjections()

  // カプセルシャドウ Indirect : CapsuleShadows.cpp
  RenderIndirectCapsuleShadows()
}

// GBuffer クリア
SetAndClearViewGBuffer()

// ベースパスの描画 : BaseRendering.cpp
RenderBasePass()

// カスタムデプス 
RenderCustomDepthPassAtLocation()

// 速度の描画 : VelocityRendering.cpp
RenderVelocities()

// ベースパス後のポストプロセス : CompositionLighting.cpp
GCompositionLighting.ProcessAfterBasePass()

// ディファードのライティング処理
if ( deferred )
{
    // カプセルシャドウ Indirect : CapsuleShadowRendering.cpp
    RenderIndirectCapsuleShadows()

   // DFAO
   RenderDFAOAsIndirectShadowing() 

   // ライティング : LightRendering.cpp 
   RenderLights(): 

   // ライトグリッドのフィルタリング : TranslucentLighting.cpp
   FilterTraslucentVolumeLighting()

   //  動的スカイライト : DistanceFieldAmbientOcclusion.cpp
   RenderDynamicSkyLighting()

    // リフレクション : ReflectionEnvironment.cpp
    RenderDeferredReclections()
   
   // スクリーンスペース SSS とか : CompositionLighting
   GCompositionLighting.ProcessAfterLighting()
}
    // ライトシャフトのオクルージョン : LightShaftRendering.cpp
    RenderLightShaftOcclusion()

   // 大気散乱フォグ : AtmosphericRendering.cpp
   RenderAtmosphere()

   // フォグ : FogRendering.cpp
   RenderFog()

   // 半透明の描画 : TranslucentLighting.cpp
   RenderTranslucency()

   // 屈折 : DistortionRendering.cpp
   RenderDistortion()

   // ブルーム付きライトシャフト : LightShaftRendering.cpp
   RenderLightsShaftBloom()

   // DistanceField
   RenderDistanceFieldLighting()

   // ポストプロセス : PostProcessing.cpp
   GPostProcessing.Process()  
}

FDeferredShadingSceneRenderer::RenderLights() : LightRendering.cpp

	if (bAllowSimpleLights)
	{
		GatherSimpleLights(ViewFamily, Views, SimpleLights); // LightRendering.cpp
	}

	// Build a list of visible lights.
	for (TSparseArray<FLightSceneInfoCompact>::TConstIterator LightIt(Scene->Lights);
               LightIt; ++LightIt)
	{
            ...
        }

       // ソート
       SortedLights.Sort( FCompareFSortedLightSceneInfo() );

      // Iterate over all lights to be rendered and build ranges for tiled deferred and unshadowed lights
     for (int32 LightIndex = 0; LightIndex < SortedLights.Num(); LightIndex++)
     {
            ...
      }

      if(ViewFamily.EngineShowFlags.DirectLighting)
      {
         	if (ShouldUseTiledDeferred(SupportedByTiledDeferredLightEnd, SimpleLights.InstanceData.Num()) && !bAnyUnsupportedByTiledDeferred && !bAnyViewIsStereo)
                {
                        RenderTiledDeferredLighting();    
                }

               	if (bRenderSimpleLightsStandardDeferred)
		{
			RenderSimpleLightsStandardDeferred(RHICmdList, SimpleLights);
		}

		// Draw non-shadowed non-light function lights without changing render targets between them
		for (int32 LightIndex = StandardDeferredStart; LightIndex < AttenuationLightStart; LightIndex++)
		{
			const FSortedLightSceneInfo& SortedLightInfo = SortedLights[LightIndex];
			const FLightSceneInfo* const LightSceneInfo = SortedLightInfo.LightSceneInfo;

			// Render the light to the scene color buffer, using a 1x1 white texture as input 
			RenderLight(RHICmdList, LightSceneInfo, NULL, false, false);
		}

		if (GUseTranslucentLightingVolumes && GSupportsVolumeTextureRendering)
		{
			if (AttenuationLightStart)
			{
				// Inject non-shadowed, non-light function lights in to the volume.
				SCOPED_DRAW_EVENT(RHICmdList, InjectNonShadowedTranslucentLighting);
				InjectTranslucentVolumeLightingArray(RHICmdList, SortedLights, AttenuationLightStart);
			}
				
			if (SimpleLights.InstanceData.Num() > 0)
			{
				SCOPED_DRAW_EVENT(RHICmdList, InjectSimpleLightsTranslucentLighting);
				InjectSimpleTranslucentVolumeLightingArray(RHICmdList, SimpleLights);
			}
		}
      }
    
 	// Draw shadowed and light function lights
	for (int32 LightIndex = AttenuationLightStart; LightIndex < SortedLights.Num(); LightIndex++)
	{
                 if (bDrawShadows)
		{
                 	RenderShadowProjections(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, bInjectedTranslucentVolume);


                }

		// Render light function to the attenuation buffer.
		if (bDirectLighting)
		{
			if (bDrawLightFunction)
			{
				const bool bLightFunctionRendered = RenderLightFunction(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, bDrawShadows, false);
				bUsedShadowMaskTexture |= bLightFunctionRendered;
			}

			if (bDrawPreviewIndicator)
			{
				RenderPreviewShadowsIndicator(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, bUsedShadowMaskTexture);
			}
                }

		// Render the light to the scene color buffer, conditionally using the attenuation buffer or a 1x1 white texture as input 
		if(bDirectLighting)
		{
			RenderLight(RHICmdList, &LightSceneInfo, ScreenShadowMaskTexture, false, true);
		}
        }
FSceneRenderer::GatherSimpleLights( )
void FSceneRenderer::GatherSimpleLights(const FSceneViewFamily& ViewFamily, const TArray<FViewInfo>& Views, FSimpleLightArray& SimpleLights)
{
	TArray<const FPrimitiveSceneInfo*, SceneRenderingAllocator> PrimitivesWithSimpleLights;

	// Gather visible primitives from all views that might have simple lights
	for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++)
	{
		const FViewInfo& View = Views[ViewIndex];
		for (int32 PrimitiveIndex = 0; PrimitiveIndex < View.VisibleDynamicPrimitives.Num(); PrimitiveIndex++)
		{
			const FPrimitiveSceneInfo* PrimitiveSceneInfo = View.VisibleDynamicPrimitives[PrimitiveIndex];
			const int32 PrimitiveId = PrimitiveSceneInfo->GetIndex();
			const FPrimitiveViewRelevance& PrimitiveViewRelevance = View.PrimitiveViewRelevanceMap[PrimitiveId];

			if (PrimitiveViewRelevance.bHasSimpleLights)
			{
				// TArray::AddUnique is slow, but not expecting many entries in PrimitivesWithSimpleLights
				PrimitivesWithSimpleLights.AddUnique(PrimitiveSceneInfo);
			}
		}
	}

	// Gather simple lights from the primitives
	for (int32 PrimitiveIndex = 0; PrimitiveIndex < PrimitivesWithSimpleLights.Num(); PrimitiveIndex++)
	{
		const FPrimitiveSceneInfo* Primitive = PrimitivesWithSimpleLights[PrimitiveIndex];
		Primitive->Proxy->GatherSimpleLights(ViewFamily, SimpleLights);
	}
}

FPostProcessing::Process( ) をざっくり

// PostProcessing.cpp

FRenderingCompositePassContext CompositeContext(RHICmdList, View);
FPostprocessContext Context(RHICmdList, CompositeContext.Graph, View);
...
if (AllowFullPostProcessing(View, FeatureLevel))
{
   AddPostProcessMaterial(Context, BL_BeforeTranslucency, SeparateTranslucency);
 
   ....

   // ガウシアン DOF の場合
   if(VelocityInput.IsValid())
   {
 	bSepTransWasApplied = AddPostProcessDepthOfFieldGaussian(Context, DepthOfFieldStat, VelocityInput, SeparateTranslucency);
    }
    else
    {
	FRenderingCompositePass* NoVelocity = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessInput(GSystemTextures.BlackDummy));
	FRenderingCompositeOutputRef NoVelocityRef(NoVelocity);
	bSepTransWasApplied = AddPostProcessDepthOfFieldGaussian(Context, DepthOfFieldStat, NoVelocityRef, SeparateTranslucency);
	}
       ....
  
      if(SeparateTranslucency.IsValid() && !bSepTransWasApplied)
      {
		// separate translucency is done here or in AddPostProcessDepthOfFieldBokeh()
    	        FRenderingCompositePass* NodeRecombined = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessBokehDOFRecombine(bIsComputePass));
		NodeRecombined->SetInput(ePId_Input0, Context.FinalOutput);
		NodeRecombined->SetInput(ePId_Input2, SeparateTranslucency);

		Context.FinalOutput = FRenderingCompositeOutputRef(NodeRecombined);
       }

	AddPostProcessMaterial(Context, BL_BeforeTonemapping, SeparateTranslucency);      

        // テンポラル AA
	if( AntiAliasingMethod == AAM_TemporalAA && ViewState)
        {
   	   if(VelocityInput.IsValid())
	    {
		AddTemporalAA( Context, VelocityInput );
	    }
	   else
	    {
		FRenderingCompositePass* NoVelocity = Context.Graph.RegisterPass(new(FMemStack::Get()) FRCPassPostProcessInput(GSystemTextures.BlackDummy));
		FRenderingCompositeOutputRef NoVelocityRef(NoVelocity);
		AddTemporalAA( Context, NoVelocityRef );
		}
	}

       // 自動露出のヒストグラム

       // ダウンサンプリング

       // 自動露出補正

       // ブルーム

      // トーンマッパー
				auto Node = AddSinglePostProcessMaterial(Context, BL_ReplacingTonemapper);

				if(Node)
				{
					// a custom tonemapper is provided
					Node->SetInput(ePId_Input0, Context.FinalOutput);

					// We are binding separate translucency here because the post process SceneTexture node can reference 
					// the separate translucency buffers through ePId_Input1. 
					// TODO: Check if material actually uses this texture and only bind if needed.
					Node->SetInput(ePId_Input1, SeparateTranslucency);
					Node->SetInput(ePId_Input2, BloomOutputCombined);
					Context.FinalOutput = Node;
				}
				else
				{
					Tonemapper = AddTonemapper(Context, BloomOutputCombined, AutoExposure.EyeAdaptation, AutoExposure.MethodId, false, bHDRTonemapperOutput);
				}
      // FXAA 
}
else
{
}
// 可視化
...

if ( dbDOScreenPercentage )
{
    // アップスケール
    FRenderingCompositePass* Node = Context.Graph.RegisterPass(new(FMemStack::Get()) 
                                                            FRCPassPostProcessUpscale(View, UpscaleQuality, PaniniConfig));
    Node->SetInput(ePId_Input0, FRenderingCompositeOutputRef(Context.FinalOutput)); // Bilinear sampling.
    Node->SetInput(ePId_Input1, FRenderingCompositeOutputRef(Context.FinalOutput)); // Point sampling.
    Context.FinalOutput = FRenderingCompositeOutputRef(Node);				
}

Context.FinalOutput = AddPostProcessMaterialChain(Context, BL_AfterTonemapping, SeparateTranslucency, PreTonemapHDRColor, PostTonemapHDRColor);


// グラフを実行
// execute the graph/DAG
CompositeContext.Process(Context.FinalOutput.GetPass(), TEXT("PostProcessing"));
....

Notes of RenderThread

f:id:gregory-igehy:20180315172222j:plain

  • WindowsRunnableThread.cpp
    • (0) uint32 FRunnableThreadWin::GuardedRun()
    • (1) uint32 FRunnableThreadWin::Run()
  • RenderingThread.cpp
    • (2) virtual uint32 RenderingThread::Run(void) override
    • (3) void RenderingThreadMain( FEvent* TaskGraphBoundSyncEvent )
  • TaskGraph.cpp
    • (4) virtual void FNamedTaskThread::ProcessTasksUntilQuit(int32 QueueIndex) override
    • (5) void FNamedTaskThread::ProcessTasksNamedThread(int32 QueueIndex, bool bAllowStall)
  • TaskGraphInterfaces.h
    • (6) virtual void TGraphTask::ExecuteTask(TArray& NewTasks, ENamedThreads::Type CurrentThread) final override
  • SceneRendering.cpp
    • (7) static void RenderViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneRenderer* SceneRenderer)
  • DeferredShading.cpp
    • (8) void FDeferredShadingSceneRenderer::Render(FRHICommandListImmediate& RHICmdList)

Forward shading

f:id:gregory-igehy:20180315180024j:plain

Game 側

UWorld
  • https://docs.unrealengine.com/latest/INT/API/Runtime/Engine/Engine/UWorld/
  • The World is the top level object representing a map or a sandbox in which Actors and Components will exist and be rendered.
  • A World can be a single Persistent Level with an optional list of streaming levels
  • that are loaded and unloaded via volumes and blueprint functions or it can be a collection of levels organized with a World Composition.
  • In a standalone game, generally only a single World exists except during seamless area transitions
  • when both a destination and current world exists. In the editor many Worlds exist:
  • The level being edited, each PIE instance, each editor tool which has an interactive rendered viewport, and many more.
ULevel
  • http://api.unrealengine.com/INT/API/Runtime/Engine/Engine/ULevel/index.html
  • The level object. Contains the level's actor list, BSP information, and brush list.
  • Every Level has a World as its Outer and can be used as the PersistentLevel,
  • however, when a Level has been streamed in the OwningWorld represents the World that it is a part of.
  • A Level is a collection of Actors (lights, volumes, mesh instances etc.).
  • Multiple Levels can be loaded and unloaded into the World to create a streaming experience.
UPrimitiveComponent : 描画や物理と相互作用する基底クラス, カリングの単位
  • https://docs.unrealengine.com/latest/INT/API/Runtime/Engine/Components/UPrimitiveComponent/index.html
  • PrimitiveComponents are SceneComponents that contain or generate some sort of geometry,
  • generally to be rendered or used as collision data.
  • There are several subclasses for the various types of geometry,
  • but the most common by far are the ShapeComponents (Capsule, Sphere, Box),
  • StaticMeshComponent, and SkeletalMeshComponent.
  • ShapeComponents generate geometry that is used for collision detection but are not rendered,
  • while StaticMeshComponents and SkeletalMeshComponents contain pre-built geometry that is rendered, but can also be used for collision detection.
  • ULightComponent : ライト用のクラス
  • FSceneView : FScene を描画する 1 視点

RenderThread 側

  • FScene : UWorld のレンダースレッド版
USceneComponent : FScene に追加する要素の基底クラス
FPrimitiveSceneProxy : UPrimitiveComponent の RenderThread 版
FPrimitiveSceneInfo : レンダラーの内部状態,UPrimitiveComponent と FPrimitiveSceneProxy
FViewInfo : FSceneView のレンダースレッド版
FSceneViewState : 複数フレームに渡るビューの情報を保存するもの
FSceneRenderer : 毎フレーム作られる一時状態

マテリアル

FMaterial : 描画用のマテリアルのインターフェース
FMaterialResource : FMaterial の実装
UMaterialInstance : マテリアルのインスタンス
UMaterialInstanceConstant : マテリアルの定数
UMaterialInstanceDynamic : ランタイムで変えられる

DebugPrint

#include "Engine.h"

if ( GEngine != nullptr )
{
FString str = FString::Printf( TEXT( "last_render_time = %f" ), render_time );
GEngine->AddOnScreenDebugMessage( 2, 1.0f, FColor::Red,
str );
}

UE4 RenderTask

  • class FRenderBasePassDynamicDataThreadTask : public FRenderTask
  • class FRenderPrepassDynamicDataThreadTask : public FRenderTask
  • class FDrawShadowMeshElementsThreadTask : public FRenderTask
  • class FRenderDepthDynamicThreadTask : public FRenderTask
  • class FDrawSortedTransAnyThreadTask : public FRenderTask
  • class FRenderVelocityDynamicThreadTask : public FRenderTask