UE 渲染入门 v2

概述

代码版本5.3,目前讨论PIE状态下的,能打断点
https://www.yuque.com/chenweilin-tryw7/gbfomp/ygwycvgmwps8niby?singleDoc# 《UE 渲染入门 v2》

FViewport::Draw

void UEditorEngine::Tick( float DeltaSeconds, bool bIdleMode )
{
    // 。。。

    if( FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
    {
        for (auto ContextIt = WorldList.CreateIterator(); ContextIt; ++ContextIt)
        {
            FWorldContext &PieContext = *ContextIt;
            if (PieContext.WorldType != EWorldType::PIE)
            {
                continue;
            }

            PlayWorld = PieContext.World();
            GameViewport = PieContext.GameViewport;

            // Render playworld. This needs to happen after the other viewports for screenshots to work correctly in PIE.
            if (PlayWorld && GameViewport && !bIsSimulatingInEditor)
            {
                // Use the PlayWorld as the GWorld, because who knows what will happen in the Tick.
                UWorld* OldGWorld = SetPlayInEditorWorld( PlayWorld );

                // Render everything.
                GameViewport->LayoutPlayers();
                check(GameViewport->Viewport);
                GameViewport->Viewport->Draw();

                // Pop the world
                RestoreEditorWorld( OldGWorld );
            }
        }
    }

    // 。。。
}

在Tick中,如果这一帧允许昂贵的任务,引擎就会遍历所有PIE World
GameViewport = PieContext.GameViewport;
GameViewport->Viewport->Draw();
然后调用Draw

    UPROPERTY()
    TObjectPtr<class UGameViewportClient> GameViewport;
    /** The platform-specific viewport which this viewport client is attached to. */
    FViewport* Viewport;
void FViewport::Draw( bool bShouldPresent /*= true */)
{
    SCOPED_NAMED_EVENT(FViewport_Draw, FColor::Red);
    UWorld* World = GetClient()->GetWorld();

    // Ignore reentrant draw calls, since we can only redraw one viewport at a time.
    static bool bReentrant = false; // 避免重入绘制

    if(!bReentrant)
    {
         // 。。。   
    }
}
void FViewport::Draw( bool bShouldPresent /*= true */)
{
    SCOPED_NAMED_EVENT(FViewport_Draw, FColor::Red);
    UWorld* World = GetClient()->GetWorld();

    // Ignore reentrant draw calls, since we can only redraw one viewport at a time.
    static bool bReentrant = false;
    if(!bReentrant)
    {
        // See what screenshot related features are required
        static const auto CVarDumpFrames = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.BufferVisualizationDumpFrames"));
        GIsHighResScreenshot = GIsHighResScreenshot || bTakeHighResScreenShot;
        bool bAnyScreenshotsRequired = FScreenshotRequest::IsScreenshotRequested() || GIsHighResScreenshot || GIsDumpingMovie;
        bool bBufferVisualizationDumpingRequired = bAnyScreenshotsRequired && CVarDumpFrames && CVarDumpFrames->GetValueOnGameThread();

        // if this is a game viewport, and game rendering is disabled, then we don't want to actually draw anything
        if ( World && World->IsGameWorld() && !bIsGameRenderingEnabled)
        {
            // 不绘制 viewport,但是仍然需要更新关卡流
            // since we aren't drawing the viewport, we still need to update streaming
            World->UpdateLevelStreaming();
        }
        else
        {
            if( GIsHighResScreenshot ) // 是高清截图
            {
                // 准备参数
                const bool bShowUI = false;
                const bool bAddFilenameSuffix = GetHighResScreenshotConfig().FilenameOverride.IsEmpty();
                const bool bHDRScreenshot = GetSceneHDREnabled();

                // 配置保存路径,并且设置 bIsScreenshotRequested = true
                FScreenshotRequest::RequestScreenshot( FString(), bShowUI, bAddFilenameSuffix, bHDRScreenshot);

                // 进入高清截图
                HighResScreenshot();
            }
            else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired) // 有任何截图请求 && 游戏线程 r.BufferVisualizationDumpFrames == 1 (变量在上面)
            {
                // request the screenshot early so we have the name setup that BufferVisualization can dump it's content
                const bool bShowUI = false;
                const bool bAddFilenameSuffix = true;

                // 配置截图保存路径
                FScreenshotRequest::RequestScreenshot( FString(), bShowUI, bAddFilenameSuffix );
            }

            // 屏幕尺寸 > 0
            if( SizeX > 0 && SizeY > 0 )
            {
                // 准备参数 垂直同步相关
                static const auto CVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.VSync"));
                bool bLockToVsync = CVar->GetValueOnGameThread() != 0;
                ULocalPlayer* Player = (GEngine && World) ? GEngine->GetFirstGamePlayer(World) : NULL;
                if ( Player )
                {
                    bLockToVsync |= (Player && Player->PlayerController && Player->PlayerController->bCinematicMode);
                }

                // 渲染线程执行 Viewport->BeginRenderFrame(RHICmdList);
                EnqueueBeginRenderFrame(bShouldPresent);

                // Calculate gamethread time (excluding idle time)
                // 一些时间计算
                {
                    // 。。。
                }

                UWorld* ViewportWorld = ViewportClient->GetWorld();

                // 准备 FCanvas 画布,然后调用Draw准备一些数据丢进命令列表,最后 Flush_GameThread 绘制
                FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
                Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
                {
                    ViewportClient->Draw(this, &Canvas);
                }
                Canvas.Flush_GameThread(); // 刷新游戏画布

                // 事件,截图相关,设置垂直同步
                UGameViewportClient::OnViewportRendered().Broadcast(this);
                ViewportClient->ProcessScreenShots(this);
                SetRequiresVsync(bLockToVsync);

                // 和上面 EnqueueBeginRenderFrame 对应,结束绘制
                EnqueueEndRenderFrame(bLockToVsync, bShouldPresent);

                GInputLatencyTimer.GameThreadTrigger = false;
            }
        }

        // 渲染后,摄像机和电影的一些逻辑略
        // 。。。
    }
}

可以看到

if ( World && World->IsGameWorld() && !bIsGameRenderingEnabled)
{

}
else
{

}

如果有绘制,会走下面的逻辑

if( GIsHighResScreenshot ) 
{
    // 。。。
    HighResScreenshot();
}
else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired)
{

}

if( SizeX > 0 && SizeY > 0 )
{
    // 。。。
    FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
    Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
    {
        ViewportClient->Draw(this, &Canvas);
    }
    Canvas.Flush_GameThread();

    // 。。。
}

核心逻辑是
FCanvas Canvas; // 准备画布
ViewportClient->Draw(this, &Canvas); // 把viewport画到画布上面去,这里是准备一些,绘制的数据,命令等塞进命令列表中。
Canvas.Flush_GameThread(); // 还需要调用更新,才能真正刷新画布。
另外高清截图分支,看 HighResScreenshot 也是一样的,也能找到类似的逻辑,所以可以从这里开始。

FViewport::HighResScreenshot

准备变量

void FViewport::HighResScreenshot()
{
    if (!ViewportClient->GetEngineShowFlags())
    {
        return;
    }

    // We need to cache this as FScreenshotRequest is a global and the filename is
    // cleared out before we use it below
    const FString CachedScreenshotName = FScreenshotRequest::GetFilename();

    // 设置HDR,SizeXY, 然后渲染线程 Resource->InitResource(RHICmdList);,这里的 DummyViewport 是 FRenderResource的子类
    FDummyViewport* DummyViewport = new FDummyViewport(ViewportClient);
    DummyViewport->SetupHDR(GetDisplayColorGamut(), GetDisplayOutputFormat(), GetSceneHDREnabled());
    DummyViewport->SizeX = (GScreenshotResolutionX > 0) ? GScreenshotResolutionX : SizeX;
    DummyViewport->SizeY = (GScreenshotResolutionY > 0) ? GScreenshotResolutionY : SizeY;
    BeginInitResource(DummyViewport);

    // 自己看英文 SetHighResScreenshotMask SetMotionBlur
    ViewportClient->GetEngineShowFlags()->SetHighResScreenshotMask(GetHighResScreenshotConfig().bMaskEnabled);
    ViewportClient->GetEngineShowFlags()->SetMotionBlur(false);

    // 一些控制台参数
    // Forcing 128-bit rendering pipeline
    static IConsoleVariable* SceneColorFormatVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.SceneColorFormat"));
    static IConsoleVariable* PostColorFormatVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.PostProcessingColorFormat"));
    static IConsoleVariable* ForceLODVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ForceLOD"));
    check(SceneColorFormatVar && PostColorFormatVar);
    const int32 OldSceneColorFormat = SceneColorFormatVar->GetInt();
    const int32 OldPostColorFormat = PostColorFormatVar->GetInt();
    const int32 OldForceLOD = ForceLODVar ? ForceLODVar->GetInt() : -1;

    // 是否强制使用128位渲染管线
    if (GetHighResScreenshotConfig().bForce128BitRendering)
    {
        SceneColorFormatVar->Set(5, ECVF_SetByCode);
        PostColorFormatVar->Set(1, ECVF_SetByCode);
    }

    if (ForceLODVar)
    {
        // Force highest LOD
        ForceLODVar->Set(0, ECVF_SetByCode);
    }

    // Render the requested number of frames (at least once)
    static const auto HighResScreenshotDelay = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.HighResScreenshotDelay"));
    const uint32 DefaultScreenshotDelay = 4;
    uint32 FrameDelay = HighResScreenshotDelay ? FMath::Max(HighResScreenshotDelay->GetValueOnGameThread(), 1) : DefaultScreenshotDelay;

开头缓存了一些截图保存路径,然后准备了一些变量。

  • 包括 FDummyViewport,是否HDR,渲染的XY大小,设置完让渲染线程初始化资源
  • 包括 SceneColorFormat,PostColorFormat,ForceLOD,bForce128BitRendering,
  • HighResScreenshotDelay,DefaultScreenshotDelay,FrameDelay 等

什么意思自己看英文

    // End the frame that was started before HighResScreenshot() was called. Pass nullptr for the viewport because there's no need to
    // call EndRenderFrame on the real viewport, as BeginRenderFrame hasn't been called yet.
    HighResScreenshotEndFrame(nullptr);
    GIsHighResScreenshot = false;

重置状态,
HighResScreenshot() 之前开始的帧结束掉,没有实际需要绘制的viewport,所以传了一个nullptr
然后设置 正在高清截图为false

根据帧数延迟渲染

    // Perform run-up.
    while (FrameDelay)
    {
        HighResScreenshotBeginFrame(DummyViewport);
        if (FrameDelay == 1)
        {
            GIsHighResScreenshot = true;
        }
        FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
        {
            ViewportClient->Draw(DummyViewport, &Canvas);
        }
        Canvas.Flush_GameThread();

        // Draw the debug canvas 调试画布
        DummyViewport->GetDebugCanvas()->SetAllowedModes(FCanvas::Allow_DeleteOnRender);
        DummyViewport->GetDebugCanvas()->Flush_GameThread(true);

        HighResScreenshotEndFrame(DummyViewport); // 提交到渲染队列

        FlushRenderingCommands();

        --FrameDelay;
    }

可以看到这里有上面提到的结构
FCanvas Canvas 创建画布
ViewportClient->Draw 绘制
Canvas.Flush_GameThread(); 刷新游戏线程

同时也注意到这个包在了 begin end 里面
HighResScreenshotBeginFrame(DummyViewport);
HighResScreenshotEndFrame(DummyViewport);

我们暂时先不管这里是如何渲染的,往下看
这里可以明显看出来,他在设置一些OLD的值

恢复一些旧变量的值

我们暂时先不管这里是如何渲染的,往下看
这里可以明显看出来,他在设置一些OLD的值

    ViewportClient->GetEngineShowFlags()->SetHighResScreenshotMask(MaskShowFlagBackup);
    ViewportClient->GetEngineShowFlags()->MotionBlur = MotionBlurShowFlagBackup;
    bool bIsScreenshotSaved = ViewportClient->ProcessScreenShots(DummyViewport);

    SceneColorFormatVar->Set(OldSceneColorFormat, ECVF_SetByCode);
    PostColorFormatVar->Set(OldPostColorFormat, ECVF_SetByCode);
    if (ForceLODVar)
    {
        ForceLODVar->Set(OldForceLOD, ECVF_SetByCode);
    }

说明这些变量在截图过程中可能被改掉了。

结束

释放资源
刷新渲染命令,这里面可以看到fence,在等待gpu完成任务

    BeginReleaseResource(DummyViewport);
    FlushRenderingCommands();
    delete DummyViewport;
    // once the screenshot is done we disable the feature to get only one frame
    GIsHighResScreenshot = false;
    bTakeHighResScreenShot = false;

    // Notification of a successful screenshot 
    // 弹框,然用户选择保存
    if ((GIsEditor || !IsFullscreen()) && !GIsAutomationTesting && bIsScreenshotSaved)
    {
        auto Message = NSLOCTEXT("UnrealClient", "HighResScreenshotSavedAs", "High resolution screenshot saved as");
        FNotificationInfo Info(Message);
        Info.bFireAndForget = true;
        Info.ExpireDuration = 5.0f;
        Info.bUseSuccessFailIcons = false;
        Info.bUseLargeFont = false;

        const FString HyperLinkText = FPaths::ConvertRelativePathToFull(CachedScreenshotName);
        Info.Hyperlink = FSimpleDelegate::CreateStatic([](FString SourceFilePath) 
        {
            FPlatformProcess::ExploreFolder(*(FPaths::GetPath(SourceFilePath)));
        }, HyperLinkText);
        Info.HyperlinkText = FText::FromString(HyperLinkText);

        FSlateNotificationManager::Get().AddNotification(Info);
        UE_LOG(LogClient, Log, TEXT("%s %s"), *Message.ToString(), *HyperLinkText);
    }

    // Start a new frame for the real viewport. Same as above, pass nullptr because BeginRenderFrame will be called
    // after this function returns.
    HighResScreenshotBeginFrame(nullptr);
}

FCanvas

在gank这个之前要了解一下canvas

FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
    ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();
    /**
    * Constructor.
    */
    ENGINE_API FCanvas(FRenderTarget* InRenderTarget, FHitProxyConsumer* InHitProxyConsumer, UWorld* InWorld, ERHIFeatureLevel::Type InFeatureLevel, ECanvasDrawMode DrawMode = CDM_DeferDrawing, float InDPIScale = 1.0f);

看构造函数,
需要一个 render target,
一个命中代理消费者这里是空,比如处理鼠标点击命中
World,
渲染Level(其实就是渲染支持的特性的级别)

里面的局部数据

/**
 * Encapsulates the canvas state.
 */
class FCanvas
{
public:
    class FCanvasSortElement
    {
    public:
        bool operator==(const FCanvasSortElement& Other) const { return DepthSortKey == Other.DepthSortKey; }
        int32 DepthSortKey;
        TArray<class FCanvasBaseRenderItem*> RenderBatchArray;
    };
    TArray<FCanvasSortElement> SortedElements; // 从后往前排序的 Batched canvas elements
    TMap<int32,int32> SortedElementLookupMap; // Map from sortkey to array index of SortedElements for faster lookup of existing entries
    int32 LastElementIndex; // 最后一个元素的索引 Store index of last Element off to avoid semi expensive Find()

    ENGINE_API FCanvasSortElement& GetSortElement(int32 DepthSortKey); // Get the sort element for the given sort key. Allocates a new entry if one does not exist
};

Canvas理解为画布吧。
里面有一个 TArray SortedElements; 代表画布的内容,并且根据命名可以知道,他还可以按照深度排序
FCanvasSortElement 又是一个 Array。FCanvasBaseRenderItem
有这么多类都继承他

class FMeshMaterialRenderItem : public FCanvasBaseRenderItem {};
class FMeshMaterialRenderItem2 : public FCanvasBaseRenderItem {};
class FCanvasBatchedElementRenderItem : public FCanvasBaseRenderItem {};
class FCanvasTileRendererItem : public FCanvasBaseRenderItem {};
class FCanvasTriangleRendererItem : public FCanvasBaseRenderItem {};

基类要求重写get和渲染方法

/**
* Base interface for canvas items which can be batched for rendering
*/
class FCanvasBaseRenderItem
{
public:
    virtual ~FCanvasBaseRenderItem()
    {}

    virtual bool Render_RenderThread(FCanvasRenderContext& RenderContext, FMeshPassProcessorRenderState& DrawRenderState, const FCanvas* Canvas) = 0;
    virtual bool Render_GameThread(const FCanvas* Canvas, FCanvasRenderThreadScope& RenderScope) = 0;

    virtual class FCanvasBatchedElementRenderItem* GetCanvasBatchedElementRenderItem() { return NULL; }
    virtual class FCanvasTileRendererItem* GetCanvasTileRendererItem() { return NULL; }
    virtual class FCanvasTriangleRendererItem* GetCanvasTriangleRendererItem() { return NULL; }
};

比如我们看看三角形,如果打断点一般会出现瓦片的那个类。

class FCanvasTriangleRendererItem : public FCanvasBaseRenderItem
{
    class FRenderData
    {
        // ...

        struct FTriangleInst
        {
            FCanvasUVTri Tri;
            FHitProxyId HitProxyId;
        };
        TArray<FTriangleInst> Triangles;
    };

    TSharedPtr<FRenderData> Data;
}

FCanvasUVTri 在点进去,就可以明显看到我们写DX的时候都会定义一个缓冲区,然后传递一些数据过去的那个结构。我们的canvas理解到这,有了一个大体认识,然后来看draw

/** Simple 2d triangle with UVs */
USTRUCT(BlueprintType)
struct FCanvasUVTri
{
    GENERATED_BODY()

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FVector2D V0_Pos;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FVector2D V0_UV;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FLinearColor V0_Color;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FVector2D V1_Pos;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FVector2D V1_UV;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FLinearColor V1_Color;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FVector2D V2_Pos;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FVector2D V2_UV;

    UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = CanvasUVTri)
    FLinearColor V2_Color;
};

FEditorViewportClient::Draw

FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
    ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();

根据断点,我们跑到这里了。开头做了一些变量的缓存设置,包括viewport和 time,world

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    FViewport* ViewportBackup = Viewport;
    Viewport = InViewport ? InViewport : Viewport;

    UWorld* World = GetWorld();
    FGameTime Time;
    if (!World || (GetScene() != World->Scene) || UseAppTime()) 
    {
        Time = FGameTime::GetTimeSinceAppStart();
    }
    else
    {
        Time = World->GetTime();
    }

    // Early out if we are changing maps in editor as there is no reason to render the scene and it may not even be valid (For unsaved maps)
    if (World && World->IsPreparingMapChange())
    {
        return;
    }

    // 。。。

}

然后继续设置一些变量 bStereoRendering 中文是立体渲染,其实就是VR
然后是 DebugCanvas 的一些设置,OverrideShowFlagsFunc 的一个函数钩子。

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    // 。。。

    // Allow HMD to modify the view later, just before rendering
    const bool bStereoRendering = GEngine->IsStereoscopic3D( InViewport );
    FCanvas* DebugCanvas = Viewport->GetDebugCanvas();
    if (DebugCanvas)
    {
        DebugCanvas->SetScaledToRenderTarget(bStereoRendering);
        DebugCanvas->SetStereoRendering(bStereoRendering);
    }
    Canvas->SetScaledToRenderTarget(bStereoRendering);
    Canvas->SetStereoRendering(bStereoRendering);

    FEngineShowFlags UseEngineShowFlags = EngineShowFlags;
    if (OverrideShowFlagsFunc)
    {
        OverrideShowFlagsFunc(UseEngineShowFlags);
    }

    // 。。。
}

FSceneViewFamilyContext & FSceneView

后面一大段,主要解决两个点

  • FSceneViewFamilyContext ViewFamily 构造了一个这个对象,然后对他设置一堆参数
  • 然后通过这个对象,通过 CalcSceneView 创建一个 FSceneView

    void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
    {
    // 。。。
    
    // Setup a FSceneViewFamily/FSceneView for the viewport.
    FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
        Canvas->GetRenderTarget(),
        GetScene(),
        UseEngineShowFlags)
        .SetTime(Time)
        .SetRealtimeUpdate( IsRealtime() && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
        .SetViewModeParam( ViewModeParam, ViewModeParamName ) );
    
    // 。。。
    
    // 省略了一堆 ViewFamily 属性的设置
    
    // 。。。
    
    FSceneView* View = nullptr;
    
    // Stereo rendering
    const bool bStereoDeviceActive = bStereoRendering && GEngine->StereoRenderingDevice.IsValid();
    int32 NumViews = bStereoRendering ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering) : 1;
    for( int StereoViewIndex = 0; StereoViewIndex < NumViews; ++StereoViewIndex )
    {
        View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);
    
        // 。。。
    }
    
    // 。。。

所以首先了解一下 FSceneViewFamilyContext

FSceneViewFamilyContext

/**
 * A view family which deletes its views when it goes out of scope.
 */
class FSceneViewFamilyContext : public FSceneViewFamily
{
public:
    /** Initialization constructor. */
    FSceneViewFamilyContext( const ConstructionValues& CVS)
        :   FSceneViewFamily(CVS)
    {}

    /** Destructor. */
    ENGINE_API virtual ~FSceneViewFamilyContext();
};
/**
 * A set of views into a scene which only have different view transforms and owner actors.
 */
class FSceneViewFamily {};

A set of views into a scene which only have different view transforms and owner actors
场景中的一组视图,仅仅view transforms 和 owner actors 不同

class FSceneViewFamily
{
public:
    struct ConstructionValues
    {
        ENGINE_API ConstructionValues(
            const FRenderTarget* InRenderTarget,
            FSceneInterface* InScene,
            const FEngineShowFlags& InEngineShowFlags
            );

        /** The views which make up the family. */
        const FRenderTarget* RenderTarget;

        /** The render target which the views are being rendered to. */
        FSceneInterface* Scene;

        // 。。。
    };

    // 组成这个视图组的视图
    TArray<const FSceneView*> Views; 

    // 视图模式枚举,比如DX中我们会描述把三角形处理为线框,面,显然为basecolor,的那个枚举
    EViewModeIndex ViewMode;

    // RT
    const FRenderTarget* RenderTarget;

大概看看,类内有一个协助给构造函数传递参数的结构,里面有RT, Scene对象等。
成员函数 TArray<const FSceneView*> Views; 表示这个视图组下的所有视图,
EViewModeIndex 视图的渲染模式,
RT指针

FRenderTarget 是什么呀

/**
 * A render target.
 */
class FRenderTarget
{
public:

    FRenderTarget() {};
    virtual ~FRenderTarget() {};

    ENGINE_API virtual const FTextureRHIRef& GetRenderTargetTexture() const; // 返回 RenderTargetTextureRHI,可以看到整个类只有这一个属性,都在描述它
    ENGINE_API virtual FUnorderedAccessViewRHIRef GetRenderTargetUAV() const; // 新构造一个FUnorderedAccessViewRHIRef(),然后返回

    // 返回一个RDG纹理,RDG是 UE5的一个render pass 管理系统,这里pass是渲染层的意思
    ENGINE_API virtual FRDGTextureRef GetRenderTargetTexture(FRDGBuilder& GraphBuilder) const;

    // 获取 RT 的一些属性,比如大小,gama,色域,是否HDR等
    virtual FIntPoint GetSizeXY() const = 0;
    ENGINE_API virtual float GetDisplayGamma() const;
    virtual EDisplayColorGamut GetDisplayColorGamut() const { return EDisplayColorGamut::sRGB_D65; }
    virtual EDisplayOutputFormat GetDisplayOutputFormat() const { return EDisplayOutputFormat::SDR_sRGB; }
    virtual bool GetSceneHDREnabled() const { return false; }

    // 
    virtual void ProcessToggleFreezeCommand() {};
    virtual bool HasToggleFreezeCommand() { return false; };

    // 读取像素到 xxx
    ENGINE_API bool ReadPixels(TArray< FColor >& OutImageData,FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0) );
    ENGINE_API bool ReadPixelsPtr(FColor* OutImageBytes, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_UNorm, CubeFace_MAX), FIntRect InSrcRect = FIntRect(0, 0, 0, 0));
    ENGINE_API bool ReadFloat16Pixels(TArray<FFloat16Color>& OutputBuffer,ECubeFace CubeFace=CubeFace_PosX);
    ENGINE_API bool ReadLinearColorPixels(TArray<FLinearColor>& OutputBuffer, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0));
    ENGINE_API bool ReadLinearColorPixelsPtr(FLinearColor* OutImageBytes, FReadSurfaceDataFlags InFlags = FReadSurfaceDataFlags(RCM_MinMax, CubeFace_MAX), FIntRect InRect = FIntRect(0, 0, 0, 0));

    virtual FRHIGPUMask GetGPUMask(FRHICommandListImmediate& RHICmdList) const { return FRHIGPUMask::GPU0(); }

protected:

    FTextureRHIRef RenderTargetTextureRHI;

    bool ReadFloat16Pixels(FFloat16Color* OutImageBytes,ECubeFace CubeFace=CubeFace_PosX);
};

缩减一下注释,可以看到类内只有
FTextureRHIRef RenderTargetTextureRHI; 这一属性,整个类都在描述他。

using FTextureRHIRef                   = TRefCountPtr<FRHITexture>;

总之就是一种描述资源的
什么资源
看这个枚举,找几个单词眼熟的 RRT_VertexShader,RRT_PixelShader,RRT_Texture2D,RRT_GPUFence

/** An enumeration of the different RHI reference types. */
enum ERHIResourceType : uint8
{
    RRT_None,

    RRT_SamplerState,
    RRT_RasterizerState,
    RRT_DepthStencilState,
    RRT_BlendState,
    RRT_VertexDeclaration,
    RRT_VertexShader,
    RRT_MeshShader,
    RRT_AmplificationShader,
    RRT_PixelShader,
    RRT_GeometryShader,
    RRT_RayTracingShader,
    RRT_ComputeShader,
    RRT_GraphicsPipelineState,
    RRT_ComputePipelineState,
    RRT_RayTracingPipelineState,
    RRT_BoundShaderState,
    RRT_UniformBufferLayout,
    RRT_UniformBuffer,
    RRT_Buffer,
    RRT_Texture,
    // @todo: texture type unification - remove these
    RRT_Texture2D,
    RRT_Texture2DArray,
    RRT_Texture3D,
    RRT_TextureCube,
    // @todo: texture type unification - remove these
    RRT_TextureReference,
    RRT_TimestampCalibrationQuery,
    RRT_GPUFence,
    RRT_RenderQuery,
    RRT_RenderQueryPool,
    RRT_ComputeFence,
    RRT_Viewport,
    RRT_UnorderedAccessView,
    RRT_ShaderResourceView,
    RRT_RayTracingAccelerationStructure,
    RRT_StagingBuffer,
    RRT_CustomPresent,
    RRT_ShaderLibrary,
    RRT_PipelineBinaryLibrary,

    RRT_Num
};

回到 FSceneViewFamily

class FSceneViewFamily
{
public:
    // 组成这个视图组的视图
    TArray<const FSceneView*> Views; 

    // 视图模式枚举,比如DX中我们会描述把三角形处理为线框,面,显然为basecolor,的那个枚举
    EViewModeIndex ViewMode;

    // RT
    const FRenderTarget* RenderTarget;

    FEngineShowFlags EngineShowFlags; // 标志位
    FGameTime Time; // 时间

    // 计数
    uint32 FrameNumber;
    uint64 FrameCounter = 0;

    bool bAdditionalViewFamily; // 当前视图组是否是附加的
    bool bRealtimeUpdate; // 是否实时更新
    bool bDeferClear; // 是否延迟清除后缓冲区
    bool bResolveScene; // 是否把场景渲染的结果复制给RT

    bool bMultiGPUForkAndJoin; // 是否使用相同的GPU Mask渲染
    bool bForceCopyCrossGPU = false; // 强制把GPU中复制到一个持久资源

    bool bIsMultipleViewFamily = false; // 是否是单帧中的多个视图组之一。会影响遮挡逻辑
    bool bIsFirstViewInMultipleViewFamily = true; // 是否多视图组的第一个视图
    bool bIsSceneTexturesInitialized = false;
    bool bIsViewFamilyInfo = false; // 是否是视图组的信息?

    ESceneCaptureSource SceneCaptureSource; // 场景捕获的来源,例如 ESceneCaptureSource::SCS_SceneColorHDR 以 SceneColor (HDR) in RGB, Inv Opacity in A 输出颜色
    ESceneCaptureCompositeMode SceneCaptureCompositeMode; // 场景捕捉的合成方式,覆盖,叠加,合成

    bool bThumbnailRendering = false; // 是否用于缩略图渲染
    bool bWorldIsPaused;
    bool bIsHDR;
    bool bRequireMultiView; // 是否为 scenecolor and depth 分配多视图
    float GammaCorrection; // 是否使用gama矫正值

    FExposureSettings ExposureSettings; // 相机设置的曝光设置
    TArray<TSharedRef<class ISceneViewExtension, ESPMode::ThreadSafe> > ViewExtensions; // 渲染线程中修改视图参数的扩展
    FDisplayInternalsData DisplayInternalsData; // 调试用的一个面板内容,控制台修改 0 1

    /**
     * Secondary view fraction to support High DPI monitor still with same primary screen percentage range for temporal
     * upscale to test content consistently in editor no mater of the HighDPI scale. 
     */
    float SecondaryViewFraction;
    ESecondaryScreenPercentageMethod SecondaryScreenPercentageMethod;

    int8 LandscapeLODOverride; // 覆盖视口landscape LOD 的标志
    bool bCurrentlyBeingEdited; // 场景是否可编辑
    bool bOverrideVirtualTextureThrottle; // 是否禁用虚拟纹理的标志
    int32 VirtualTextureFeedbackFactor; 

    bool bDrawBaseInfo; // 是否应绘制基本附件体积 
    bool bNullifyWorldSpacePosition; // 是否把着色器世界空间的位置强设为0

    FString ProfileDescription; // Unreal Insights 分析的视图系列的可选描述
    float* ProfileSceneRenderTime; // 场景渲染的时间追踪

    ENGINE_API ERHIFeatureLevel::Type GetFeatureLevel() const; // 获取RHI的特性级别
    EShaderPlatform GetShaderPlatform() const { return GShaderPlatformForFeatureLevel[GetFeatureLevel()]; } // 获取着色器的平台

    FORCEINLINE EDebugViewShaderMode GetDebugViewShaderMode() const { return DVSM_None; }
    FORCEINLINE int32 GetViewModeParam() const { return -1; }
    FORCEINLINE FName GetViewModeParamName() const { return NAME_None; }
    FORCEINLINE bool UseDebugViewVSDSHS() const { return false; }
    FORCEINLINE bool UseDebugViewPS() const { return false; }

    ENGINE_API bool SupportsScreenPercentage() const; // 返回的视图,是否支持屏幕的百分比
    FORCEINLINE bool AllowTranslucencyAfterDOF() const { return bAllowTranslucencyAfterDOF; } // 是否运行景深之后渲染半透明的物体
    ORCEINLINE bool AllowStandardTranslucencySeparated() const { return bAllowStandardTranslucencySeparated; } // 允许标准半透明分离?

    FORCEINLINE const ISceneViewFamilyScreenPercentage* GetScreenPercentageInterface() const { return ScreenPercentageInterface; } // 屏幕的百分比接口
    FORCEINLINE void SetScreenPercentageInterface(ISceneViewFamilyScreenPercentage* InScreenPercentageInterface);

    // 扩展数据
    template<typename TExtensionData> TExtensionData* GetExtentionData()
    template<typename TExtensionData> TExtensionData* GetOrCreateExtentionData()

    FORCEINLINE void SetTemporalUpscalerInterface(UE::Renderer::Private::ITemporalUpscaler* InTemporalUpscalerInterface)
    FORCEINLINE const UE::Renderer::Private::ITemporalUpscaler* GetTemporalUpscalerInterface() const

    FORCEINLINE void SetSecondarySpatialUpscalerInterface(ISpatialUpscaler* InSpatialUpscalerInterface)
    FORCEINLINE const ISpatialUpscaler* GetSecondarySpatialUpscalerInterface() const

    void SetSceneRenderer(ISceneRenderer* NewSceneRenderer) { SceneRenderer = NewSceneRenderer; }
    ISceneRenderer* GetSceneRenderer() const { check(SceneRenderer); return SceneRenderer; }

    ISceneRenderer* SceneRenderer; //  The scene renderer that is rendering this view family. This is only initialized in the rendering thread's copies of the FSceneViewFamily.
    ISceneViewFamilyScreenPercentage* ScreenPercentageInterface; /** Interface to handle screen percentage of the views of the family. */

    /** Renderer private interfaces, automatically have same lifetime as FSceneViewFamily. */ // 采样器接口
    UE::Renderer::Private::ITemporalUpscaler* TemporalUpscalerInterface;
    ISpatialUpscaler* PrimarySpatialUpscalerInterface;
    ISpatialUpscaler* SecondarySpatialUpscalerInterface;

    bool bAllowTranslucencyAfterDOF;
    bool bAllowStandardTranslucencySeparated;
    bool bIsInFocus = true;

过一下变量,大概这么个意思,后面根据这些参数构造成SceneView

CalcSceneView

View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);

注意这里先是 FLevelEditorViewportClient::CalcSceneView

FSceneView* FLevelEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    bWasControlledByOtherViewport = false; // 是否被其他viewport控制

    // set all other matching viewports to my location, if the LOD locking is enabled,
    // unless another viewport already set me this frame (otherwise they fight)
    // 是否锁定LOD
    if (GEditor->bEnableLODLocking)
    {
        for (FLevelEditorViewportClient* ViewportClient : GEditor->GetLevelViewportClients()) // 拿到关卡编辑器视口客户端
        {
            //only change camera for a viewport that is looking at the same scene
            if (ViewportClient == NULL || GetScene() != ViewportClient->GetScene())
            {
                continue;
            }

            // go over all other level viewports
            if (ViewportClient->Viewport && ViewportClient != this)
            {
                // force camera of same-typed viewports
                if (ViewportClient->GetViewportType() == GetViewportType()) // 关卡编辑器,上面一排有一个可以显示出正顶侧三视图,主视口位置动了,同步更新其他的视口。这里的set属性的意思
                {
                    ViewportClient->SetViewLocation( GetViewLocation() );
                    ViewportClient->SetViewRotation( GetViewRotation() );
                    ViewportClient->SetOrthoZoom( GetOrthoZoom() );

                    // don't let this other viewport update itself in its own CalcSceneView
                    ViewportClient->bWasControlledByOtherViewport = true;
                }
                // when we are LOD locking, ortho views get their camera position from this view, so make sure it redraws
                else if (IsPerspective() && !ViewportClient->IsPerspective()) // 当前是透视,其他不是
                {
                    // don't let this other viewport update itself in its own CalcSceneView
                    ViewportClient->bWasControlledByOtherViewport = true;
                }
            }

            // if the above code determined that this viewport has changed, delay the update unless
            // an update is already in the pipe
            if (ViewportClient->bWasControlledByOtherViewport && ViewportClient->TimeForForceRedraw == 0.0)
            {
                ViewportClient->TimeForForceRedraw = FPlatformTime::Seconds() + 0.9 + FMath::FRand() * 0.2; // 延迟更新视口
            }
        }
    }

    // FSceneView 是场景空间到2D屏幕的一个投影
    FSceneView* View = FEditorViewportClient::CalcSceneView(ViewFamily, StereoViewIndex);

    View->ViewActor = ActorLocks.GetLock().GetLockedActor(); // 摄像机看着的对象
    View->SpriteCategoryVisibility = SpriteCategoryVisibility; // bit array 控制 sprite 可见性
    View->bCameraCut = bEditorCameraCut; // 摄像机是否能在编辑器中被切换
    View->bHasSelectedComponents = GEditor->GetSelectedComponentCount() > 0; // 关卡编辑器选中物体,物体detail中选中了组件

    return View;

}

核心位于最后面
FSceneView* View = FEditorViewportClient::CalcSceneView(ViewFamily, StereoViewIndex);
其他部分
这个函数主要多了一些编辑器的特殊逻辑,比如视口可以点出三视图,多了一些相关的操作

这一大段都是为了构造 FSceneViewInitOptions 这个初始化选项,然后作为构造函数参数给 FSceneView,
new 完再做一些后续设置,最后返回

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    const bool bStereoRendering = StereoViewIndex != INDEX_NONE;

    FSceneViewInitOptions ViewInitOptions;

    // 。。。

    FSceneView* View = new FSceneView(ViewInitOptions);

    // 。。。
}

FSceneViewInitOptions

为了构造 FSceneView,我们需要 FSceneViewInitOptions 帮我们传递一些参数,内容大概是这一
挺复杂的

// Construction parameters for a FSceneView
struct FSceneViewInitOptions : public FSceneViewProjectionData

,我们先看父类

// Projection data for a FSceneView
struct FSceneViewProjectionData
{
    FVector ViewOrigin; // 视图的原点 /** The view origin. */
    FMatrix ViewRotationMatrix; // 世界空间 -》 视口空间 的旋转矩阵 /** Rotation matrix transforming from world space to view space. */
    FMatrix ProjectionMatrix; // 投影矩阵 /** UE projection matrix projects such that clip space Z=1 is the near plane, and Z=0 is the infinite far plane. */

protected:
    FIntRect ViewRect; //The unconstrained (no aspect ratio bars applied) view rectangle (also unscaled)
    FIntRect ConstrainedViewRect; // The constrained view rectangle (identical to UnconstrainedUnscaledViewRect if aspect ratio is not constrained)

public:
    void SetViewRectangle(const FIntRect& InViewRect); // set ViewRect = ConstrainedViewRect = InViewRect
    void SetConstrainedViewRectangle(const FIntRect& InViewRect); // set ConstrainedViewRect = InViewRect;
    bool IsValidViewRectangle() const; // ConstrainedViewRect.Min.X 和 ConstrainedViewRect.Min.Y >= 0 且 ConstrainedViewRect.Width() 和 ConstrainedViewRect.Height() > 0
    bool IsPerspectiveProjection() const; // return ProjectionMatrix.M[3][3] < 1.0f;

    FMatrix ComputeViewProjectionMatrix() const { return FTranslationMatrix(-ViewOrigin) * ViewRotationMatrix * ProjectionMatrix; } // VP
};

回到这个类

// Construction parameters for a FSceneView
struct FSceneViewInitOptions : public FSceneViewProjectionData
{
    const FSceneViewFamily* ViewFamily;
    FSceneViewStateInterface* SceneViewStateInterface;
    const AActor* ViewActor; // 视图所属的玩家actor
    int32 PlayerIndex; // 玩家索引
    FViewElementDrawer* ViewElementDrawer; // 提供场景交互接口

    // 视图的属性
    FLinearColor BackgroundColor;
    FLinearColor OverlayColor;
    FLinearColor ColorScale;

    // 立体渲染(VR)的属性
    EStereoscopicPass StereoPass; /** For stereoscopic rendering, whether or not this is a full pass(全部渲染), or a primary(主要渲染通道) / secondary pass(共用渲染通道) */
    int32 StereoViewIndex; /** For stereoscopic rendering, a unique index to identify the view across view families */
    float WorldToMetersScale; /** Conversion from world units (uu) to meters, so we can scale motion to the world appropriately */

    // 视口位置和旋转 /** The view origin and rotation without any stereo offsets applied to it */
    FVector ViewLocation;
    FRotator ViewRotation;

    // 隐藏 和 显示组件的ID集合
    TSet<FPrimitiveComponentId> HiddenPrimitives;
    TOptional<TSet<FPrimitiveComponentId>> ShowOnlyPrimitives; /** The primitives which are visible for this view. If the array is not empty, all other primitives will be hidden. */

    FIntPoint CursorPos; // 鼠标位置 -1,-1 if not setup
    float LODDistanceFactor; // LOD距离缩放
    float OverrideFarClippingPlaneDistance; //  /** If > 0, overrides the view's far clipping plane with a plane at the specified distance. */
    FVector OriginOffsetThisFrame; /** World origin offset value. Non-zero only for a single frame when origin is rebased */
    bool bInCameraCut; // /** Was there a camera cut this frame? */
    bool bUseFieldOfViewForLOD; /** Whether to use FOV when computing mesh LOD. */

    // FOV相关 /** Actual field of view and that desired by the camera originally */
    float FOV;
    float DesiredFOV;

    bool bUseFauxOrthoViewPos; /** In case of ortho, generate a fake view position that has a non-zero W component. The view position will be derived based on the view matrix. */
    bool bIsSceneCapture; /** Whether this view is being used to render a scene capture. */
    bool bIsSceneCaptureCube; /** Whether the scene capture is a cube map (bIsSceneCapture will also be set). */
    bool bSceneCaptureUsesRayTracing; /** Whether this view uses ray tracing, for views that are used to render a scene capture. */
    bool bIsReflectionCapture; /** Whether this view is being used to render a reflection capture. */
    bool bIsPlanarReflection; /** Whether this view is being used to render a planar reflection. */

#if WITH_EDITOR
    uint64 EditorViewBitflag; /** default to 0'th view index, which is a bitfield of 1 */
    FVector OverrideLODViewOrigin; /** this can be specified for ortho views so that it's min draw distance/LOD parenting etc, is controlled by a perspective viewport */
    bool bDisableGameScreenPercentage;  /** Whether game screen percentage should be disabled. */
#endif

    // ...
};

CalcSceneView

回来

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    const bool bStereoRendering = StereoViewIndex != INDEX_NONE;

    FSceneViewInitOptions ViewInitOptions;

    // 。。。

    FSceneView* View = new FSceneView(ViewInitOptions);

    // 。。。
}

熟悉了 FSceneViewInitOptions 后,我们可以一步一步看看这些变量是如何计算出来并设置的

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    const bool bStereoRendering = StereoViewIndex != INDEX_NONE;

    FSceneViewInitOptions ViewInitOptions;

    // FViewportCameraTransform view Location, Rotation, LookAt, StartLocation,可以看出来这是描述摄像机的
    FViewportCameraTransform& ViewTransform = GetViewTransform();
    // 类型 比如正交,透视
    const ELevelViewportType EffectiveViewportType = GetViewportType();

    // Apply view modifiers.
    FEditorViewportViewModifierParams ViewModifierParams;
    {
        // 。。。
    }

    // 。。。

}

FEditorViewportViewModifierParams

切出去看一下这个结构

/** Parameter struct for editor viewport view modifiers */
struct FEditorViewportViewModifierParams
{
    FMinimalViewInfo ViewInfo;

    // 后处理混合,输入一个后处理的设置,和混合权重
    void AddPostProcessBlend(const FPostProcessSettings& Settings, float Weight)
    {
        check(PostProcessSettings.Num() == PostProcessBlendWeights.Num());
        PostProcessSettings.Add(Settings);
        PostProcessBlendWeights.Add(Weight);
    }

private:
    TArray<FPostProcessSettings> PostProcessSettings;
    TArray<float> PostProcessBlendWeights;

    friend class FEditorViewportClient;
};

主要是后处理设置和权重,另外就是一个 FMinimalViewInfo ViewInfo; 结构

USTRUCT(BlueprintType)
struct FMinimalViewInfo
{
    GENERATED_USTRUCT_BODY()

    // 都是摄像机的一些属性,为了篇幅看起来不太多,清晰一点,我把注解去掉了
    FVector Location;
    FRotator Rotation;
    float FOV;
    float DesiredFOV;

    // 正交模式 宽度,近景,远景层面的距离
    float OrthoWidth; /** The desired width (in world units) of the orthographic view (ignored in Perspective mode) */
    float OrthoNearClipPlane; /** The near plane distance of the orthographic view (in world units) */
    float OrthoFarClipPlane; /** The far plane distance of the orthographic view (in world units) */

    // 透视模式 的近景和横纵比
    float PerspectiveNearClipPlane; /** near plane distance of the perspective view (in world units). Set to a negative value to use the default global value of GNearClippingPlane */
    float AspectRatio; // Aspect Ratio (Width/Height)

    // Aspect ratio axis constraint override
    TOptional<EAspectRatioAxisConstraint> AspectRatioAxisConstraint;

    uint32 bConstrainAspectRatio:1; // If bConstrainAspectRatio is true, black bars will be added if the destination view has a different aspect ratio than this camera requested.
    uint32 bUseFieldOfViewForLOD:1; // If true, account for the field of view angle when computing which level of detail to use for meshes.

    TEnumAsByte<ECameraProjectionMode::Type> ProjectionMode; // The type of camera,是透视还是正交

    // 后处理参数
    float PostProcessBlendWeight; /** Indicates if PostProcessSettings should be applied. */
    struct FPostProcessSettings PostProcessSettings; /** Post-process settings to use if PostProcessBlendWeight is non-zero. */

    // 
    FVector2D OffCenterProjectionOffset; /** Off-axis / off-center projection offset as proportion of screen dimensions */
    TOptional<FTransform> PreviousViewTransform; /** Optional transform to be considered as this view's previous transform */

    // ...
};

CalcSceneView

回来

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    // 。。。

    // Apply view modifiers.
    FEditorViewportViewModifierParams ViewModifierParams;
    {
        // Location Rotation FOV

        ViewModifierParams.ViewInfo.Location = ViewTransform.GetLocation();
        ViewModifierParams.ViewInfo.Rotation = ViewTransform.GetRotation();

        if (bUseControllingActorViewInfo)
        {
            ViewModifierParams.ViewInfo.FOV = ControllingActorViewInfo.FOV;
        }
        else
        {
            ViewModifierParams.ViewInfo.FOV = ViewFOV;
        }

        if (bShouldApplyViewModifiers)
        {
            ViewModifiers.Broadcast(ViewModifierParams);
        }
    }

    // 临时保存一下变量
    const FVector ModifiedViewLocation = ViewModifierParams.ViewInfo.Location;
    FRotator ModifiedViewRotation = ViewModifierParams.ViewInfo.Rotation;
    const float ModifiedViewFOV = ViewModifierParams.ViewInfo.FOV;
    if (bUseControllingActorViewInfo)
    {
        ControllingActorViewInfo.Location = ViewModifierParams.ViewInfo.Location;
        ControllingActorViewInfo.Rotation = ViewModifierParams.ViewInfo.Rotation;
        ControllingActorViewInfo.FOV = ViewModifierParams.ViewInfo.FOV;
    }

    // 这一段目的都是为了这个赋值
    ViewInitOptions.ViewOrigin = ModifiedViewLocation;

    // 。。。

VR相关

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    // 。。。

    // Apply head tracking!  Note that this won't affect what the editor *thinks* the view location and rotation is, it will
    // only affect the rendering of the scene.
    if( bStereoRendering && GEngine->XRSystem.IsValid() && GEngine->XRSystem->IsHeadTrackingAllowed() )
    {
        FQuat CurrentHmdOrientation;
        FVector CurrentHmdPosition;
        GEngine->XRSystem->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, CurrentHmdOrientation, CurrentHmdPosition );

        const FQuat VisualRotation = ModifiedViewRotation.Quaternion() * CurrentHmdOrientation; // 四元数乘法 等于角度相加
        ModifiedViewRotation = VisualRotation.Rotator();
        ModifiedViewRotation.Normalize();
    }

SetViewRectangle 设置视口矩形,并限制viewport 大小,并且处理调试命令 CVarEditorViewportTest 的影响

    FIntPoint ViewportSize = Viewport->GetSizeXY();

    // 限制范围
    ViewportSize.X = FMath::Max(ViewportSize.X, 1);
    ViewportSize.Y = FMath::Max(ViewportSize.Y, 1);
    FIntPoint ViewportOffset(0, 0);

    // We expect some size to avoid problems with the view rect manipulation
    if (ViewportSize.X > 50 && ViewportSize.Y > 50)
    {
        int32 Value = CVarEditorViewportTest.GetValueOnGameThread(); // 获取一个测试命令变量的值

        if (Value)
        {
            int InsetX = ViewportSize.X / 4;
            int InsetY = ViewportSize.Y / 4;

            // this allows to test various typical view port situations
            // 出于测试目的的数值修改
            switch (Value)
            {
            case 1: ViewportOffset.X += InsetX; ViewportOffset.Y += InsetY; ViewportSize.X -= InsetX * 2; ViewportSize.Y -= InsetY * 2; break;
            case 2: ViewportOffset.Y += InsetY; ViewportSize.Y -= InsetY * 2; break;
            case 3: ViewportOffset.X += InsetX; ViewportSize.X -= InsetX * 2; break;
            case 4: ViewportSize.X /= 2; ViewportSize.Y /= 2; break;
            case 5: ViewportSize.X /= 2; ViewportSize.Y /= 2; ViewportOffset.X += ViewportSize.X;   break;
            case 6: ViewportSize.X /= 2; ViewportSize.Y /= 2; ViewportOffset.Y += ViewportSize.Y; break;
            case 7: ViewportSize.X /= 2; ViewportSize.Y /= 2; ViewportOffset.X += ViewportSize.X; ViewportOffset.Y += ViewportSize.Y; break;
            }
        }
    }

    // 应用视口矩形
    ViewInitOptions.SetViewRectangle(FIntRect(ViewportOffset, ViewportOffset + ViewportSize));

处理宽高比例的约束

    // no matter how we are drawn (forced or otherwise), reset our time here
    TimeForForceRedraw = 0.0;

    // 宽高比的调整
    const bool bConstrainAspectRatio = bUseControllingActorViewInfo && ControllingActorViewInfo.bConstrainAspectRatio; // 调整宽度,高度是否要变换
    EAspectRatioAxisConstraint AspectRatioAxisConstraint = GetDefault<ULevelEditorViewportSettings>()->AspectRatioAxisConstraint; // 约束宽高比的方式
    if (bUseControllingActorViewInfo && ControllingActorAspectRatioAxisConstraint.IsSet())
    {
        AspectRatioAxisConstraint = ControllingActorAspectRatioAxisConstraint.GetValue();
    }

拿到world settings 设置 WorldToMetersScale

    AWorldSettings* WorldSettings = nullptr;
    if( GetScene() != nullptr && GetScene()->GetWorld() != nullptr )
    {
        WorldSettings = GetScene()->GetWorld()->GetWorldSettings();
    }
    if( WorldSettings != nullptr )
    {
        ViewInitOptions.WorldToMetersScale = WorldSettings->WorldToMeters;
    }

计算正交和投影矩阵

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    // 。。。

    if (bUseControllingActorViewInfo)
    {
        // 后面再看
    }
    else
    {
        //
        if (EffectiveViewportType == LVT_Perspective)
        {
            // If stereo rendering is enabled, update the size and offset appropriately for this pass
            // @todo vreditor: Also need to update certain other use cases of ViewFOV like culling, streaming, etc.  (needs accessor)

            // VR 先不管,显然我们这里进不来
            if( bStereoRendering )
            {
                // 。。。
            }

            // Calc view rotation matrix 计算view矩阵
            ViewInitOptions.ViewRotationMatrix = CalcViewRotationMatrix(ModifiedViewRotation);

            // 。。。

        }
        else
        {
            // 。。。
        }

        // 。。。
    }

}

所以我们逻辑首先看的是
ViewInitOptions.ViewRotationMatrix = CalcViewRotationMatrix(ModifiedViewRotation);

FMatrix FEditorViewportClient::CalcViewRotationMatrix(const FRotator& InViewRotation) const
{
    const FViewportCameraTransform& ViewTransform = GetViewTransform();

    if (bUsingOrbitCamera)
    {
        // @todo vreditor: Not stereo friendly yet
        // 带轨迹的相机, 这么算
        return FTranslationMatrix(ViewTransform.GetLocation()) * ViewTransform.ComputeOrbitMatrix();
    }
    else
    {
        // Create the view matrix
        return FInverseRotationMatrix(InViewRotation);
    }
}

看 FInverseRotationMatrix

template<typename T>
FORCEINLINE TInverseRotationMatrix<T>::TInverseRotationMatrix(const TRotator<T>& Rot)
    : TMatrix<T>(
        TMatrix<T>( // Yaw
            TPlane<T>(+FMath::Cos(Rot.Yaw * UE_PI / 180.f), -FMath::Sin(Rot.Yaw * UE_PI / 180.f), 0.0f, 0.0f),
            TPlane<T>(+FMath::Sin(Rot.Yaw * UE_PI / 180.f), +FMath::Cos(Rot.Yaw * UE_PI / 180.f), 0.0f, 0.0f),
            TPlane<T>(0.0f, 0.0f, 1.0f, 0.0f),
            TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f)) *
        TMatrix<T>( // Pitch
            TPlane<T>(+FMath::Cos(Rot.Pitch * UE_PI / 180.f), 0.0f, -FMath::Sin(Rot.Pitch * UE_PI / 180.f), 0.0f),
            TPlane<T>(0.0f, 1.0f, 0.0f, 0.0f),
            TPlane<T>(+FMath::Sin(Rot.Pitch * UE_PI / 180.f), 0.0f, +FMath::Cos(Rot.Pitch * UE_PI / 180.f), 0.0f),
            TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f)) *
        TMatrix<T>( // Roll
            TPlane<T>(1.0f, 0.0f, 0.0f, 0.0f),
            TPlane<T>(0.0f, +FMath::Cos(Rot.Roll * UE_PI / 180.f), +FMath::Sin(Rot.Roll * UE_PI / 180.f), 0.0f),
            TPlane<T>(0.0f, -FMath::Sin(Rot.Roll * UE_PI / 180.f), +FMath::Cos(Rot.Roll * UE_PI / 180.f), 0.0f),
            TPlane<T>(0.0f, 0.0f, 0.0f, 1.0f))
    )
{ }

假设绕X, Y, Z 三个轴旋转的角度为 Yaw, Row,Pitch。View矩阵为

回来

    if (bUseControllingActorViewInfo)
    {
        // 。。。
    }
    else
    {
        //
        if (EffectiveViewportType == LVT_Perspective)
        {
            // 。。。

            // Calc view rotation matrix
            ViewInitOptions.ViewRotationMatrix = CalcViewRotationMatrix(ModifiedViewRotation);

            // Rotate view 90 degrees 乘一个单位矩阵?
            ViewInitOptions.ViewRotationMatrix = ViewInitOptions.ViewRotationMatrix * FMatrix(
                FPlane(0, 0, 1, 0),
                FPlane(1, 0, 0, 0),
                FPlane(0, 1, 0, 0),
                FPlane(0, 0, 0, 1));

            if( bStereoRendering ) // VR 先不看
            {
                // 。。。
            }
            else
            {
                const float MinZ = GetNearClipPlane();
                const float MaxZ = MinZ;
                // Avoid zero ViewFOV's which cause divide by zero's in projection matrix
                const float MatrixFOV = FMath::Max(0.001f, ModifiedViewFOV) * (float)PI / 360.0f; // 弧度的一个角

                if (bConstrainAspectRatio) // 是否约束屏幕的宽高比
                {
                    if ((bool)ERHIZBuffer::IsInverted) // 近景是否大于远景,那么API就在使用反向的Z缓冲区
                    {
                        ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix( // 算投影矩阵
                            MatrixFOV,
                            MatrixFOV,
                            1.0f,
                            AspectRatio,
                            MinZ,
                            MaxZ
                            );
                    }
                    else
                    {
                        ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix( // 算投影矩阵
                            MatrixFOV,
                            MatrixFOV,
                            1.0f,
                            AspectRatio,
                            MinZ,
                            MaxZ
                            );
                    }
                }
                else
                {
                    float XAxisMultiplier;
                    float YAxisMultiplier;

                    // X 轴为1 还是 Y轴为1
                    if (((ViewportSize.X > ViewportSize.Y) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) || (AspectRatioAxisConstraint == AspectRatio_MaintainXFOV))
                    {
                        //if the viewport is wider than it is tall
                        XAxisMultiplier = 1.0f;
                        YAxisMultiplier = ViewportSize.X / (float)ViewportSize.Y;
                    }
                    else
                    {
                        //if the viewport is taller than it is wide
                        XAxisMultiplier = ViewportSize.Y / (float)ViewportSize.X;
                        YAxisMultiplier = 1.0f;
                    }

                    if ((bool)ERHIZBuffer::IsInverted) // 近景是否大于远景,那么API就在使用反向的Z缓冲区
                    {
                        ViewInitOptions.ProjectionMatrix = FReversedZPerspectiveMatrix( // 投影矩阵
                            MatrixFOV,
                            MatrixFOV,
                            XAxisMultiplier,
                            YAxisMultiplier,
                            MinZ,
                            MaxZ
                            );
                    }
                    else
                    {
                        ViewInitOptions.ProjectionMatrix = FPerspectiveMatrix( // 投影矩阵
                            MatrixFOV,
                            MatrixFOV,
                            XAxisMultiplier,
                            YAxisMultiplier,
                            MinZ,
                            MaxZ
                            );
                    }
                }
            }
        }
        else
        {
            // 。。。
        }

        if (bConstrainAspectRatio)
        {
            ViewInitOptions.SetConstrainedViewRectangle(Viewport->CalculateViewExtents(AspectRatio, ViewInitOptions.GetViewRect()));
        }
    }

ERHIZBuffer::IsInverted 反向Z是为啥呢?
是为了提升深度精度的利用率。传统的是近是0,远是1。反向就是近是1,远是0。什么原理不知道。┑( ̄Д  ̄)┍

这个点进去就是我们推的投影矩阵公式

template<typename T>
FORCEINLINE TReversedZPerspectiveMatrix<T>::TReversedZPerspectiveMatrix(T HalfFOVX, T HalfFOVY, T MultFOVX, T MultFOVY, T MinZ, T MaxZ)
    : TMatrix<T>(
        TPlane<T>(MultFOVX / FMath::Tan(HalfFOVX),    0.0f,                               0.0f,                                                   0.0f),
        TPlane<T>(0.0f,                               MultFOVY / FMath::Tan(HalfFOVY),    0.0f,                                                   0.0f),
        TPlane<T>(0.0f,                               0.0f,                               ((MinZ == MaxZ) ? 0.0f : MinZ / (MinZ - MaxZ)),         1.0f),
        TPlane<T>(0.0f,                               0.0f,                               ((MinZ == MaxZ) ? MinZ : -MaxZ * MinZ / (MinZ - MaxZ)), 0.0f)
    )
{ }

另外else里面是正交情况下的计算

    if (bUseControllingActorViewInfo)
    {
        // 。。。
    }
    else
    {
        //
        if (EffectiveViewportType == LVT_Perspective)
        {
            // 。。。
        }
        else
        {
            // 正交

            static_assert((bool)ERHIZBuffer::IsInverted, "Check all the Rotation Matrix transformations!");
            FMatrix::FReal ZScale = 0.5f / UE_OLD_HALF_WORLD_MAX;   // LWC_TODO: WORLD_MAX misuse?
            FMatrix::FReal ZOffset = UE_OLD_HALF_WORLD_MAX;

            //The divisor for the matrix needs to match the translation code.
            // 为了屏幕尺寸发生变化的时候,它也发生变化
            const float Zoom = GetOrthoUnitsPerPixel(Viewport);

            float OrthoWidth = Zoom * ViewportSize.X / 2.0f;
            float OrthoHeight = Zoom * ViewportSize.Y / 2.0f;

            if (EffectiveViewportType == LVT_OrthoXY)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(1, 0, 0, 0),
                    FPlane(0, -1, 0, 0),
                    FPlane(0, 0, -1, 0),
                    FPlane(0, 0, 0, 1));
            }
            else if (EffectiveViewportType == LVT_OrthoXZ)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(1, 0, 0, 0),
                    FPlane(0, 0, -1, 0),
                    FPlane(0, 1, 0, 0),
                    FPlane(0, 0, 0, 1));
            }
            else if (EffectiveViewportType == LVT_OrthoYZ)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(0, 0, 1, 0),
                    FPlane(1, 0, 0, 0),
                    FPlane(0, 1, 0, 0),
                    FPlane(0, 0, 0, 1));
            }
            else if (EffectiveViewportType == LVT_OrthoNegativeXY)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(-1, 0, 0, 0),
                    FPlane(0, -1, 0, 0),
                    FPlane(0, 0, 1, 0),
                    FPlane(0, 0, 0, 1));
            }
            else if (EffectiveViewportType == LVT_OrthoNegativeXZ)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(-1, 0, 0, 0),
                    FPlane(0, 0, 1, 0),
                    FPlane(0, 1, 0, 0),
                    FPlane(0, 0, 0, 1));
            }
            else if (EffectiveViewportType == LVT_OrthoNegativeYZ)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(0, 0, -1, 0),
                    FPlane(-1, 0, 0, 0),
                    FPlane(0, 1, 0, 0),
                    FPlane(0, 0, 0, 1));
            }
            else if (EffectiveViewportType == LVT_OrthoFreelook)
            {
                ViewInitOptions.ViewRotationMatrix = FMatrix(
                    FPlane(0, 0, 1, 0),
                    FPlane(1, 0, 0, 0),
                    FPlane(0, 1, 0, 0),
                    FPlane(0, 0, 0, 1));
            }
            else
            {
                // Unknown viewport type
                check(false);
            }

            ViewInitOptions.ProjectionMatrix = FReversedZOrthoMatrix(
                OrthoWidth,
                OrthoHeight,
                ZScale,
                ZOffset
                );
        }

        if (bConstrainAspectRatio)
        {
            ViewInitOptions.SetConstrainedViewRectangle(Viewport->CalculateViewExtents(AspectRatio, ViewInitOptions.GetViewRect()));
        }
    }

最开始,还漏一个

    if (bUseControllingActorViewInfo)
    {
        // @todo vreditor: Not stereo friendly yet
        ViewInitOptions.ViewRotationMatrix = FInverseRotationMatrix(ModifiedViewRotation) * FMatrix(
            FPlane(0, 0, 1, 0),
            FPlane(1, 0, 0, 0),
            FPlane(0, 1, 0, 0),
            FPlane(0, 0, 0, 1));

        FMinimalViewInfo::CalculateProjectionMatrixGivenView(ControllingActorViewInfo, AspectRatioAxisConstraint, Viewport, /*inout*/ ViewInitOptions);
    }

使用控制角色的view info

void FMinimalViewInfo::CalculateProjectionMatrixGivenView(const FMinimalViewInfo& ViewInfo, TEnumAsByte<enum EAspectRatioAxisConstraint> AspectRatioAxisConstraint, FViewport* Viewport, FSceneViewProjectionData& InOutProjectionData)
{
    FIntRect ViewExtents = Viewport->CalculateViewExtents(ViewInfo.AspectRatio, InOutProjectionData.GetViewRect());
    CalculateProjectionMatrixGivenViewRectangle(ViewInfo, AspectRatioAxisConstraint, ViewExtents, InOutProjectionData);
}
void FMinimalViewInfo::CalculateProjectionMatrixGivenViewRectangle(const FMinimalViewInfo& ViewInfo, TEnumAsByte<enum EAspectRatioAxisConstraint> AspectRatioAxisConstraint, const FIntRect& ConstrainedViewRectangle, FSceneViewProjectionData& InOutProjectionData)
{
    // 。。。
}

CalcSceneView

回到这继续

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    const bool bStereoRendering = StereoViewIndex != INDEX_NONE;

    FSceneViewInitOptions ViewInitOptions;

    // 。。。

    // 保护性设置,避免非法值
    if (!ViewInitOptions.IsValidViewRectangle())
    {
        // Zero sized rects are invalid, so fake to 1x1 to avoid asserts later on
        ViewInitOptions.SetViewRectangle(FIntRect(0, 0, 1, 1));
    }

    // 。。。
}

VR相关可以先掠过

    // Allocate our stereo view state on demand, so that only viewports that actually use stereo features have one
    const int32 ViewStateIndex = (StereoViewIndex != INDEX_NONE) ? StereoViewIndex : 0;
    if (bStereoRendering)
    {
        if (StereoViewStates.Num() <= ViewStateIndex)
        {
            StereoViewStates.SetNum(ViewStateIndex + 1);
        }

        if (StereoViewStates[ViewStateIndex].GetReference() == nullptr)
        {
            FSceneInterface* Scene = GetScene();
            StereoViewStates[ViewStateIndex].Allocate(Scene ? Scene->GetFeatureLevel() : GMaxRHIFeatureLevel);
        }
    }

一些变量的赋值

    ViewInitOptions.ViewFamily = ViewFamily;
    ViewInitOptions.SceneViewStateInterface = ( (ViewStateIndex == 0) ? ViewState.GetReference() : StereoViewStates[ViewStateIndex].GetReference() );
    ViewInitOptions.StereoViewIndex = StereoViewIndex;

    ViewInitOptions.ViewElementDrawer = this;

    ViewInitOptions.BackgroundColor = GetBackgroundColor();

    ViewInitOptions.EditorViewBitflag = (uint64)1 << ViewIndex, // send the bit for this view - each actor will check it's visibility bits against this

    // for ortho views to steal perspective view origin
    ViewInitOptions.OverrideLODViewOrigin = FVector::ZeroVector;

    ViewInitOptions.FOV = ModifiedViewFOV;
    if (bUseControllingActorViewInfo)
    {
        ViewInitOptions.bUseFieldOfViewForLOD = ControllingActorViewInfo.bUseFieldOfViewForLOD;
        ViewInitOptions.FOV = ControllingActorViewInfo.FOV;
    }

    ViewInitOptions.OverrideFarClippingPlaneDistance = FarPlane;
    ViewInitOptions.CursorPos = CurrentMousePos;

获取 r.Test.ConstrainedView 这个值
最后 ViewInitOptions.SetConstrainedViewRectangle(ConstrainedViewRect); 修改 FIntRect ConstrainedViewRect; 这个值
干嘛用的在PIE运行敲一下,看下效果就知道了

r.Test.ConstrainedView 是一个控制台变量,用于测试不同的视口矩形配置。这在使用电影/编辑器时可能会发生。它的值可以是以下之一:  
0:关闭(默认)
1:将视口的上下部分约束到一定的范围内
2:将视口的左右部分约束到一定的范围内
其他值:对视口进行不同的约束配置
这个变量主要用于开发和测试阶段,以确保在各种视口配置下,游戏的渲染和表现都是正确的。
    static const auto CVarVSync = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Test.ConstrainedView"));
    int32 Value = CVarVSync->GetValueOnGameThread();

    if (Value)
    {
        // 。。。

        ViewInitOptions.SetConstrainedViewRectangle(ConstrainedViewRect);
    }

    FSceneView* View = new FSceneView(ViewInitOptions);

new FSceneView

然后 FSceneView 终于出现了
然后后续做一些设置

    FSceneView* View = new FSceneView(ViewInitOptions);

    // ViewLocation 和 ViewRotation
    View->ViewLocation = ModifiedViewLocation;
    View->ViewRotation = ModifiedViewRotation;

    // Color初始化
    View->SubduedSelectionOutlineColor = GEngine->GetSubduedSelectionOutlineColor();
    const UEditorStyleSettings* EditorStyle = GetDefault<UEditorStyleSettings>();
    check(View->AdditionalSelectionOutlineColors.Num() <= UE_ARRAY_COUNT(EditorStyle->AdditionalSelectionColors))
    for (int OutlineColorIndex = 0; OutlineColorIndex < View->AdditionalSelectionOutlineColors.Num(); ++OutlineColorIndex)
    {
        View->AdditionalSelectionOutlineColors[OutlineColorIndex] = EditorStyle->AdditionalSelectionColors[OutlineColorIndex];
    }

    // 加进视图里面
    int32 FamilyIndex = ViewFamily->Views.Add(View);
    check(FamilyIndex == View->StereoViewIndex || View->StereoViewIndex == INDEX_NONE);

    // 后处理相关
    View->StartFinalPostprocessSettings( View->ViewLocation );

    if (bUseControllingActorViewInfo)
    {
        // Pass on the previous view transform of the controlling actor to the view
        View->PreviousViewTransform = ControllingActorViewInfo.PreviousViewTransform;
        View->OverridePostProcessSettings(ControllingActorViewInfo.PostProcessSettings, ControllingActorViewInfo.PostProcessBlendWeight);

        for (int32 ExtraPPBlendIdx = 0; ExtraPPBlendIdx < ControllingActorExtraPostProcessBlends.Num(); ++ExtraPPBlendIdx)
        {
            FPostProcessSettings const& PPSettings = ControllingActorExtraPostProcessBlends[ExtraPPBlendIdx];
            float const Weight = ControllingActorExtraPostProcessBlendWeights[ExtraPPBlendIdx];
            View->OverridePostProcessSettings(PPSettings, Weight);
        }
    }
    else
    {
        OverridePostProcessSettings(*View);
    }

    if (ViewModifierParams.ViewInfo.PostProcessBlendWeight > 0.f)
    {
        View->OverridePostProcessSettings(ViewModifierParams.ViewInfo.PostProcessSettings, ViewModifierParams.ViewInfo.PostProcessBlendWeight);
    }
    const int32 PPNum = FMath::Min(ViewModifierParams.PostProcessSettings.Num(), ViewModifierParams.PostProcessBlendWeights.Num());
    for (int32 PPIndex = 0; PPIndex < PPNum; ++PPIndex)
    {
        const FPostProcessSettings& PPSettings = ViewModifierParams.PostProcessSettings[PPIndex];
        const float PPWeight = ViewModifierParams.PostProcessBlendWeights[PPIndex];
        View->OverridePostProcessSettings(PPSettings, PPWeight);
    }

    View->EndFinalPostprocessSettings(ViewInitOptions);

    // 允许自定义一些扩展
    for (int ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ViewExt++)
    {
        ViewFamily->ViewExtensions[ViewExt]->SetupView(*ViewFamily, *View);
    }

    return View;

FEditorViewportClient::Draw

回到这里接着
View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE); 继续看

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    // 。。。

    // Setup a FSceneViewFamily/FSceneView for the viewport.
    FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
        Canvas->GetRenderTarget(),
        GetScene(),
        UseEngineShowFlags)
        .SetTime(Time)
        .SetRealtimeUpdate( IsRealtime() && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
        .SetViewModeParam( ViewModeParam, ViewModeParamName ) );

    // 。。。

    // 省略了一堆 ViewFamily 属性的设置

    // 。。。

    FSceneView* View = nullptr;

    // Stereo rendering
    const bool bStereoDeviceActive = bStereoRendering && GEngine->StereoRenderingDevice.IsValid();
    int32 NumViews = bStereoRendering ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering) : 1;
    for( int StereoViewIndex = 0; StereoViewIndex < NumViews; ++StereoViewIndex )
    {
        View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);

        SetupViewForRendering(ViewFamily,*View); // 一些灯光参数,标志位的设置。另外还有像素调试器,拖拽,音频设置

        FSlateRect SafeFrame; // 视口的约束矩形
        View->CameraConstrainedViewRect = View->UnscaledViewRect;

        // 根据视口大小和DPI缩放算出一个矩形,保证窗口不会超过编辑器
        if (CalculateEditorConstrainedViewRect(SafeFrame, Viewport, Canvas->GetDPIScale()))
        {
            View->CameraConstrainedViewRect = FIntRect(SafeFrame.Left, SafeFrame.Top, SafeFrame.Right, SafeFrame.Bottom);
        }

        if (World)
        {       
            // 更新上一帧的view info
            FWorldCachedViewInfo& WorldViewInfo = World->CachedViewInfoRenderedLastFrame.AddDefaulted_GetRef();
            WorldViewInfo.ViewMatrix = View->ViewMatrices.GetViewMatrix();
            WorldViewInfo.ProjectionMatrix = View->ViewMatrices.GetProjectionMatrix();
            WorldViewInfo.ViewProjectionMatrix = View->ViewMatrices.GetViewProjectionMatrix();
            WorldViewInfo.ViewToWorld = View->ViewMatrices.GetInvViewMatrix();
            World->LastRenderTime = World->GetTimeSeconds();
        }
    }

    // 。。。

}

根据预览分辨率比例和全局分辨率比例设置视口的屏幕百分比接口,并确保接口已设置。它还处理了在编辑器视口中忽略特定的屏幕百分比设置

    // 设置 Screen 百分比 接口
    {
        // If a screen percentage interface was not set by one of the view extension, then set the legacy one.
        // 设置 Screen 百分比 接口
        if (ViewFamily.GetScreenPercentageInterface() == nullptr)
        {
            float GlobalResolutionFraction = 1.0f;

            // Apply preview resolution fraction. Supported in stereo for VR Editor Mode only
            if ((!bStereoRendering || bInVREditViewMode) && 
                SupportsPreviewResolutionFraction() && ViewFamily.SupportsScreenPercentage())
            {
                if (PreviewResolutionFraction.IsSet() && bIsPreviewingResolutionFraction)
                {
                    GlobalResolutionFraction = PreviewResolutionFraction.GetValue();
                }
                else
                {
                    GlobalResolutionFraction = GetDefaultPrimaryResolutionFractionTarget();
                }

                // Force screen percentage's engine show flag to be turned on for preview screen percentage.
                ViewFamily.EngineShowFlags.ScreenPercentage = (GlobalResolutionFraction != 1.0);
            }

            // In editor viewport, we ignore r.ScreenPercentage and FPostProcessSettings::ScreenPercentage by design.
            ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(
                ViewFamily, GlobalResolutionFraction));
        }

        check(ViewFamily.GetScreenPercentageInterface() != nullptr);
    }

    // 约束了宽高比,会把画布先化成黑色的
    if (IsAspectRatioConstrained())
    {
        // Clear the background to black if the aspect ratio is constrained, as the scene view won't write to all pixels.
        Canvas->Clear(FLinearColor::Black);
    }

BeginRenderingViewFamily

回顾一下

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    // 。。。

    // Setup a FSceneViewFamily/FSceneView for the viewport.
    FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(
        Canvas->GetRenderTarget(),
        GetScene(),
        UseEngineShowFlags)
        .SetTime(Time)
        .SetRealtimeUpdate( IsRealtime() && FSlateThrottleManager::Get().IsAllowingExpensiveTasks() )
        .SetViewModeParam( ViewModeParam, ViewModeParamName ) );

    // 。。。

    // 省略了一堆 ViewFamily 属性的设置

    // 。。。

    FSceneView* View = nullptr;

    // Stereo rendering
    const bool bStereoDeviceActive = bStereoRendering && GEngine->StereoRenderingDevice.IsValid();
    int32 NumViews = bStereoRendering ? GEngine->StereoRenderingDevice->GetDesiredNumberOfViews(bStereoRendering) : 1;
    for( int StereoViewIndex = 0; StereoViewIndex < NumViews; ++StereoViewIndex )
    {
        View = CalcSceneView( &ViewFamily, bStereoRendering ? StereoViewIndex : INDEX_NONE);

        // 。。。
    }

    // 。。。
    // 
    GetRendererModule().BeginRenderingViewFamily(Canvas,&ViewFamily);

我们构造
FSceneViewFamilyContext ViewFamily;
然后创建了 FSceneView* View;
并且在创建后做了一些设置。
我们要开始画3D场景了

    // Draw the 3D scene
    GetRendererModule().BeginRenderingViewFamily(Canvas,&ViewFamily);
void FRendererModule::BeginRenderingViewFamily(FCanvas* Canvas, FSceneViewFamily* ViewFamily)
{
    BeginRenderingViewFamilies(Canvas, TArrayView<FSceneViewFamily*>(&ViewFamily, 1));
}
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(BeginRenderingViewFamily);
    check(Canvas);

    // 验证,多个view families的scene必须一样。一个view families可以理解为一个摄像机,一个families里面可以有多个各种的视图
    for (FSceneViewFamily* ViewFamily : ViewFamilies)
    {
        check(ViewFamily);
        check(ViewFamily->Scene == ViewFamilies[0]->Scene);
    }

    FScene* const Scene = ViewFamilies[0]->Scene ? ViewFamilies[0]->Scene->GetRenderScene() : nullptr;
    if (Scene)
    {
        World = Scene->GetWorld();
        if (World)
        {
            // Guarantee that all render proxies are up to date before kicking off a BeginRenderViewFamily.
            World->SendAllEndOfFrameUpdates(); // 确保渲染代理都是最新的

            GetNaniteVisualizationData().Pick(World);
        }
    }

SendAllEndOfFrameUpdates

SendAllEndOfFrameUpdates 在渲染开始之前,确保渲染代理都是最新的

/**
    * Send all render updates to the rendering thread.
    */
void UWorld::SendAllEndOfFrameUpdates()
{
    SCOPED_NAMED_EVENT(UWorld_SendAllEndOfFrameUpdates, FColor::Yellow);
    SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate);
    CSV_SCOPED_TIMING_STAT_EXCLUSIVE(EndOfFrameUpdates);
    CSV_SCOPED_SET_WAIT_STAT(EndOfFrameUpdates);

    // Allow systems to complete async work that could introduce additional components to end of frame updates
    FWorldDelegates::OnWorldPreSendAllEndOfFrameUpdates.Broadcast(this);

    if (!HasEndOfFrameUpdates()) // 确保延迟组件更新到了渲染线程
    {
        return;
    }

    // 。。。

}
bool UWorld::HasEndOfFrameUpdates() const
{
    return ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num() > 0 || ComponentsThatNeedEndOfFrameUpdate.Num() > 0 || bMaterialParameterCollectionInstanceNeedsDeferredUpdate;
}

其实在tick里面,收集所有world并执行 SendAllEndOfFrameUpdates
已经执行过了,这里面不一定有东西要执行

    /////////////////////////////
    // Redraw viewports.
    {
        // Gather worlds that need EOF updates
        // This must be done in two steps as the object hash table is locked during ForEachObjectOfClass so any NewObject calls would fail
        TArray<UWorld*, TInlineAllocator<4>> WorldsToEOFUpdate;
        ForEachObjectOfClass(UWorld::StaticClass(), [&WorldsToEOFUpdate](UObject* WorldObj)
        {
            UWorld* World = CastChecked<UWorld>(WorldObj);
            if (World->HasEndOfFrameUpdates())
            {
                WorldsToEOFUpdate.Add(World);
            }
        });

        // Make sure deferred component updates have been sent to the rendering thread.
        for (UWorld* World : WorldsToEOFUpdate)
        {
            World->SendAllEndOfFrameUpdates();
        }
    }

    // UpdateSingleViewportClient
/**
    * Send all render updates to the rendering thread.
    */
void UWorld::SendAllEndOfFrameUpdates()
{

    // 。。。

    // 遍历所有需要末尾帧同步的组件列表
    // Wait for tasks that are generating data for the render proxies, but are not awaited in any TickFunctions 
    // E.g., see cloth USkeletalMeshComponent::UpdateClothStateAndSimulate
    for (UActorComponent* Component : ComponentsThatNeedPreEndOfFrameSync)
    {
        if (Component)
        {
            check(!IsValid(Component) || Component->GetMarkedForPreEndOfFrameSync());
            if (IsValid(Component))
            {
                Component->OnPreEndOfFrameSync(); // 执行
            }
            FMarkComponentEndOfFrameUpdateState::ClearMarkedForPreEndOfFrameSync(Component); // 清除
        }
    }
    ComponentsThatNeedPreEndOfFrameSync.Reset(); // reset

    //If we call SendAllEndOfFrameUpdates during a tick, we must ensure that all marked objects have completed any async work etc before doing the updates.
    // 如果这个函数在tick中被调用,那么就对所有组件 需要的 OnEndOfFrameUpdateDuringTick
    if (bInTick)
    {
        SCOPE_CYCLE_COUNTER(STAT_OnEndOfFrameUpdateDuringTick);

        //If this proves too slow we can possibly have the mark set a complimentary bit array that marks which components need this call? Or just another component array?
        for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate)
        {
            if (Component)
            {
                Component->OnEndOfFrameUpdateDuringTick();
            }
        }
        for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
        {
            if (Component)
            {
                Component->OnEndOfFrameUpdateDuringTick();
            }
        }
    }

    // Issue a GPU event to wrap GPU work done during SendAllEndOfFrameUpdates, like skin cache updates
    // 皮肤缓存的更新,比如蒙皮
    FSendAllEndOfFrameUpdates SendAllEndOfFrameUpdates(Scene);
    BeginSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);

    // update all dirty components. 更新所有标记为脏的组件
    FGuardValue_Bitfield(bPostTickComponentUpdate, true);

    // 收集合并 LocalComponentsThatNeedEndOfFrameUpdate
    static TArray<UActorComponent*> LocalComponentsThatNeedEndOfFrameUpdate; 
    {
        QUICK_SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate_Gather);
        check(IsInGameThread() && !LocalComponentsThatNeedEndOfFrameUpdate.Num());
        LocalComponentsThatNeedEndOfFrameUpdate.Append(ComponentsThatNeedEndOfFrameUpdate);
    }

    // 是否有并行的通知事件
    // 控制台变量 允许异步渲染线程更新 在游戏线程更新的时候 && 上面收集的组件数量 > 工作线程数量 && 工作线程数量 > 2
    const bool IsUsingParallelNotifyEvents = CVarAllowAsyncRenderThreadUpdatesDuringGamethreadUpdates.GetValueOnGameThread() > 0 && 
    LocalComponentsThatNeedEndOfFrameUpdate.Num() > FTaskGraphInterface::Get().GetNumWorkerThreads() &&
    FTaskGraphInterface::Get().GetNumWorkerThreads() > 2;

跳过几个lambda,用到了再看

    if (IsUsingParallelNotifyEvents)
    {
#if WITH_EDITOR
        FObjectCacheEventSink::BeginQueueNotifyEvents();
#endif
        ParallelForWithPreWork(LocalComponentsThatNeedEndOfFrameUpdate.Num(), ParallelWork, GTWork);
#if WITH_EDITOR
        // Any remaining events will be flushed with this call
        FObjectCacheEventSink::EndQueueNotifyEvents();
#endif
    }
    else
    {
        GTWork();
        ParallelFor(LocalComponentsThatNeedEndOfFrameUpdate.Num(), ParallelWork); // ParallelFor 用于在多线程中执行相同的任务
    }

先看 GTWork

    auto GTWork = [this]()
    {
        QUICK_SCOPE_CYCLE_COUNTER(STAT_PostTickComponentUpdate_ForcedGameThread);

        // To avoid any problems in case of reentrancy during the deferred update pass, we gather everything and clears the buffers first
        // Reentrancy can occur if a render update need to force wait on an async resource and a progress bar ticks the game-thread during that time.
        // 准备一个 这么大的 UActorComponent 数组
        TArray< UActorComponent*> DeferredUpdates;
        DeferredUpdates.Reserve(ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num());

        for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
        {
            if (Component)
            {
                if (Component->IsRegistered() && !Component->IsTemplate() && IsValid(Component))
                {
                    DeferredUpdates.Add(Component);
                }

                // 确保标记帧在结束线程
                check(!IsValid(Component) || Component->GetMarkedForEndOfFrameUpdateState() == EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread);

                // 已经添加进来了,就可以设置为未标记
                FMarkComponentEndOfFrameUpdateState::Set(Component, INDEX_NONE, EComponentMarkedForEndOfFrameUpdateState::Unmarked);
            }
        }

        ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Reset();
        ComponentsThatNeedEndOfFrameUpdate.Reset();

        // We are only regenerating render state here, not components
        FStaticMeshComponentBulkReregisterContext ReregisterContext(Scene, DeferredUpdates, EBulkReregister::RenderState);

        // 然后对刚刚的array执行 DoDeferredRenderUpdates_Concurrent
        for (UActorComponent* Component : DeferredUpdates)
        {
            Component->DoDeferredRenderUpdates_Concurrent();
        }
    };
void UActorComponent::DoDeferredRenderUpdates_Concurrent()
{
    LLM_SCOPE(ELLMTag::SceneRender);
    LLM_SCOPED_TAG_WITH_OBJECT_IN_SET(GetOutermost(), ELLMTagSet::Assets);

    checkf(!IsUnreachable(), TEXT("%s"), *GetFullName());
    checkf(!IsTemplate(), TEXT("%s"), *GetFullName());
    checkf(IsValid(this), TEXT("%s"), *GetFullName());

    FScopeCycleCounterUObject ContextScope(this);
    FScopeCycleCounterUObject AdditionalScope(STATS ? AdditionalStatObject() : nullptr); // 统计用的

    if(!IsRegistered())
    {
        UE_LOG(LogActorComponent, Log, TEXT("UpdateComponent: (%s) Not registered, Aborting."), *GetPathName());
        return;
    }

    if(bRenderStateDirty) // 组件是否已经把整个状态发送到渲染线程了
    {
        SCOPE_CYCLE_COUNTER(STAT_PostTickComponentRecreate);
        RecreateRenderState_Concurrent();
        checkf(!bRenderStateDirty, TEXT("Failed to route CreateRenderState_Concurrent (%s)"), *GetFullName());
    }
    else // 如果没有
    {
        SCOPE_CYCLE_COUNTER(STAT_PostTickComponentLW);

        // 是否xxx是dirty的,如果是就调用 SendXXX_Concurrent

        if(bRenderTransformDirty) 
        {
            // Update the component's transform if the actor has been moved since it was last updated.
            SendRenderTransform_Concurrent();
        }

        if(bRenderDynamicDataDirty)
        {
            SendRenderDynamicData_Concurrent();
        }

        if (bRenderInstancesDirty)
        {
            SendRenderInstanceData_Concurrent();
        }
    }
}

比如这里
SendRenderTransform_Concurrent
继续往下看,就能找到 CPU 的 Transform 数据是如何传递到 GPU的。

ParallelWork
...

SendRenderTransform_Concurrent

SendRenderTransform_Concurrent

这里是 UActorComponet 的 SendRenderTransform_Concurrent
但是打断点啥的就会发现其实是这个类
他的注释是这样的

/**
 * 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.
 */
UCLASS(abstract, HideCategories=(Mobility, VirtualTexture), ShowCategories=(PhysicsVolume), MinimalAPI)
class UPrimitiveComponent : public USceneComponent, public INavRelevantInterface, public IInterface_AsyncCompilation, public IPhysicsComponent
{
};

大概就是说这个类是用于渲染和碰撞的。

void UPrimitiveComponent::SendRenderTransform_Concurrent()
{
    UpdateBounds(); // 更新物体边界,包围盒,用于碰撞AABB之类的,从 UStaticMeshComponent::UpdateBounds() 开始

    // 。。。
}
void UStaticMeshComponent::UpdateBounds()
{
    NotifyIfStaticMeshChanged();

    Super::UpdateBounds();
}
void UPrimitiveComponent::SendRenderTransform_Concurrent()
{
    UpdateBounds();

    // If the primitive isn't hidden update its transform.
    const bool bDetailModeAllowsRendering   = DetailMode <= GetCachedScalabilityCVars().DetailMode; // 细节模式是否允许被渲染
    if( bDetailModeAllowsRendering && (ShouldRender() || bCastHiddenShadow || bAffectIndirectLightingWhileHidden || bRayTracingFarField))
    {
        // Update the scene info's transform for this primitive.
        GetWorld()->Scene->UpdatePrimitiveTransform(this);
    }

    Super::SendRenderTransform_Concurrent();
}


GetWorld()->Scene->UpdatePrimitiveTransform(this);

FScene::UpdatePrimitiveTransform

一些时间记录

void FScene::UpdatePrimitiveTransform(UPrimitiveComponent* Primitive)
{
    SCOPE_CYCLE_COUNTER(STAT_UpdatePrimitiveTransformGT);
    SCOPED_NAMED_EVENT(FScene_UpdatePrimitiveTransform, FColor::Yellow);

    // Save the world transform for next time the primitive is added to the scene
    const float WorldTime = GetWorld()->GetTimeSeconds();
    float DeltaTime = WorldTime - Primitive->LastSubmitTime;
    if (DeltaTime < -0.0001f || Primitive->LastSubmitTime < 0.0001f)
    {
        // Time was reset?
        Primitive->LastSubmitTime = WorldTime;
    }
    else if (DeltaTime > 0.0001f)
    {
        // First call for the new frame?
        Primitive->LastSubmitTime = WorldTime;
    }

    // 。。。

}
void FScene::UpdatePrimitiveTransform(UPrimitiveComponent* Primitive)
{
    // ...

    if (Primitive->SceneProxy) // 这个代理,相当于从 UPrimitiveComponent 里面拷贝了一些数据给渲染线程用
    {
    }
    else // 如果没有就要去加
    {
        // If the primitive doesn't have a scene info object yet, it must be added from scratch.
        AddPrimitive(Primitive);
    }

如果有

    if (Primitive->SceneProxy)
    {
        // Check if the primitive needs to recreate its proxy for the transform update.
        // 是否创建资源代理来更新transform
        if (Primitive->ShouldRecreateProxyOnUpdateTransform()) // return (Mobility != EComponentMobility::Movable);
        {
            // Re-add the primitive from scratch to recreate the primitive's proxy.
            RemovePrimitive(Primitive);
            AddPrimitive(Primitive);
        }
        else
        {
            // root position
            FVector AttachmentRootPosition = Primitive->GetActorPositionForRenderer();

            // 构造一个更新参数
            struct FPrimitiveUpdateParams
            {
                FScene* Scene;
                FPrimitiveSceneProxy* PrimitiveSceneProxy;
                FBoxSphereBounds WorldBounds;
                FBoxSphereBounds LocalBounds;
                FMatrix LocalToWorld;
                TOptional<FTransform> PreviousTransform;
                FVector AttachmentRootPosition;
            };

            FPrimitiveUpdateParams UpdateParams;
            UpdateParams.Scene = this;
            UpdateParams.PrimitiveSceneProxy = Primitive->SceneProxy;
            UpdateParams.WorldBounds = Primitive->Bounds;
            UpdateParams.LocalToWorld = Primitive->GetRenderMatrix(); // world matrix
            UpdateParams.AttachmentRootPosition = AttachmentRootPosition;
            UpdateParams.LocalBounds = Primitive->GetLocalBounds();
            UpdateParams.PreviousTransform = FMotionVectorSimulation::Get().GetPreviousTransform(Primitive);

            // Help track down primitive with bad bounds way before the it gets to the renderer.
            ensureMsgf(!UpdateParams.WorldBounds.BoxExtent.ContainsNaN() && !UpdateParams.WorldBounds.Origin.ContainsNaN() && !FMath::IsNaN(UpdateParams.WorldBounds.SphereRadius) && FMath::IsFinite(UpdateParams.WorldBounds.SphereRadius),
                TEXT("NaNs found on Bounds for Primitive %s: Owner: %s, Resource: %s, Level: %s, Origin: %s, BoxExtent: %s, SphereRadius: %f"),
                *Primitive->GetName(),
                *Primitive->SceneProxy->GetOwnerName().ToString(),
                *Primitive->SceneProxy->GetResourceName().ToString(),
                *Primitive->SceneProxy->GetLevelName().ToString(),
                *UpdateParams.WorldBounds.Origin.ToString(),
                *UpdateParams.WorldBounds.BoxExtent.ToString(),
                UpdateParams.WorldBounds.SphereRadius
            );

            bool bPerformUpdate = true;

            // Redundant 冗余更新,和dirty是类似的意思
            const bool bAllowSkip = GSkipRedundantTransformUpdate && Primitive->SceneProxy->CanSkipRedundantTransformUpdates();
            if (bAllowSkip || GWarningOnRedundantTransformUpdate)
            {
                // 设置为冗余的更新_任何线程,主要检查这些变量值是不是和本身发生了变化,如果没变当然不用更新了
                if (Primitive->SceneProxy->WouldSetTransformBeRedundant_AnyThread(
                    UpdateParams.LocalToWorld,
                    UpdateParams.WorldBounds,
                    UpdateParams.LocalBounds,
                    UpdateParams.AttachmentRootPosition))
                {
                    // 变量和本身比没有变化,而且跳过更新,那就 bPerformUpdate = false;
                    if (bAllowSkip)
                    {
                        // Do not perform the transform update!
                        bPerformUpdate = false;
                    }
                    else
                    {
                        // Not skipping, and warnings are enabled.
                        UE_LOG(LogRenderer, Warning,
                            TEXT("Redundant UpdatePrimitiveTransform for Primitive %s: Owner: %s, Resource: %s, Level: %s"),
                            *Primitive->GetName(),
                            *Primitive->SceneProxy->GetOwnerName().ToString(),
                            *Primitive->SceneProxy->GetResourceName().ToString(),
                            *Primitive->SceneProxy->GetLevelName().ToString()
                        );
                    }
                }
            }

            // 需要更新,让渲染线程去执行。带着参数过去
            if (bPerformUpdate)
            {
                ENQUEUE_RENDER_COMMAND(UpdateTransformCommand)(
                    [UpdateParams](FRHICommandListImmediate& RHICmdList)
                    {
                        FScopeCycleCounter Context(UpdateParams.PrimitiveSceneProxy->GetStatId());
                        UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread(
                            UpdateParams.PrimitiveSceneProxy,
                            UpdateParams.WorldBounds,
                            UpdateParams.LocalBounds,
                            UpdateParams.LocalToWorld,
                            UpdateParams.AttachmentRootPosition,
                            UpdateParams.PreviousTransform
                        );
                    }
                );
            }
        }
    }

上面一大堆,就是为了准备一个 UpdateParams
执行 UpdateParams.Scene->UpdatePrimitiveTransform_RenderThread
这个必须要在渲染线程里面执行

void FScene::UpdatePrimitiveTransform_RenderThread(FPrimitiveSceneProxy* PrimitiveSceneProxy, const FBoxSphereBounds& WorldBounds, const FBoxSphereBounds& LocalBounds, const FMatrix& LocalToWorld, const FVector& AttachmentRootPosition, const TOptional<FTransform>& PreviousTransform)
{
    check(IsInRenderingThread());

#if VALIDATE_PRIMITIVE_PACKED_INDEX
    if (AddedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) != nullptr)
    {
        check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex == INDEX_NONE);
    }
    else
    {
        check(PrimitiveSceneProxy->GetPrimitiveSceneInfo()->PackedIndex != INDEX_NONE);
    }

    check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneProxy->GetPrimitiveSceneInfo()) == nullptr);
#endif

    // 游戏线程带过来的参数,更新到渲染线程的 UpdatedTransforms 里面
    UpdatedTransforms.Update(PrimitiveSceneProxy, { WorldBounds, LocalBounds, LocalToWorld, AttachmentRootPosition });

    // 上一个transform是有意义的,要重叠一下
    if (PreviousTransform.IsSet())
    {
        OverridenPreviousTransforms.Update(PrimitiveSceneProxy->GetPrimitiveSceneInfo(), PreviousTransform.GetValue().ToMatrixWithScale());
    }
}

另外一个分支,允许用代理更新transform

    if (Primitive->ShouldRecreateProxyOnUpdateTransform()) // return (Mobility != EComponentMobility::Movable);
    {
        // Re-add the primitive from scratch to recreate the primitive's proxy.
        RemovePrimitive(Primitive);
        AddPrimitive(Primitive);
    }

会先remove 再 add

void FScene::RemovePrimitive(UPrimitiveComponent* Primitive)
{
    // If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
    if (Primitive->bBulkReregister)
    {
        return;
    }
    BatchRemovePrimitives(MakeArrayView(&Primitive, 1));
}

FScene::RemovePrimitive

void FScene::RemovePrimitive(UPrimitiveComponent* Primitive)
{
    // If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
    if (Primitive->bBulkReregister)
    {
        return;
    }
    BatchRemovePrimitives(MakeArrayView(&Primitive, 1));
}
void FScene::BatchRemovePrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
    SCOPE_CYCLE_COUNTER(STAT_RemoveScenePrimitiveGT);
    SCOPED_NAMED_EVENT(FScene_RemovePrimitive, FColor::Yellow);

    struct FPrimitiveRemoveInfo
    {
        FPrimitiveSceneInfo* PrimitiveSceneInfo;
        FThreadSafeCounter* AttachmentCounter;
    };

    // 维护 RemoveInfos
    TArray<FPrimitiveRemoveInfo, TInlineAllocator<1>> RemoveInfos;
    RemoveInfos.Reserve(InPrimitives.Num());

    for (UPrimitiveComponent* Primitive : InPrimitives)
    {
        FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->SceneProxy;

        if (PrimitiveSceneProxy)
        {
            FPrimitiveSceneInfo* PrimitiveSceneInfo = PrimitiveSceneProxy->GetPrimitiveSceneInfo();

            // Disassociate the primitive's scene proxy.
            Primitive->SceneProxy = NULL;

            RemoveInfos.Add({ PrimitiveSceneInfo, &Primitive->AttachmentCounter });
        }
    }

    // 让渲染线程处理 RemoveInfos
    if (RemoveInfos.Num())
    {
        // Send a command to the rendering thread to remove the primitives from the scene.
        FScene* Scene = this;
        ENQUEUE_RENDER_COMMAND(FRemovePrimitiveCommand)(
            [Scene, RemoveInfos = MoveTemp(RemoveInfos)](FRHICommandList&)
            {
                for (const FPrimitiveRemoveInfo& RemoveInfo : RemoveInfos)
                {
                    RemoveInfo.PrimitiveSceneInfo->Proxy->DestroyRenderThreadResources();
                    Scene->RemovePrimitiveSceneInfo_RenderThread(RemoveInfo.PrimitiveSceneInfo);
                    RemoveInfo.AttachmentCounter->Decrement();
                }
            });
    }
}

RemovePrimitiveSceneInfo_RenderThread

void FScene::RemovePrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo)
{
    check(IsInRenderingThread()); // 这里是渲染线程

    if (AddedPrimitiveSceneInfos.Remove(PrimitiveSceneInfo)) 
    {
        // 渲染线程移除xxx
        check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
        UpdatedTransforms.Remove(PrimitiveSceneInfo->Proxy);
        UpdatedCustomPrimitiveParams.Remove(PrimitiveSceneInfo->Proxy);
        OverridenPreviousTransforms.Remove(PrimitiveSceneInfo);
        UpdatedOcclusionBoundsSlacks.Remove(PrimitiveSceneInfo->Proxy);
        DistanceFieldSceneDataUpdates.Remove(PrimitiveSceneInfo);
        UpdatedAttachmentRoots.Remove(PrimitiveSceneInfo);

        {
            SCOPED_NAMED_EVENT(FScene_DeletePrimitiveSceneInfo, FColor::Red);
            // Delete the PrimitiveSceneInfo on the game thread after the rendering thread has processed its removal.
            // This must be done on the game thread because the hit proxy references (and possibly other members) need to be freed on the game thread.
            struct DeferDeleteHitProxies : FDeferredCleanupInterface
            {
                DeferDeleteHitProxies(TArray<TRefCountPtr<HHitProxy>>&& InHitProxies) : HitProxies(MoveTemp(InHitProxies)) {}
                TArray<TRefCountPtr<HHitProxy>> HitProxies;
            };

            // 渲染线程已经移除了,命中代理可能引用了其他成员,需要在游戏线程中释放PrimitiveSceneInfo
            BeginCleanup(new DeferDeleteHitProxies(MoveTemp(PrimitiveSceneInfo->HitProxies)));
            delete PrimitiveSceneInfo->Proxy;
            delete PrimitiveSceneInfo;
        }
    }
    else // 如果删除失败了,RemovedPrimitiveSceneInfos 会添加一个
    {
        check(PrimitiveSceneInfo->PackedIndex != INDEX_NONE);
        check(RemovedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) == nullptr);
        RemovedPrimitiveSceneInfos.FindOrAdd(PrimitiveSceneInfo);
    }
}

FScene::AddPrimitive

void FScene::AddPrimitive(UPrimitiveComponent* Primitive)
{
    // If the bulk reregister flag is set, add / remove will be handled in bulk by the FStaticMeshComponentBulkReregisterContext
    if (Primitive->bBulkReregister)
    {
        return;
    }
    BatchAddPrimitives(MakeArrayView(&Primitive, 1));
}
void FScene::BatchAddPrimitives(TArrayView<UPrimitiveComponent*> InPrimitives)
{
    // ...

    struct FCreateRenderThreadParameters
    {
        FCreateRenderThreadParameters(
            FPrimitiveSceneInfo* InPrimitiveSceneInfo,
            FPrimitiveSceneProxy* InPrimitiveSceneProxy,
            FMatrix InRenderMatrix,
            FBoxSphereBounds InWorldBounds,
            FVector InAttachmentRootPosition,
            FBoxSphereBounds InLocalBounds,
            TOptional<FTransform> InPreviousTransform)
            : PrimitiveSceneInfo(InPrimitiveSceneInfo), PrimitiveSceneProxy(InPrimitiveSceneProxy), RenderMatrix(InRenderMatrix), WorldBounds(InWorldBounds),
                AttachmentRootPosition(InAttachmentRootPosition), LocalBounds(InLocalBounds), PreviousTransform(InPreviousTransform)
        {}

        FPrimitiveSceneInfo* PrimitiveSceneInfo;
        FPrimitiveSceneProxy* PrimitiveSceneProxy;
        FMatrix RenderMatrix;
        FBoxSphereBounds WorldBounds;
        FVector AttachmentRootPosition;
        FBoxSphereBounds LocalBounds;
        TOptional<FTransform> PreviousTransform;
    };

    // 目的是构造这个 ParamsList
    TArray<FCreateRenderThreadParameters, TInlineAllocator<1>> ParamsList;
    ParamsList.Reserve(InPrimitives.Num());

    for (UPrimitiveComponent* Primitive : InPrimitives)
    {
        checkf(!Primitive->IsUnreachable(), TEXT("%s"), *Primitive->GetFullName());

        const float WorldTime = GetWorld()->GetTimeSeconds();

        // Save the world transform for next time the primitive is added to the scene
        // 上一次提交的时间
        float DeltaTime = WorldTime - Primitive->LastSubmitTime;
        if ( DeltaTime < -0.0001f || Primitive->LastSubmitTime < 0.0001f )
        {
            // Time was reset?
            Primitive->LastSubmitTime = WorldTime;
        }
        else if ( DeltaTime > 0.0001f )
        {
            // First call for the new frame?
            Primitive->LastSubmitTime = WorldTime;
        }

        checkf(!Primitive->SceneProxy, TEXT("Primitive has already been added to the scene!"));

        // Create the primitive's scene proxy.
        // 获取场景的 基源组件
        FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy();
        Primitive->SceneProxy = PrimitiveSceneProxy;
        if(!PrimitiveSceneProxy)
        {
            // Primitives which don't have a proxy are irrelevant to the scene manager.
            continue;
        }

        // Create the primitive scene info. new 一个 FPrimitiveSceneInfo
        FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo(Primitive, this);
        PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;

        // Cache the primitives initial transform.
        // 世界矩阵 和 根位置
        FMatrix RenderMatrix = Primitive->GetRenderMatrix();
        FVector AttachmentRootPosition = Primitive->GetActorPositionForRenderer();

        // 加进去
        ParamsList.Emplace(
            PrimitiveSceneInfo,
            PrimitiveSceneProxy,
            RenderMatrix,
            Primitive->Bounds,
            AttachmentRootPosition,
            Primitive->GetLocalBounds(),

            // If this primitive has a simulated previous transform, ensure that the velocity data for the scene representation is correct.
            FMotionVectorSimulation::Get().GetPreviousTransform(Primitive)
        );

        // Help track down primitive with bad bounds way before the it gets to the Renderer
        ensureMsgf(!Primitive->Bounds.ContainsNaN(),
                TEXT("Nans found on Bounds for Primitive %s: Origin %s, BoxExtent %s, SphereRadius %f"), *Primitive->GetName(), *Primitive->Bounds.Origin.ToString(), *Primitive->Bounds.BoxExtent.ToString(), Primitive->Bounds.SphereRadius);

        INC_DWORD_STAT_BY( STAT_GameToRendererMallocTotal, PrimitiveSceneProxy->GetMemoryFootprint() + PrimitiveSceneInfo->GetMemoryFootprint() );

        // Verify the primitive is valid
        VerifyProperPIEScene(Primitive, World);

        // Increment the attachment counter, the primitive is about to be attached to the scene.
        // attachment counter++, 组件准备要附加到场景里面了
        Primitive->AttachmentCounter.Increment();
    }
}

准备把 ParamsList 丢到渲染线程了
然后再渲染线程set transform

    // Create any RenderThreadResources required and send a command to the rendering thread to add the primitive to the scene.
    FScene* Scene = this;

    ENQUEUE_RENDER_COMMAND(AddPrimitiveCommand)(
        [ParamsList = MoveTemp(ParamsList), Scene](FRHICommandListImmediate& RHICmdList)
        {
            for (const FCreateRenderThreadParameters& Params : ParamsList)
            {
                FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy;
                FScopeCycleCounter Context(SceneProxy->GetStatId());
                SceneProxy->SetTransform(Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition);

                // Create any RenderThreadResources required.
                SceneProxy->CreateRenderThreadResources();

                Scene->AddPrimitiveSceneInfo_RenderThread(Params.PrimitiveSceneInfo, Params.PreviousTransform);
            }
        });

看下 AddPrimitiveSceneInfo_RenderThread。

void FScene::AddPrimitiveSceneInfo_RenderThread(FPrimitiveSceneInfo* PrimitiveSceneInfo, const TOptional<FTransform>& PreviousTransform)
{
    check(IsInRenderingThread());
    check(PrimitiveSceneInfo->PackedIndex == INDEX_NONE);
    check(AddedPrimitiveSceneInfos.Find(PrimitiveSceneInfo) == nullptr);
    AddedPrimitiveSceneInfos.FindOrAdd(PrimitiveSceneInfo);
    if (PreviousTransform.IsSet())
    {
        OverridenPreviousTransforms.Update(PrimitiveSceneInfo, PreviousTransform.GetValue().ToMatrixWithScale());
    }
}

SendAllEndOfFrameUpdates

倒退

/**
    * Send all render updates to the rendering thread.
    */
void UWorld::SendAllEndOfFrameUpdates()
{
    // 。。

    for (UMaterialParameterCollectionInstance* ParameterCollectionInstance : ParameterCollectionInstances)
    {
        if (ParameterCollectionInstance)
        {
            ParameterCollectionInstance->DeferredUpdateRenderState(false);
        }
    }
    bMaterialParameterCollectionInstanceNeedsDeferredUpdate = false;

    LocalComponentsThatNeedEndOfFrameUpdate.Reset();

    EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
}

最后是一些材质相关的更新,然后释放资源,结束

DeferredUpdateRenderState

展开看看这个

void UMaterialParameterCollectionInstance::DeferredUpdateRenderState(bool bRecreateUniformBuffer)
{
    checkf(bNeedsRenderStateUpdate || !bRecreateUniformBuffer, TEXT("DeferredUpdateRenderState was told to recreate the uniform buffer, but there's nothing to update"));

    if (bNeedsRenderStateUpdate && World.IsValid())
    {
        // Propagate the new values to the rendering thread
        TArray<FVector4f> ParameterData;
        GetParameterData(ParameterData);
        Resource->GameThread_UpdateContents(Collection.IsValid() ? Collection->StateId : FGuid(), ParameterData, GetFName(), bRecreateUniformBuffer);
    }

    bNeedsRenderStateUpdate = false;
}

获取材质参数,然后 GameThread_UpdateContents

让渲染线程 Resource->UpdateContents(InGuid, Data, InOwnerName, bRecreateUniformBuffer);

void FMaterialParameterCollectionInstanceResource::GameThread_UpdateContents(const FGuid& InGuid, const TArray<FVector4f>& Data, const FName& InOwnerName, bool bRecreateUniformBuffer)
{
    FMaterialParameterCollectionInstanceResource* Resource = this;
    ENQUEUE_RENDER_COMMAND(UpdateCollectionCommand)(
        [InGuid, Data, InOwnerName, Resource, bRecreateUniformBuffer](FRHICommandListImmediate& RHICmdList)
        {
            Resource->UpdateContents(InGuid, Data, InOwnerName, bRecreateUniformBuffer);
        }
    );
}
void FMaterialParameterCollectionInstanceResource::UpdateContents(const FGuid& InId, const TArray<FVector4f>& Data, const FName& InOwnerName, bool bRecreateUniformBuffer)
{
    Id = InId;
    OwnerName = InOwnerName;

    if (InId != FGuid() && Data.Num() > 0)
    {
        const uint32 NewSize = Data.GetTypeSize() * Data.Num();
        check(UniformBufferLayout == nullptr || UniformBufferLayout->Resources.Num() == 0);

        if (!bRecreateUniformBuffer && IsValidRef(UniformBuffer))
        {
            check(NewSize == UniformBufferLayout->ConstantBufferSize);
            check(UniformBuffer->GetLayoutPtr() == UniformBufferLayout);
            FRHICommandListImmediate::Get().UpdateUniformBuffer(UniformBuffer, Data.GetData());
        }
        else
        {
            FRHIUniformBufferLayoutInitializer UniformBufferLayoutInitializer(TEXT("MaterialParameterCollectionInstanceResource"));
            UniformBufferLayoutInitializer.ConstantBufferSize = NewSize;
            UniformBufferLayoutInitializer.ComputeHash();

            UniformBufferLayout = RHICreateUniformBufferLayout(UniformBufferLayoutInitializer); // 层级,用于管理 UniformBuffer,一个简单的new

            UniformBuffer = RHICreateUniformBuffer(Data.GetData(), UniformBufferLayout, UniformBuffer_MultiFrame);
        }
    }
}

UniformBuffer 统一buff,可以简单理解为常量缓冲区
可以先看else里面,不合法的时候重新创建
UniformBufferLayout = RHICreateUniformBufferLayout(UniformBufferLayoutInitializer);
直接new

FORCEINLINE FUniformBufferLayoutRHIRef RHICreateUniformBufferLayout(const FRHIUniformBufferLayoutInitializer& Initializer)
{
    LLM_SCOPE_BYNAME(TEXT("RHIMisc/CreateUniformBufferLayout"));
    return new FRHIUniformBufferLayout(Initializer);
}

创建统一缓冲区(常量缓冲区)
UniformBuffer = RHICreateUniformBuffer(Data.GetData(), UniformBufferLayout, UniformBuffer_MultiFrame);

FORCEINLINE FUniformBufferRHIRef RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation = EUniformBufferValidation::ValidateResources)
{
    LLM_SCOPE_BYNAME(TEXT("RHIMisc/CreateUniformBuffer"));
    return GDynamicRHI->RHICreateUniformBuffer(Contents, Layout, Usage, Validation);
}

然后进入不同跨平台的接口

打断点看,默认可能会进DX11

FUniformBufferRHIRef FD3D11DynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FD3D12DynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)

FUniformBufferRHIRef FOpenGLDynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FVulkanDynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
FUniformBufferRHIRef FMetalDynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)

上面还有一个
FRHICommandListImmediate::Get().UpdateUniformBuffer(UniformBuffer, Data.GetData());

    FORCEINLINE void UpdateUniformBuffer(FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
    {
        FRHICommandListScopedPipelineGuard ScopedPipeline(*this);
        GDynamicRHI->RHIUpdateUniformBuffer(*this, UniformBufferRHI, Contents);
    }

然后才到跨平台的接口

void FD3D12DynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FD3D11DynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FMetalDynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FOpenGLDynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
void FVulkanDynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)

SendAllEndOfFrameUpdates

总结一下

/**
    * Send all render updates to the rendering thread.
    */
void UWorld::SendAllEndOfFrameUpdates()

函数的目的是,要把所有要渲染的东西加到渲染队列里面了
首先必须是最后一帧

    if (!HasEndOfFrameUpdates())
    {
        return;
    }

遍历各种 UActorComponent 做结束帧相关逻辑

    // Wait for tasks that are generating data for the render proxies, but are not awaited in any TickFunctions 
    // E.g., see cloth USkeletalMeshComponent::UpdateClothStateAndSimulate
    for (UActorComponent* Component : ComponentsThatNeedPreEndOfFrameSync)
    {
        if (Component)
        {
            check(!IsValid(Component) || Component->GetMarkedForPreEndOfFrameSync());
            if (IsValid(Component))
            {
                Component->OnPreEndOfFrameSync();
            }
            FMarkComponentEndOfFrameUpdateState::ClearMarkedForPreEndOfFrameSync(Component);
        }
    }
    ComponentsThatNeedPreEndOfFrameSync.Reset();

    //If we call SendAllEndOfFrameUpdates during a tick, we must ensure that all marked objects have completed any async work etc before doing the updates.
    if (bInTick)
    {
        SCOPE_CYCLE_COUNTER(STAT_OnEndOfFrameUpdateDuringTick);

        //If this proves too slow we can possibly have the mark set a complimentary bit array that marks which components need this call? Or just another component array?
        for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate)
        {
            if (Component)
            {
                Component->OnEndOfFrameUpdateDuringTick();
            }
        }
        for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread)
        {
            if (Component)
            {
                Component->OnEndOfFrameUpdateDuringTick();
            }
        }
    }

最后是
BeginSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);

EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
包起来一堆代码

static TArray<UActorComponent*> LocalComponentsThatNeedEndOfFrameUpdate; // 本地组件是否需要结束帧的更新
做一些延迟更新相关的,比如transform,其他数据,就是把数据拷贝到渲染线程的代理对象
还有处理材质参数

BeginRenderingViewFamilies

void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
    TRACE_CPUPROFILER_EVENT_SCOPE(BeginRenderingViewFamily);
    check(Canvas);
    for (FSceneViewFamily* ViewFamily : ViewFamilies)
    {
        check(ViewFamily);
        check(ViewFamily->Scene == ViewFamilies[0]->Scene);
    }

    UWorld* World = nullptr;

    FScene* const Scene = ViewFamilies[0]->Scene ? ViewFamilies[0]->Scene->GetRenderScene() : nullptr;
    if (Scene)
    {
        World = Scene->GetWorld();
        if (World)
        {
            // Guarantee that all render proxies are up to date before kicking off a BeginRenderViewFamily.
            World->SendAllEndOfFrameUpdates();

            GetNaniteVisualizationData().Pick(World); // 给nanite手机一些数据用
        }
    }

    // 统计的
    ENQUEUE_RENDER_COMMAND(SetRtWaitCriticalPath)(
    [](FRHICommandList& RHICmdList)
    {
        // Rendering is up and running now, so waits are considered part of the RT critical path
        FThreadIdleStats::BeginCriticalPath();
    });

    // 更新统一表达式的缓存
    FUniformExpressionCacheAsyncUpdateScope AsyncUpdateScope;

    // Vedio random access memery VRAM的全称
    // 更新一些参数,用于优化
    ENQUEUE_RENDER_COMMAND(UpdateFastVRamConfig)(
        [](FRHICommandList& RHICmdList)
        {
            GFastVRamConfig.Update();
        });

    // Flush the canvas first.
    // 刷新游戏线程,确保绘制已经完成了
    Canvas->Flush_GameThread();

void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
    // 。。。

    // FrameNumber 更新
    if (Scene)
    {
        // We allow caching of per-frame, per-scene data
        if (ViewFamilies[0]->bIsFirstViewInMultipleViewFamily)
        {
            Scene->IncrementFrameNumber();
        }
        for (FSceneViewFamily* ViewFamily : ViewFamilies)
        {
            ViewFamily->FrameNumber = Scene->GetFrameNumber();
        }
    }
    else
    {
        // this is passes to the render thread, better access that than GFrameNumberRenderThread
        for (FSceneViewFamily* ViewFamily : ViewFamilies)
        {
            ViewFamily->FrameNumber = GFrameNumber;
        }
    }
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
    // 。。。

    // 对所有 family的 ViewExtensions 扩展做 BeginRenderViewFamily
    for (FSceneViewFamily* ViewFamily : ViewFamilies)
    {
        PRAGMA_DISABLE_DEPRECATION_WARNINGS
        // If optional parameter OverrideFrameCounter is set, use its value instead of GFrameCounter.
        ViewFamily->FrameCounter = ViewFamily->OverrideFrameCounter.IsSet() ? ViewFamily->OverrideFrameCounter.GetValue() : GFrameCounter;
        PRAGMA_ENABLE_DEPRECATION_WARNINGS

#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST)
        {
            extern TSharedRef<ISceneViewExtension, ESPMode::ThreadSafe> GetRendererViewExtension();

            ViewFamily->ViewExtensions.Add(GetRendererViewExtension());
        }
#endif // !(UE_BUILD_SHIPPING || UE_BUILD_TEST)

        // Force the upscalers to be set no earlier than ISceneViewExtension::BeginRenderViewFamily();
        check(ViewFamily->GetTemporalUpscalerInterface() == nullptr);
        check(ViewFamily->GetPrimarySpatialUpscalerInterface() == nullptr);
        check(ViewFamily->GetSecondarySpatialUpscalerInterface() == nullptr);

        for (int ViewExt = 0; ViewExt < ViewFamily->ViewExtensions.Num(); ViewExt++)
        {
            ViewFamily->ViewExtensions[ViewExt]->BeginRenderViewFamily(*ViewFamily);
        }

        checkf(!(ViewFamily->GetTemporalUpscalerInterface() != nullptr && ViewFamily->GetPrimarySpatialUpscalerInterface() != nullptr),
            TEXT("Conflict setting up a third party primary spatial upscaler or temporal upscaler."));
    }
void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
    // 。。。

    if (Scene)
    {       
        // Set the world's "needs full lighting rebuild" flag if the scene has any uncached static lighting interactions.
        if(World)
        {
            // Note: reading NumUncachedStaticLightingInteractions on the game thread here which is written to by the rendering thread
            // This is reliable because the RT uses interlocked mechanisms to update it
            World->SetMapNeedsLightingFullyRebuilt(Scene->NumUncachedStaticLightingInteractions, Scene->NumUnbuiltReflectionCaptures); // 需要完全构建光照的标志
        }

        // Construct the scene renderers.  This copies the view family attributes into its own structures.
        TArray<FSceneRenderer*> SceneRenderers;

        TArray<const FSceneViewFamily*> ViewFamiliesConst;
        for (FSceneViewFamily* ViewFamily : ViewFamilies)
        {
            ViewFamiliesConst.Add(ViewFamily);
        }

        // 创建场景渲染器 SceneRenderers
        FSceneRenderer::CreateSceneRenderers(ViewFamiliesConst, Canvas->GetHitProxyConsumer(), SceneRenderers);

}

void FRendererModule::BeginRenderingViewFamilies(FCanvas* Canvas, TArrayView<FSceneViewFamily*> ViewFamilies)
{
    // 。。。

    if (Scene)
    {       
        // 。。。

        // 是否启用了命中代理
        bool bShowHitProxies = false;
        for (FSceneRenderer* SceneRenderer : SceneRenderers)
        {
            if (SceneRenderer->ViewFamily.EngineShowFlags.HitProxies)
            {
                bShowHitProxies = true;
                break;
            }
        }

        if (!bShowHitProxies)
        {
            USceneCaptureComponent::UpdateDeferredCaptures(Scene); // 延迟更新和当前场景相关的一些 场景捕捉组件

            // 平面反射相关的
            for (int32 ReflectionIndex = 0; ReflectionIndex < Scene->PlanarReflections_GameThread.Num(); ReflectionIndex++)
            {
                UPlanarReflectionComponent* ReflectionComponent = Scene->PlanarReflections_GameThread[ReflectionIndex];
                for (FSceneRenderer* SceneRenderer : SceneRenderers)
                {
                    Scene->UpdatePlanarReflectionContents(ReflectionComponent, *SceneRenderer);
                }
            }
        }

        for (FSceneRenderer* SceneRenderer : SceneRenderers)
        {
            SceneRenderer->ViewFamily.DisplayInternalsData.Setup(World); // 显示内部数据,根据一些命令行变量,决定如何显示一些内容
        }

        FSceneRenderer::PreallocateCrossGPUFences(SceneRenderers); // 预分配画GPU的围栏

        const uint64 DrawSceneEnqueue = FPlatformTime::Cycles64();
        ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)(
            [LocalSceneRenderers = CopyTemp(SceneRenderers), DrawSceneEnqueue](FRHICommandListImmediate& RHICmdList)
            {
                uint64 SceneRenderStart = FPlatformTime::Cycles64();
                const float StartDelayMillisec = FPlatformTime::ToMilliseconds64(SceneRenderStart - DrawSceneEnqueue);
                CSV_CUSTOM_STAT_GLOBAL(DrawSceneCommand_StartDelay, StartDelayMillisec, ECsvCustomStatOp::Set);
                RenderViewFamilies_RenderThread(RHICmdList, LocalSceneRenderers); // 渲染场景
                FlushPendingDeleteRHIResources_RenderThread(); // 一些同步操作
            });

        // Force kick the RT if we've got RT polling on.
        // This saves us having to wait until the polling period before the scene draw starts executing.
        if (GRenderThreadPollingOn)
        {
            FTaskGraphInterface::Get().WakeNamedThread(ENamedThreads::GetRenderThread()); // 强制唤醒渲染线程
        }
    }
}

FCanvas::Flush_GameThread

上面有一个函数需要仔细看下

void FCanvas::Flush_GameThread(bool bForce)
{
    SCOPE_CYCLE_COUNTER(STAT_Canvas_FlushTime);

    if( !(AllowedModes&Allow_Flush) && !bForce ) // 是否允许刷新画布,是否强制刷新
    { 
        return; 
    }

    // current render target set for the canvas
    check(RenderTarget);

    if (!(AllowedModes & Allow_DeleteOnRender)) // 刷新的时候必须 允许 Allow_DeleteOnRender
    {
        ensureMsgf(false, TEXT("Flush_GameThread requires Allow_DeleteOnRender flag to be set."));
    }

    // no need to set the render target if we aren't going to draw anything to it!
    if (SortedElements.Num() == 0)
    {
        return;
    }

一般是在这种地方,前面是Draw,添加了要绘制的元素,SortedElements就有东西需要画

FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
{
    ViewportClient->Draw(this, &Canvas);
}
Canvas.Flush_GameThread();
void FCanvas::Flush_GameThread(bool bForce)
{
    // 。。。

    // Update the font cache with new text before elements are drawn
    // 字体服务是否初始化了
    if (FEngineFontServices::IsInitialized())
    {
        FEngineFontServices::Get().UpdateCache(); // 如果没有
    }

    // FCanvasSortElement compare class
    // 进行排序
    struct FCompareFCanvasSortElement
    {
        FORCEINLINE bool operator()( const FCanvasSortElement& A, const FCanvasSortElement& B ) const
        {
            return B.DepthSortKey < A.DepthSortKey;
        }
    };
    // sort the array of FCanvasSortElement entries so that higher sort keys render first (back-to-front)
    SortedElements.Sort(FCompareFCanvasSortElement());

    bool bEmitCanvasDrawEvents = GetEmitDrawEvents(); // 变量没用?

    // Only create these render commands if we actually have something to draw.
    if(SortedElements.Num() > 0 && !GUsingNullRHI)
    {
        // 有东西要画

        FCanvasRenderThreadScope RenderThreadScope(*this);

        // iterate over the FCanvasSortElements in sorted order and render all the batched items for each entry
        for( int32 Idx=0; Idx < SortedElements.Num(); Idx++ )
        {
            FCanvasSortElement& SortElement = SortedElements[Idx];
            for( int32 BatchIdx=0; BatchIdx < SortElement.RenderBatchArray.Num(); BatchIdx++ )
            {
                FCanvasBaseRenderItem* RenderItem = SortElement.RenderBatchArray[BatchIdx]; // 要画的东西
                if( RenderItem )
                {
                    // mark current render target as dirty since we are drawing to it
                    bRenderTargetDirty |= RenderItem->Render_GameThread(this, RenderThreadScope);

                    RenderThreadScope.DeferredDelete(RenderItem); // 延迟删除
                }
            }
            SortElement.RenderBatchArray.Empty();
        }
    }
    else
    {
        // 。。。
    }

Render_GameThread

bool FCanvasBatchedElementRenderItem::Render_GameThread(const FCanvas* Canvas, FCanvasRenderThreadScope& RenderScope)
{   
    checkSlow(Data);
    bool bDirty=false;
    if( Data->BatchedElements.HasPrimsToDraw() ) // 批处理是否需要绘制图元
    {
        bDirty = true;

        // current render target set for the canvas
        // 获取 RT 和 gamma(用于后面颜色计算)
        const FRenderTarget* CanvasRenderTarget = Canvas->GetRenderTarget();
        float Gamma = 1.0f / CanvasRenderTarget->GetDisplayGamma();
        if ( Data->Texture && Data->Texture->bIgnoreGammaConversions )
        {
            Gamma = 1.0f;
        }

        // Render the batched elements.
        // 准备一个绘制参数
        struct FBatchedDrawParameters
        {
            FRenderData* RenderData;
            uint32 bHitTesting : 1;
            uint32 ViewportSizeX;
            uint32 ViewportSizeY;
            float DisplayGamma;
            uint32 AllowedCanvasModes;
            ERHIFeatureLevel::Type FeatureLevel;
            EShaderPlatform ShaderPlatform;
        };
        // all the parameters needed for rendering
        FBatchedDrawParameters DrawParameters =
        {
            Data,
            (uint32)(Canvas->IsHitTesting() ? 1 : 0),
            (uint32)CanvasRenderTarget->GetSizeXY().X,
            (uint32)CanvasRenderTarget->GetSizeXY().Y,
            Gamma,
            Canvas->GetAllowedModes(),
            Canvas->GetFeatureLevel(),
            Canvas->GetShaderPlatform()
        };

        // 添加一个pass,lambda在渲染线程去做
        RenderScope.AddPass(
            TEXT("CanvasBatchedElements"),
            [DrawParameters](FRHICommandList& RHICmdList)
        {
            FSceneView SceneView = FBatchedElements::CreateProxySceneView(DrawParameters.RenderData->Transform.GetMatrix(), FIntRect(0, 0, DrawParameters.ViewportSizeX, DrawParameters.ViewportSizeY));

            FMeshPassProcessorRenderState DrawRenderState;

            // disable depth test & writes
            DrawRenderState.SetDepthStencilState(TStaticDepthStencilState<false, CF_Always>::GetRHI());
            DrawRenderState.SetBlendState(TStaticBlendState<>::GetRHI());

            // draw batched items
            DrawParameters.RenderData->BatchedElements.Draw( // 《==========这里也要看看
                RHICmdList,
                DrawRenderState,
                DrawParameters.FeatureLevel,
                SceneView,
                DrawParameters.bHitTesting,
                DrawParameters.DisplayGamma);

            if(DrawParameters.AllowedCanvasModes & FCanvas::Allow_DeleteOnRender )
            {
                delete DrawParameters.RenderData;
            }
        });
    }
    if( Canvas->GetAllowedModes() & FCanvas::Allow_DeleteOnRender )
    {
        Data = NULL;
    }
    return bDirty;
}
void FCanvas::Flush_GameThread(bool bForce)
{
    // 。。。

    // Only create these render commands if we actually have something to draw.
    if(SortedElements.Num() > 0 && !GUsingNullRHI)
    {
        // 。。。
    }
    else
    {
        // iterate over the FCanvasSortElements in sorted order and delete all the batched items for each entry
        for (int32 Idx = 0; Idx < SortedElements.Num(); Idx++)
        {
            FCanvasSortElement& SortElement = SortedElements[Idx];
            for (int32 BatchIdx = 0; BatchIdx < SortElement.RenderBatchArray.Num(); BatchIdx++)
            {
                FCanvasBaseRenderItem* RenderItem = SortElement.RenderBatchArray[BatchIdx];
                if (RenderItem)
                {
                    delete RenderItem;
                }
            }
            SortElement.RenderBatchArray.Empty();
        }
    }

    // empty the array of FCanvasSortElement entries after finished with rendering
    SortedElements.Empty();
    SortedElementLookupMap.Empty();
    LastElementIndex = INDEX_NONE;
}

FEditorViewportClient::Draw

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    // 。。。

    // Draw the 3D scene
    GetRendererModule().BeginRenderingViewFamily(Canvas,&ViewFamily);

    if (View)
    {
        DrawCanvas( *Viewport, *View, *Canvas );

        DrawSafeFrames(*Viewport, *View, *Canvas);
    }

    // 。。。
}

BeginRenderingViewFamily 结束了
DrawCanvas 是绘制一些UI相关的,比如可视化HUD,Debug工具UI,编辑器模式下,选中一个物体外围有一个括弧等
DrawSafeFrames 绘制一个安全宽。比屏幕小一点,提醒美术不要做的太靠边了

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    // 。。。

    if (View)
    {
        DrawCanvas( *Viewport, *View, *Canvas );

        DrawSafeFrames(*Viewport, *View, *Canvas);
    }

    // 移除调试的线条
    // Remove temporary debug lines.
    // Possibly a hack. Lines may get added without the scene being rendered etc.
    if (World && World->LineBatcher != NULL && (World->LineBatcher->BatchedLines.Num() || World->LineBatcher->BatchedPoints.Num() || World->LineBatcher->BatchedMeshes.Num() ) )
    {
        World->LineBatcher->Flush();
    }

    // 移除前景的调试的线条
    if (World && World->ForegroundLineBatcher != NULL && (World->ForegroundLineBatcher->BatchedLines.Num() || World->ForegroundLineBatcher->BatchedPoints.Num() || World->ForegroundLineBatcher->BatchedMeshes.Num() ) )
    {
        World->ForegroundLineBatcher->Flush();
    }

    // Draw the widget.
    // 比如编辑器编辑模式 旋转物体的时候,有一个调试字体表示角度,那个HUD
    if (Widget && bShowWidget)
    {
        Widget->DrawHUD( Canvas );
    }

    // Axes indicators
    // 屏幕下面,绘制的那一条缩放条 这么长线段 是 多长 的那个东西
    const bool bShouldDrawAxes = (bDrawAxes && !ViewFamily.EngineShowFlags.Game) || (bDrawAxesGame && ViewFamily.EngineShowFlags.Game);
    if (bShouldDrawAxes && !GLevelEditorModeTools().IsViewportUIHidden() && !IsVisualizeCalibrationMaterialEnabled())
    {
        switch (GetViewportType())
        {
        case LVT_OrthoXY:
            {
                const FRotator XYRot(-90.0f, -90.0f, 0.0f);
                DrawAxes(Viewport, Canvas, &XYRot, EAxisList::XY);
                if (View)
                {
                    DrawScaleUnits(Viewport, Canvas, *View);
                }
                break;
            }

            // 。。。

        default:
            {
                DrawAxes(Viewport, Canvas);
                break;
            }
        }
    }

    // 。。。
}

绘制一些调试信息,比如敲命令stat 出来的帧数fps的那个字

    // NOTE: DebugCanvasObject will be created by UDebugDrawService::Draw() if it doesn't already exist.
    UDebugDrawService::Draw(ViewFamily.EngineShowFlags, Viewport, View, DebugCanvas);
    UCanvas* DebugCanvasObject = FindObjectChecked<UCanvas>(GetTransientPackage(),TEXT("DebugCanvasObject"));
    DebugCanvasObject->Canvas = DebugCanvas;
    DebugCanvasObject->Init( Viewport->GetSizeXY().X, Viewport->GetSizeXY().Y, View , DebugCanvas);

    // Stats display
    if( IsRealtime() && ShouldShowStats() && DebugCanvas)
    {
        const int32 XPos = 4;
        TArray< FDebugDisplayProperty > EmptyPropertyArray;
        DrawStatsHUD( World, Viewport, DebugCanvas, NULL, EmptyPropertyArray, GetViewLocation(), GetViewRotation() ); // 这里面是绘制调试信息,比如你按了 stat fps
    }

    Viewport = ViewportBackup;

函数结束

FCanvas Canvas(DummyViewport, NULL, ViewportClient->GetWorld(), ViewportClient->GetWorld()->GetFeatureLevel());
{
    ViewportClient->Draw(DummyViewport, &Canvas);
}
Canvas.Flush_GameThread();

这里Draw完成后,就到了 Flush_GameThread。
这三行逻辑在很多地方都有,不管是高清截图里面还是,游戏每帧渲染。
回到这里继续看

void FViewport::Draw( bool bShouldPresent /*= true */)
{
    // ...

    if(!bReentrant)
    {
        // if this is a game viewport, and game rendering is disabled, then we don't want to actually draw anything
        if ( World && World->IsGameWorld() && !bIsGameRenderingEnabled)
        {
            // since we aren't drawing the viewport, we still need to update streaming
            World->UpdateLevelStreaming();
        }
        else
        {
            if( GIsHighResScreenshot ) 
            {
                // 。。。
                HighResScreenshot();
            }
            else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired)
            {

            }

            if( SizeX > 0 && SizeY > 0 )
            {

                // 。。。
                FCanvas Canvas(this, nullptr, ViewportWorld, ViewportWorld ? ViewportWorld->GetFeatureLevel() : GMaxRHIFeatureLevel, FCanvas::CDM_DeferDrawing, ViewportClient->ShouldDPIScaleSceneCanvas() ? ViewportClient->GetDPIScale() : 1.0f);
                Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY));
                {
                    ViewportClient->Draw(this, &Canvas);
                }
                Canvas.Flush_GameThread();
                // 。。。
            }

        }
    }

    // ...
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇