UE 渲染入门 v1

FlushRenderingCommands

void FlushRenderingCommands()
{
    if (!GIsRHIInitialized) // RHI必须初始化
    {
        return;
    }

    TRACE_CPUPROFILER_EVENT_SCOPE(FlushRenderingCommands); // 开启CPU性能分析
    FCoreRenderDelegates::OnFlushRenderingCommandsStart.Broadcast(); // 广播事件,开始刷命令了
    FSuspendRenderingTickables SuspendRenderingTickables; // 挂起渲染的tick

    // Need to flush GT because render commands from threads other than GT are sent to
    // the main queue of GT when RT is disabled
    if (!GIsThreadedRendering
        && !FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::GameThread)
        && !FTaskGraphInterface::Get().IsThreadProcessingTasks(ENamedThreads::GameThread_Local))
    {
        FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread);
        FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread_Local);
    }

    // ENQUEUE_RENDER_COMMAND 会让里面的lambda执行在渲染线程中
    ENQUEUE_RENDER_COMMAND(FlushPendingDeleteRHIResourcesCmd)([](FRHICommandListImmediate& RHICmdList)
    {
        RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThreadFlushResources);
        //double flush to flush out the deferred deletions queued into the ImmediateCmdList
        RHICmdList.ImmediateFlush(EImmediateFlushType::FlushRHIThread);
    });

    // Find the objects which may be cleaned up once the rendering thread command queue has been flushed.
    // 等待清空的对象列表
    FPendingCleanupObjects* PendingCleanupObjects = GetPendingCleanupObjects();

    // Issue a fence command to the rendering thread and wait for it to complete.
    // 围栏
    FRenderCommandFence Fence;
    Fence.BeginFence();
    Fence.Wait();

    // Delete the objects which were enqueued for deferred cleanup before the command queue flush.
    delete PendingCleanupObjects;

    FCoreRenderDelegates::OnFlushRenderingCommandsEnd.Broadcast();
}

回忆我们在DX12里面,提交完命令后使用围栏,等待命令完成。类似的功能

    ThrowIfFailed(GraphicsCommandList->Close())
    ID3D12CommandList* CommandLists[] = {GraphicsCommandList.Get()};
    CommandQueue->ExecuteCommandLists(_countof(CommandLists), CommandLists);

    WaitGPUCommandQueueComplete();
void CDirectXRenderingEngine::WaitGPUCommandQueueComplete()
{
    CurrentFenceValue++;

    // set fence value, and wait for GPU to finish
    ThrowIfFailed(CommandQueue->Signal(Fence.Get(), CurrentFenceValue))

    if (Fence->GetCompletedValue() < CurrentFenceValue)
    {
        HANDLE EventHandle = CreateEventEx(nullptr, nullptr, false, EVENT_ALL_ACCESS);
        ThrowIfFailed(Fence->SetEventOnCompletion(CurrentFenceValue, EventHandle))
        WaitForSingleObject(EventHandle, INFINITE); // block the thread until GPU finish
        CloseHandle(EventHandle);
    }
}

UpdateSingleViewportClient

了解虚幻是怎么渲染一帧的

bool UEditorEngine::UpdateSingleViewportClient(FEditorViewportClient* InViewportClient, const bool bInAllowNonRealtimeViewportToDraw, bool bLinkedOrthoMovement )
{
    bool bUpdatedNonRealtimeViewport = false;

    if (InViewportClient->Viewport->IsSlateViewport())
    {
        // When rendering the viewport we need to know whether the final result will be shown on a HDR display. This affects the final post processing step
        FSceneViewport *SceneViewport = static_cast<FSceneViewport*>(InViewportClient->Viewport);
        TSharedPtr<SWindow> Window = SceneViewport->FindWindow();
        if (Window)
        {
            InViewportClient->Viewport->SetHDRMode(Window->GetIsHDR());
        }
    }

如果是SlateViewport。那么就cast为 FSceneViewport。
并从中获得 SWindow,设置HDR。

class FSceneViewport : public FViewportFrame, public FViewport, public ISlateViewport, public IViewportRenderTargetProvider
{
};

看一下函数和属性,初步印象,基本都是描述一个窗口的。例如鼠标键盘相关。几何数据,Init Release RHI , 资源等。 [03A-10]

bool UEditorEngine::UpdateSingleViewportClient(FEditorViewportClient* InViewportClient, const bool bInAllowNonRealtimeViewportToDraw, bool bLinkedOrthoMovement )
{
    bool bUpdatedNonRealtimeViewport = false;

    // 。。。

    // Always submit view information for content streaming 
    // otherwise content for editor view can be streamed out if there are other views (ex: thumbnails)
    if (InViewportClient->IsPerspective())
    {
        float XSize = static_cast<float>(InViewportClient->Viewport->GetSizeXY().X);

        IStreamingManager::Get().AddViewInformation( InViewportClient->GetViewLocation(), XSize, XSize / FMath::Tan(FMath::DegreesToRadians(InViewportClient->ViewFOV * 0.5f)) );
    }

如果是透视视口。视场角的屏幕大小
Tan 0.5FOV = XSize / FOVScreenSize

bool UEditorEngine::UpdateSingleViewportClient(FEditorViewportClient* InViewportClient, const bool bInAllowNonRealtimeViewportToDraw, bool bLinkedOrthoMovement )
{
    bool bUpdatedNonRealtimeViewport = false;

    // ...

    // Only allow viewports to be drawn if we are not throttling for slate UI responsiveness or if the viewport client requested a redraw
    // Note about bNeedsRedraw: Redraws can happen during some Slate events like checking a checkbox in a menu to toggle a view mode in the viewport.  In those cases we need to show the user the results immediately
    if( FSlateThrottleManager::Get().IsAllowingExpensiveTasks() || InViewportClient->bNeedsRedraw ) // 允许昂贵的认为,并且需要渲染
    {
        // Switch to the world used by the viewport before its drawn
        FScopedConditionalWorldSwitcher WorldSwitcher( InViewportClient ); // 游戏浏览的时候,临时切到另一个世界,过作用域再切回来

        // Add view information for perspective viewports.
        if( InViewportClient->IsPerspective() ) // 透视
        {
            if (UWorld* ViewportClientWorld = InViewportClient->GetWorld())
            {
                ViewportClientWorld->ViewLocationsRenderedLastFrame.Add(InViewportClient->GetViewLocation()); // 记录上一次的视口位置
            }

            // If we're currently simulating in editor, then we'll need to make sure that sub-levels are streamed in.
            // When using PIE, this normally happens by UGameViewportClient::Draw().  But for SIE, we need to do
            // this ourselves!
            if( PlayWorld != NULL && bIsSimulatingInEditor && InViewportClient->IsSimulateInEditorViewport() ) // SIE
            {
                // Update level streaming.
                InViewportClient->GetWorld()->UpdateLevelStreaming(); // 更新关卡流,为了无缝关卡切换用

                // Also make sure hit proxies are refreshed for SIE viewports, as the user may be trying to grab an object or widget manipulator that's moving!
                if( InViewportClient->IsRealtime() )
                {
                    // @todo simulate: This may cause simulate performance to be worse in cases where you aren't needing to interact with gizmos.  Consider making this optional.
                    InViewportClient->RequestInvalidateHitProxy( InViewportClient->Viewport ); // 实时的命中代理无效,例如SIE模式下,点视口都是无效的。
                }
            }
        }

        // Redraw the viewport if it's realtime.
        if( InViewportClient->IsRealtime() )
        {
            InViewportClient->Viewport->Draw();
            InViewportClient->bNeedsRedraw = false;
            InViewportClient->bNeedsLinkedRedraw = false;
        }
        // Redraw any linked ortho viewports that need to be updated this frame.
        else if( InViewportClient->IsOrtho() && bLinkedOrthoMovement && InViewportClient->IsVisible() ) // 正交
        {
            if( InViewportClient->bNeedsLinkedRedraw || InViewportClient->bNeedsRedraw )
            {
                // Redraw this viewport
                InViewportClient->Viewport->Draw();
                InViewportClient->bNeedsLinkedRedraw = false;
                InViewportClient->bNeedsRedraw = false;
            }
            else
            {
                // This viewport doesn't need to be redrawn.  Skip this frame and increment the number of frames we skipped.
                InViewportClient->FramesSinceLastDraw++;
            }
        }
        // Redraw the viewport if there are pending redraw, and we haven't already drawn one viewport this frame.
        else if (InViewportClient->bNeedsRedraw && bInAllowNonRealtimeViewportToDraw)
        {
            InViewportClient->Viewport->Draw();
            InViewportClient->bNeedsRedraw = false;
            bUpdatedNonRealtimeViewport = true;
        }
        else if(UWorld* World = GetWorld())
        {
            // We're not rendering but calculate the view anyway so that we can cache the last "rendered" view info in the UWorld.
            FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(InViewportClient->Viewport, InViewportClient->GetScene(), InViewportClient->EngineShowFlags));
            FSceneView* View = InViewportClient->CalcSceneView(&ViewFamily);

            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();
        }

        if (InViewportClient->bNeedsInvalidateHitProxy)
        {
            InViewportClient->Viewport->InvalidateHitProxy();
            InViewportClient->bNeedsInvalidateHitProxy = false;
        }
    }

    return bUpdatedNonRealtimeViewport;
}

FViewport::Draw

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"));

        // bTakeHighResScreenShot 会在函数 bool FViewport::TakeHighResScreenShot() 中被设置
        GIsHighResScreenshot = GIsHighResScreenshot || bTakeHighResScreenShot;

        // FScreenshotRequest::IsScreenshotRequested() 会在 FScreenshotRequest::RequestScreenshot 被设置
        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)
        {
            // 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();
                FScreenshotRequest::RequestScreenshot( FString(), bShowUI, bAddFilenameSuffix, bHDRScreenshot);
                HighResScreenshot();
            }
            else if(bAnyScreenshotsRequired && bBufferVisualizationDumpingRequired) // 是否要截图 && 缓冲区可视化
            {
                // 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 );
            }

            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);
                }
                EnqueueBeginRenderFrame(bShouldPresent); // 渲染线程开始帧

                // Calculate gamethread time (excluding idle time)
                // 时间计算
                {
                    static uint64 LastFrameUpdated = MAX_uint64;
                    if (GFrameCounter != LastFrameUpdated)
                    {
                        static uint32 Lastimestamp = 0;
                        static bool bStarted = false;
                        uint32 CurrentTime  = FPlatformTime::Cycles();
                        FThreadIdleStats& GameThread = FThreadIdleStats::Get();
                        if (bStarted)
                        {
                            uint32 ThreadTime   = CurrentTime - Lastimestamp;
                            // add any stalls via sleep or fevent
                            GGameThreadTime     = (ThreadTime > GameThread.Waits) ? (ThreadTime - GameThread.Waits) : ThreadTime;
                            GGameThreadWaitTime = GameThread.Waits;
                        }
                        else
                        {
                            bStarted = true;
                        }

                        LastFrameUpdated = GFrameCounter;
                        Lastimestamp        = CurrentTime;
                        GameThread.Reset();
                    }
                }

                UWorld* ViewportWorld = ViewportClient->GetWorld();

                // 把世界画在画布上
                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); // 如果是截图,这里会处理截图的内容

                // Slate doesn't present immediately. Tag the viewport as requiring vsync so that it happens.
                SetRequiresVsync(bLockToVsync);
                EnqueueEndRenderFrame(bLockToVsync, bShouldPresent);

                GInputLatencyTimer.GameThreadTrigger = false;
            }
        }

        // Reset the camera cut flags if we are in a viewport that has a world
        if (World)
        {
            for( FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator )
            {
                APlayerController* PlayerController = Iterator->Get();
                if (PlayerController && PlayerController->PlayerCameraManager)
                {
                    // 设置一下变量的值,有其他用。比如是否要用上一帧的遮挡查询运动模糊
                    PlayerController->PlayerCameraManager->bGameCameraCutThisFrame = false;
                }
            }
        }

        // countdown the present delay, and then stop the movie at the end
        // this doesn't need to be on rendering thread as long as we have a long enough delay (2 or 3 frames), because
        // the rendering thread will never be more than one frame behind
        if (PresentAndStopMovieDelay > 0)
        {
            PresentAndStopMovieDelay--;
            // stop any playing movie
            if (PresentAndStopMovieDelay == 0)
            {
                // Enable game rendering again if it isn't already.
                bIsGameRenderingEnabled = true;
            }
        }
    }
}

FEditorViewportClient::Draw

    UWorld* ViewportWorld = ViewportClient->GetWorld();
    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();

上面渲染那一小块。准备一个画布,然后把 ViewportClient 渲染上去。
这个Draw是这个地方
void FEditorViewportClient::Draw(FViewport InViewport, FCanvas Canvas)

void FEditorViewportClient::Draw(FViewport* InViewport, FCanvas* Canvas)
{
    // 备份视口,如果有 InViewport 就用,否则就是之前的
    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;
    }

    // Allow HMD to modify the view later, just before rendering
    // 是否立体渲染(指VR)
    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);
    }

    // 。。。
}

FSceneViewFamily

/**
 * 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();
};

先理解这个对象,主要就是 FSceneViewFamily 包含一些构造参数了。

/**
 * A set of views into a scene which only have different view transforms and owner actors.
 */
class FSceneViewFamily
{
};

表示一组视图

类内一个struct
用于描述创建FSceneViewFamily实例的参数

    /**
    * Helper struct for creating FSceneViewFamily instances
    * If created with specifying a time it will retrieve them from the world in the given scene.
    * 
    * @param InRenderTarget     The render target which the views are being rendered to.
    * @param InScene            The scene being viewed.
    * @param InShowFlags        The show flags for the views.
    *
    */
    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;

        // 。。。
    };

回到这个类

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

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

    // RT
    const FRenderTarget* RenderTarget;

这里的 FRenderTarget 也值得一看

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);
};

回来继续

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;

反正就这么个东西。。。

FCanvas

先看这一小段成员

/**
 * Encapsulates the canvas state.
 */
class FCanvas
{
private:
    // ...

    TArray<int32> DepthSortKeyStack; // 深度排序Key。使用的一直是top的key
    TArray<FTransformEntry> TransformStack;   // 存储矩阵的栈,最底部的是投影矩阵
    FIntRect ViewRect; /** View rect for the render target */
    FIntRect ScissorRect; /** Scissor rect for the render target */
    FRenderTarget* RenderTarget; /** Current render target used by the canvas */

    FHitProxyConsumer* HitProxyConsumer; // 收集点击命中的东西
    TRefCountPtr<HHitProxy> CurrentHitProxy; // 命中代理

    FSceneInterface* Scene;

    // Enum for canvas features that are allowed
    enum ECanvasAllowModes
    {
        Allow_Flush = 1 << 0, // flushing and rendering
        Allow_DeleteOnRender = 1 << 1 // delete the render batches when rendering
    };
    uint32 AllowedModes; // 画布开关, ECanvasAllowModes  就在上面

    bool bRenderTargetDirty; // 画布是否为脏, true if the render target has been rendered to since last calling SetRenderTarget(),避免多次渲染
    FGameTime Time;

    bool bScaledToRenderTarget; // true, if Canvas should be scaled to whole render target
    ERHIFeatureLevel::Type FeatureLevel; // Feature level that we are currently rendering with
    bool bStereoRendering; // true, if Canvas should be rendered in stereo 立体渲染VR
    bool bUseInternalTexture; // true, if Canvas is being rendered in its own texture

    FIntPoint ParentSize;

    enum ECanvasDrawMode
    {
        CDM_DeferDrawing, // 延迟渲染
        CDM_ImmediateDrawing // 马上渲染
    };
    ECanvasDrawMode DrawMode;
    float DPIScale;

    void Construct(); // Shared construction function

    // ...
};

FCanvers里面有一个类,主要包括 DepthSortKey 和一个 == 方法。
还有 TArray<class FCanvasBaseRenderItem*> RenderBatchArray;

    /** 
    * Contains all of the batched elements that need to be rendered at a certain depth sort key
    */
    class FCanvasSortElement
    {
    public:
        // ...

        bool operator==(const FCanvasSortElement& Other) const { return DepthSortKey == Other.DepthSortKey; }
        int32 DepthSortKey;

        /** list of batches that should be rendered at this sort key level */
        // 渲染的批量元素合集的类
        TArray<class FCanvasBaseRenderItem*> RenderBatchArray;
    };

关于 FCanvasBaseRenderItem

/**
* 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; }
};

这里有 FCanvasTileRendererItem。Tile 瓦片,渲染一个很大的东西的时候,可以让他一块一块的出现,就是瓦片的概念。
这个类继承了 FCanvasBaseRenderItem,并重写了一些接口。同时 FCanvasTriangleRendererItem 和 FCanvasBatchedElementRenderItem 也一样

class FCanvasTileRendererItem : public FCanvasBaseRenderItem
{

    TSharedPtr<FRenderData> Data;
}

里面有个 FRenderData。里面就有 TArray Tiles; 瓦片的实例

我们看下三角形这个类

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

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

    TSharedPtr<FRenderData> Data;
}

看看 FCanvasUVTri,

/** 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;
};

回去FCanvas

/**
 * 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
};
/**
 * Encapsulates the canvas state.
 */
class FCanvas
{
public:

    // 批处理的元素类型
    enum EElementType
    {
        ET_Line,
        ET_Triangle,
        ET_MAX
    };

    ENGINE_API static FCanvas* Create(FRDGBuilder& GraphBuilder, FRDGTextureRef InRenderTarget, FHitProxyConsumer* InHitProxyConsumer, const FGameTime& Time, ERHIFeatureLevel::Type InFeatureLevel, float InDPIScale = 1.0f);
    ENGINE_API FCanvas(FRenderTarget* InRenderTarget, FHitProxyConsumer* InHitProxyConsumer, UWorld* InWorld, ERHIFeatureLevel::Type InFeatureLevel, ECanvasDrawMode DrawMode = CDM_DeferDrawing, float InDPIScale = 1.0f);
    ENGINE_API FCanvas(FRenderTarget* InRenderTarget, FHitProxyConsumer* InHitProxyConsumer, const FGameTime& Time, ERHIFeatureLevel::Type InFeatureLevel, float InDPIScale = 1.0f);
    ENGINE_API ~FCanvas();

    // 混合模式 转为 简单的混合模式。
    ENGINE_API static ESimpleElementBlendMode BlendToSimpleElementBlend(EBlendMode BlendMode);

    // TODO
    ENGINE_API FBatchedElements* GetBatchedElements(EElementType InElementType, FBatchedElementParameters* InBatchedElementParameters = NULL, const FTexture* Texture = NULL, ESimpleElementBlendMode BlendMode = SE_BLEND_MAX, const FDepthFieldGlowInfo& GlowInfo = FDepthFieldGlowInfo(), bool bApplyDPIScale = true);

    // Add Item
    ENGINE_API void AddTileRenderItem(float X, float Y, float SizeX, float SizeY, float U, float V, float SizeU, float SizeV, const FMaterialRenderProxy* MaterialRenderProxy, FHitProxyId HitProxyId, bool bFreezeTime, FColor InColor);
    ENGINE_API void AddTriangleRenderItem(const FCanvasUVTri& Tri, const FMaterialRenderProxy* MaterialRenderProxy, FHitProxyId HitProxyId, bool bFreezeTime);

    ENGINE_API void Flush_RenderThread(FRHICommandListImmediate& RHICmdList, bool bForce = false);
    ENGINE_API void Flush_RenderThread(FRDGBuilder& GraphBuilder, bool bForce = false);
    ENGINE_API void Flush_GameThread(bool bForce = false);

    ENGINE_API void PushRelativeTransform(const FMatrix& Transform);
    ENGINE_API void PushAbsoluteTransform(const FMatrix& Transform);
    ENGINE_API void PopTransform();
    ENGINE_API void SetBaseTransform(const FMatrix& Transform);

    ENGINE_API static FMatrix CalcBaseTransform2D(uint32 ViewSizeX, uint32 ViewSizeY);
    ENGINE_API static FMatrix CalcBaseTransform3D(uint32 ViewSizeX, uint32 ViewSizeY, float fFOV, float NearPlane);
    ENGINE_API static FMatrix CalcViewMatrix(uint32 ViewSizeX, uint32 ViewSizeY, float fFOV);
    ENGINE_API static FMatrix CalcProjectionMatrix(uint32 ViewSizeX, uint32 ViewSizeY, float fFOV, float NearPlane);

    FMatrix GetTransform() const;
    const FMatrix& GetBottomTransform() const;
    const FMatrix& GetFullTransform() const;
    ENGINE_API void CopyTransformStack(const FCanvas& Copy);

    ENGINE_API void SetRenderTarget_GameThread(FRenderTarget* NewRenderTarget);

    ENGINE_API void SetRenderTargetRect(const FIntRect& ViewRect);
    ENGINE_API void SetRenderTargetScissorRect( const FIntRect& ScissorRect );
    void SetRenderTargetDirty(bool bDirty);
    ENGINE_API void SetHitProxy(HHitProxy* HitProxy);

    // HitProxy Accessors.  
    FHitProxyId GetHitProxyId() const { return CurrentHiProxy ? CurrentHitProxy->Id : FHitProxyId(); }
    FHitProxyConsumer* GetHitProxyConsumer() const { return HitProxyConsumer; }
    bool IsHitTesting() const { return HitProxyConsumer != NULL; }

    // sort key
    void PushDepthSortKey(int32 InSortKey);
    int32 PopDepthSortKey();
    int32 TopDepthSortKey();

    bool HasBatchesToRender() const;
    void ClearBatchesToRender();

public:
    float AlphaModulate;

    /** Entry for the transform stack which stores a matrix and its CRC for faster comparisons */
    class FTransformEntry
    {
    public:
        FTransformEntry(const FMatrix& InMatrix)
            : Matrix(InMatrix)
        {
            MatrixCRC = FCrc::MemCrc_DEPRECATED(&Matrix, sizeof(FMatrix));
        }
        FORCEINLINE void SetMatrix(const FMatrix& InMatrix)
        {
            Matrix = InMatrix;
            MatrixCRC = FCrc::MemCrc_DEPRECATED(&Matrix, sizeof(FMatrix));
        }
        FORCEINLINE const FMatrix& GetMatrix() const
        {
            return Matrix;
        }
        FORCEINLINE uint32 GetMatrixCRC() const
        {
            return MatrixCRC;
        }
    private:
        FMatrix Matrix;
        uint32 MatrixCRC;
    };

    /** returns the transform stack */
    FORCEINLINE const TArray<FTransformEntry>& GetTransformStack() const
    {
        return TransformStack;
    }
    FORCEINLINE const FIntRect& GetViewRect() const
    {
        return ViewRect;
    }

    FORCEINLINE void SetScaledToRenderTarget(bool bScale = true)
    {
        bScaledToRenderTarget = bScale;
    }
    FORCEINLINE bool IsScaledToRenderTarget() const { return bScaledToRenderTarget; }

    FORCEINLINE void SetStereoRendering(bool bStereo = true)
    {
        bStereoRendering = bStereo;
    }
    FORCEINLINE bool IsStereoRendering() const { return bStereoRendering; }

    FORCEINLINE void SetUseInternalTexture(const bool bInUseInternalTexture)
    {
        bUseInternalTexture = bInUseInternalTexture;
    }

    FORCEINLINE bool IsUsingInternalTexture() const { return bUseInternalTexture; }

    FORCEINLINE void SetParentCanvasSize(FIntPoint InParentSize)
    {
        ParentSize = InParentSize;
    }

    FORCEINLINE FIntPoint GetParentCanvasSize() const { return ParentSize; }

    float GetDPIScale() const { return  bStereoRendering ? 1.0f : DPIScale; }

public: 

    /** 
     * Draw a CanvasItem
     *
     * @param Item          Item to draw
     */
    ENGINE_API void DrawItem(FCanvasItem& Item);

    /**
     * Draw a CanvasItem at the given coordinates
     *
     * @param Item          Item to draw
     * @param InPosition    Position to draw item
     */
    ENGINE_API void DrawItem(FCanvasItem& Item, const FVector2D& InPosition);

    /** 
     * Draw a CanvasItem at the given coordinates
     *
     * @param Item          Item to draw
     * @param X             X Position to draw item
     * @param Y             Y Position to draw item
     */
    ENGINE_API void DrawItem(FCanvasItem& Item, float X, float Y);

    /**
    * Clear the canvas
    *
    * @param    Color       Color to clear with.
    */
    ENGINE_API void Clear(const FLinearColor& Color);

    /** 
    * Draw arbitrary aligned rectangle.
    *
    * @param X - X position to draw tile at
    * @param Y - Y position to draw tile at
    * @param SizeX - Width of tile
    * @param SizeY - Height of tile
    * @param U - Horizontal position of the upper left corner of the portion of the texture to be shown(texels)
    * @param V - Vertical position of the upper left corner of the portion of the texture to be shown(texels)
    * @param SizeU - The width of the portion of the texture to be drawn. This value is in texels. 
    * @param SizeV - The height of the portion of the texture to be drawn. This value is in texels. 
    * @param Color - tint applied to tile
    * @param Texture - Texture to draw
    * @param AlphaBlend - true to alphablend
    */
    ENGINE_API void DrawTile(double X, double Y, double SizeX, double SizeY, float U, float V, float SizeU, float SizeV, const FLinearColor& Color, const FTexture* Texture = NULL, bool AlphaBlend = true);
    ENGINE_API void DrawTile(double X, double Y, double SizeX, double SizeY, float U, float V, float SizeU, float SizeV, const FLinearColor& Color, const FTexture* Texture, ESimpleElementBlendMode BlendMode);

    /** 
    * Draw an string centered on given location. 
    * This function is being deprecated. a FCanvasTextItem should be used instead.
    * 
    * @param StartX - X point
    * @param StartY - Y point
    * @param Text - Text to draw
    * @param Font - Font to use
    * @param Color - Color of the text
    * @param ShadowColor - Shadow color to draw underneath the text (ignored for distance field fonts)
    * @return total size in pixels of text drawn
    */
    ENGINE_API int32 DrawShadowedString(double StartX, double StartY, const TCHAR* Text, const UFont* Font, const FLinearColor& Color, const FLinearColor& ShadowColor = FLinearColor::Black );

    ENGINE_API int32 DrawShadowedText(double StartX, double StartY, const FText& Text, const UFont* Font, const FLinearColor& Color, const FLinearColor& ShadowColor = FLinearColor::Black );

    ENGINE_API void WrapString( FTextSizingParameters& Parameters, const float InCurX, const TCHAR* const pText, TArray<FWrappedStringElement>& out_Lines, FCanvasWordWrapper::FWrappedLineData* const OutWrappedLineData = nullptr);

    ENGINE_API void DrawNGon(const FVector2D& Center, const FColor& Color, int32 NumSides, float Radius);

    /** 
    * Contains all of the batched elements that need to be rendered at a certain depth sort key
    */
    class FCanvasSortElement
    {
    public:
        // ...

        bool operator==(const FCanvasSortElement& Other) const { return DepthSortKey == Other.DepthSortKey; }
        int32 DepthSortKey;

        /** list of batches that should be rendered at this sort key level */
        TArray<class FCanvasBaseRenderItem*> RenderBatchArray;
    };

    /** Batched canvas elements to be sorted for rendering. Sort order is back-to-front */
    TArray<FCanvasSortElement> SortedElements;
    /** Map from sortkey to array index of SortedElements for faster lookup of existing entries */
    TMap<int32,int32> SortedElementLookupMap;

    /** Store index of last Element off to avoid semi expensive Find() */
    int32 LastElementIndex;

    /**
    * Get the sort element for the given sort key. Allocates a new entry if one does not exist
    *
    * @param DepthSortKey - the key used to find the sort element entry
    * @return sort element entry
    */
    ENGINE_API FCanvasSortElement& GetSortElement(int32 DepthSortKey);

    friend class FCanvasRenderContext;
    friend class FCanvasRenderThreadScope;
};

[04-08]

FBatchedElements

批处理元素

/** Batched elements for later rendering. */
class FBatchedElements
{
public:

    // 向批处理添加 xxx
    ENGINE_API void AddLine(const FVector& Start,const FVector& End,const FLinearColor& Color,FHitProxyId HitProxyId, float Thickness = 0.0f, float DepthBias = 0.0f, bool bScreenSpace = false);
    ENGINE_API void AddTranslucentLine(const FVector& Start, const FVector& End, const FLinearColor& Color, FHitProxyId HitProxyId, float Thickness = 0.0f, float DepthBias = 0.0f, bool bScreenSpace = false);
    ENGINE_API void AddPoint(const FVector& Position,float Size,const FLinearColor& Color,FHitProxyId HitProxyId);
    ENGINE_API int32 AddVertex(const FVector4& InPosition, const FVector2D& InTextureCoordinate, const FLinearColor& InColor, FHitProxyId HitProxyId);
    ENGINE_API int32 AddVertexf(const FVector4f& InPosition, const FVector2f& InTextureCoordinate, const FLinearColor& InColor, FHitProxyId HitProxyId);
    ENGINE_API void AddTriangle(int32 V0,int32 V1,int32 V2,const FTexture* Texture,EBlendMode BlendMode);
    ENGINE_API void AddTriangle(int32 V0, int32 V1, int32 V2, const FTexture* Texture, ESimpleElementBlendMode BlendMode, const FDepthFieldGlowInfo& GlowInfo = FDepthFieldGlowInfo());
    ENGINE_API void AddTriangle(int32 V0,int32 V1,int32 V2,FBatchedElementParameters* BatchedElementParameters,ESimpleElementBlendMode BlendMode);
    ENGINE_API void AddTriangleExtensive(int32 V0,int32 V1,int32 V2,FBatchedElementParameters* BatchedElementParameters,const FTexture* Texture,ESimpleElementBlendMode BlendMode, const FDepthFieldGlowInfo& GlowInfo = FDepthFieldGlowInfo());

    // 添加 预留空间
    ENGINE_API void AddReserveTriangles(int32 NumMeshTriangles,const FTexture* Texture,ESimpleElementBlendMode BlendMode);
    ENGINE_API void ReserveTriangles(int32 NumMeshTriangles,const FTexture* Texture,ESimpleElementBlendMode BlendMode);
    ENGINE_API void AddReserveVertices(int32 NumMeshVerts);
    ENGINE_API void ReserveVertices(int32 NumMeshVerts);
    ENGINE_API void AddReserveLines(int32 NumLines, bool bDepthBiased = false, bool bThickLines = false);

    ENGINE_API void AddSprite(
        const FVector& Position,
        float SizeX,
        float SizeY,
        const FTexture* Texture,
        const FLinearColor& Color,
        FHitProxyId HitProxyId,
        float U,
        float UL,
        float V,
        float VL,
        uint8 BlendMode,
        float OpacityMaskRefVal
        );

    // 绘制批处理的元素
    ENGINE_API bool Draw(FRHICommandList& RHICmdList, const FMeshPassProcessorRenderState& DrawRenderState, ERHIFeatureLevel::Type FeatureLevel, const FSceneView& View, bool bHitTesting, float Gamma = 1.0f, EBlendModeFilter::Type Filter = EBlendModeFilter::All) const;

    // 创建FSceneView 代理
    static ENGINE_API FSceneView CreateProxySceneView(const FMatrix& ProjectionMatrix, const FIntRect& ViewRect);

    // 有没有东西要画
    FORCEINLINE bool HasPrimsToDraw() const
    {
        return( LineVertices.Num() || Points.Num() || Sprites.Num() || MeshElements.Num() || ThickLines.Num() || WireTris.Num() > 0 );
    }
private:
    // 画点
    ENGINE_API void DrawPointElements(FRHICommandList& RHICmdList, const FMatrix& Transform, const uint32 ViewportSizeX, const uint32 ViewportSizeY, const FVector& CameraX, const FVector& CameraY) const;

    // 下面是存储批处理元素的Array。点啊,瓦片呀,精灵呀

    TArray<FSimpleElementVertex> LineVertices;

    struct FBatchedPoint
    {
        FVector Position;
        float Size;
        FColor Color;
        FColor HitProxyColor;
    };
    TArray<FBatchedPoint> Points;

    struct FBatchedWireTris
    {
        float DepthBias;
    };
    TArray<FBatchedWireTris> WireTris;

    mutable TResourceArray<FSimpleElementVertex> WireTriVerts;

    struct FBatchedThickLines
    {
        FVector Start;
        FVector End;
        float Thickness;
        FLinearColor Color;
        FColor HitProxyColor;
        float DepthBias;
        uint32 bScreenSpace;
    };
    TArray<FBatchedThickLines> ThickLines;

    struct FBatchedSprite
    {
        FVector Position;
        float SizeX;
        float SizeY;
        const FTexture* Texture;
        FLinearColor Color;
        FColor HitProxyColor;
        float U;
        float UL;
        float V;
        float VL;
        float OpacityMaskRefVal;
        uint8 BlendMode;
    };
    TArray<FBatchedSprite> Sprites;

    struct FBatchedMeshElement
    {
        /** starting index in vertex buffer for this batch */
        uint32 MinVertex;
        /** largest vertex index used by this batch */
        uint32 MaxVertex;
        /** index buffer for triangles */
        TArray<uint16,TInlineAllocator<6> > Indices;
        /** all triangles in this batch draw with the same texture */
        const FTexture* Texture;
        /** Parameters for this batched element */
        TRefCountPtr<FBatchedElementParameters> BatchedElementParameters;
        /** all triangles in this batch draw with the same blend mode */
        ESimpleElementBlendMode BlendMode;
        /** all triangles in this batch draw with the same depth field glow (depth field blend modes only) */
        FDepthFieldGlowInfo GlowInfo;
    };

    /** Max number of mesh index entries that will fit in a DrawPriUP call */
    int32 MaxMeshIndicesAllowed;
    /** Max number of mesh vertices that will fit in a DrawPriUP call */
    int32 MaxMeshVerticesAllowed;

    TArray<FBatchedMeshElement,TInlineAllocator<2> > MeshElements;
    TArray<FSimpleElementVertex,TInlineAllocator<4> > MeshVertices;

    /**
     * Sets the appropriate vertex and pixel shader.
     */
    ENGINE_API void PrepareShaders(
        FRHICommandList& RHICmdList,
        FGraphicsPipelineStateInitializer& GraphicsPSOInit,
        uint32 StencilRef,
        ERHIFeatureLevel::Type FeatureLevel,
        ESimpleElementBlendMode BlendMode,
        const FRelativeViewMatrices& ViewMatrices,
        FBatchedElementParameters* BatchedElementParameters,
        const FTexture* Texture,
        bool bHitTesting,
        float Gamma,
        const FDepthFieldGlowInfo* GlowInfo = nullptr,
        const FSceneView* View = nullptr,
        float OpacityMaskRefVal = .5f
        ) const;

回到

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 ) );

    //。。。
}

这里初始化了一个 FSceneViewFamilyContext 一组视图对象,并根据情况设置了这个变量的属性

继续

    FSceneViewExtensionContext ViewExtensionContext(InViewport);
    ViewExtensionContext.bStereoEnabled = true;
    ViewFamily.ViewExtensions = GEngine->ViewExtensions->GatherActiveExtensions(ViewExtensionContext);

    for (auto ViewExt : ViewFamily.ViewExtensions)
    {
        ViewExt->SetupViewFamily(ViewFamily);
    }

这里for的 ViewExtensions 是
TArray<TSharedRef<class ISceneViewExtension, ESPMode::ThreadSafe> > ViewExtensions;
他会寻找所有继承了 ISceneViewExtension 的类,然后 SetupViewFamily

比如 这个类就会在这里被遍历
class FLandscapeSceneViewExtension : public FSceneViewExtensionBase
class FSceneViewExtensionBase : public ISceneViewExtension, public TSharedFromThis<FSceneViewExtensionBase, ESPMode::ThreadSafe>
我们可以参考这种方式把自己的一些渲染扩展嵌入进去

一些变量,并且继续在赋值 ViewFamily

    EViewModeIndex CurrentViewMode = GetViewMode();
    ViewFamily.ViewMode = CurrentViewMode;

    const bool bVisualizeBufferEnabled = CurrentViewMode == VMI_VisualizeBuffer && CurrentBufferVisualizationMode != NAME_None;
    const bool bRayTracingDebugEnabled = CurrentViewMode == VMI_RayTracingDebug && CurrentRayTracingDebugVisualizationMode != NAME_None;
    const bool bVisualizeGPUSkinCache = CurrentViewMode == VMI_VisualizeGPUSkinCache && CurrentGPUSkinCacheVisualizationMode != NAME_None;
    const bool bCanDisableTonemapper = bVisualizeBufferEnabled || bVisualizeGPUSkinCache || (bRayTracingDebugEnabled && !FRayTracingDebugVisualizationMenuCommands::DebugModeShouldBeTonemapped(CurrentRayTracingDebugVisualizationMode));

    // 设置flag
    EngineShowFlagOverride(ESFIM_Editor, ViewFamily.ViewMode, ViewFamily.EngineShowFlags, bCanDisableTonemapper);
    EngineShowFlagOrthographicOverride(IsPerspective(), ViewFamily.EngineShowFlags);
    UpdateLightingShowFlags( ViewFamily.EngineShowFlags );

还在赋值 ViewFamily

    ViewFamily.ExposureSettings = ExposureSettings;

    ViewFamily.LandscapeLODOverride = LandscapeLODOverride;

    // Setup the screen percentage and upscaling method for the view family.
    {
        checkf(ViewFamily.GetScreenPercentageInterface() == nullptr,
            TEXT("Some code has tried to set up an alien screen percentage driver, that could be wrong if not supported very well by the RHI."));

        // If not doing VR rendering, apply DPI derived resolution fraction even if show flag is disabled
        if (!bStereoRendering && SupportsLowDPIPreview() && IsLowDPIPreview() && ViewFamily.SupportsScreenPercentage())
        {
            ViewFamily.SecondaryViewFraction = GetDPIDerivedResolutionFraction();
        }
    }
    FSceneView* View = nullptr;

    // Stereo rendering
    // 如果是VR就要渲染两张图
    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);

        // 。。。
    }

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

用 ViewFamily 获得一个 FSceneView
注意断点调试的时候,实际跑的是子类的 FLevelEditorViewportClient 关卡编辑器的viewport client

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);

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    const bool bStereoRendering = StereoViewIndex != INDEX_NONE; // 立体渲染,VR

    FSceneViewInitOptions ViewInitOptions;

    // ...
}

FSceneViewInitOptions

看 FSceneViewInitOptions

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

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
};

FSceneViewInitOptions

// 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

    // ...
};

描述视图所属的玩家,鼠标,视图的各种属性,比如背景,位置旋转FOV,描述如何渲染这个视图的bool

回到这里

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

    FSceneViewInitOptions ViewInitOptions;

    // 。。。

    FSceneView* View = new FSceneView(ViewInitOptions);
}

可以注意到这一段都是为了往 ViewInitOptions 填参数,然后最后构造一个view

FSceneView* FEditorViewportClient::CalcSceneView(FSceneViewFamily* ViewFamily, const int32 StereoViewIndex)
{
    FSceneViewInitOptions ViewInitOptions;

    // FViewportCameraTransform view Location, Rotation, LookAt, StartLocation,可以看出来这是描述摄像机的
    FViewportCameraTransform& ViewTransform = GetViewTransform();

    // ELevelViewportType 视图类型 ,XY, XZ, YZ, 透视
    const ELevelViewportType EffectiveViewportType = GetViewportType();

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

    // 。。。

}

看这个结构,主要 FMinimalViewInfo ViewInfo; 内容比较多

/** 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;
};

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 */

    // ...
};
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相关

    // 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();
    }
    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();
    }

通过 WorldSettings 设置 WorldToMetersScale

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

计算透视和正交矩阵


    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);
    }
    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 )
            {
                int32 X = 0;
                int32 Y = 0;
                uint32 SizeX = ViewportSize.X;
                uint32 SizeY = ViewportSize.Y;
                GEngine->StereoRenderingDevice->AdjustViewRect( StereoViewIndex, X, Y, SizeX, SizeY );
                const FIntRect StereoViewRect = FIntRect( X, Y, X + SizeX, Y + SizeY );
                ViewInitOptions.SetViewRectangle( StereoViewRect );

                GEngine->StereoRenderingDevice->CalculateStereoViewOffset( StereoViewIndex, ModifiedViewRotation, ViewInitOptions.WorldToMetersScale, ViewInitOptions.ViewOrigin );
            }

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

            // 。。。

        }
        else
        {
            // 。。。
        }

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

看 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 先不看
            {
                // @todo vreditor: bConstrainAspectRatio is ignored in this path, as it is in the game client as well currently
                // Let the stereoscopic rendering device handle creating its own projection matrix, as needed
                ViewInitOptions.ProjectionMatrix = GEngine->StereoRenderingDevice->GetStereoProjectionMatrix(StereoViewIndex);
                ViewInitOptions.StereoPass = GEngine->StereoRenderingDevice->GetViewPassForIndex(bStereoRendering, StereoViewIndex);
            }
            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)
    )
{ }
    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)
{
    // Create the projection matrix (and possibly constrain the view rectangle)
    if (ViewInfo.bConstrainAspectRatio)
    {
        // Enforce a particular aspect ratio for the render of the scene. 
        // Results in black bars at top/bottom etc.
        InOutProjectionData.SetConstrainedViewRectangle(ConstrainedViewRectangle);

        InOutProjectionData.ProjectionMatrix = ViewInfo.CalculateProjectionMatrix();
    }
    else
    {
        float XAxisMultiplier;
        float YAxisMultiplier;

        const FIntRect& ViewRect = InOutProjectionData.GetViewRect();
        const int32 SizeX = ViewRect.Width();
        const int32 SizeY = ViewRect.Height();

        // Get effective aspect ratio axis constraint.
        AspectRatioAxisConstraint = ViewInfo.AspectRatioAxisConstraint.Get(AspectRatioAxisConstraint);

        // If x is bigger, and we're respecting x or major axis, AND mobile isn't forcing us to be Y axis aligned
        const bool bMaintainXFOV = 
            ((SizeX > SizeY) && (AspectRatioAxisConstraint == AspectRatio_MajorAxisFOV)) || 
            (AspectRatioAxisConstraint == AspectRatio_MaintainXFOV) || 
            (ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic);
        if (bMaintainXFOV)
        {
            // If the viewport is wider than it is tall
            XAxisMultiplier = 1.0f;
            YAxisMultiplier = SizeX / (float)SizeY;
        }
        else
        {
            // If the viewport is taller than it is wide
            XAxisMultiplier = SizeY / (float)SizeX;
            YAxisMultiplier = 1.0f;
        }

        float MatrixHalfFOV;
        if (!bMaintainXFOV && ViewInfo.AspectRatio != 0.f && !CVarUseLegacyMaintainYFOV.GetValueOnGameThread())
        {
            // The view-info FOV is horizontal. But if we have a different aspect ratio constraint, we need to
            // adjust this FOV value using the aspect ratio it was computed with, so we that we can compute the
            // complementary FOV value (with the *effective* aspect ratio) correctly.
            const float HalfXFOV = FMath::DegreesToRadians(FMath::Max(0.001f, ViewInfo.FOV) / 2.f);
            const float HalfYFOV = FMath::Atan(FMath::Tan(HalfXFOV) / ViewInfo.AspectRatio);
            MatrixHalfFOV = HalfYFOV;
        }
        else
        {
            // Avoid divide by zero in the projection matrix calculation by clamping FOV.
            // Note the division by 360 instead of 180 because we want the half-FOV.
            MatrixHalfFOV = FMath::Max(0.001f, ViewInfo.FOV) * (float)UE_PI / 360.0f;
        }

        if (ViewInfo.ProjectionMode == ECameraProjectionMode::Orthographic)
        {
            const float OrthoWidth = ViewInfo.OrthoWidth / 2.0f * XAxisMultiplier;
            const float OrthoHeight = (ViewInfo.OrthoWidth / 2.0f) / YAxisMultiplier;

            const float NearPlane = ViewInfo.OrthoNearClipPlane;
            const float FarPlane = ViewInfo.OrthoFarClipPlane;

            const float ZScale = 1.0f / (FarPlane - NearPlane);
            const float ZOffset = -NearPlane;

            InOutProjectionData.ProjectionMatrix = FReversedZOrthoMatrix(
                OrthoWidth, 
                OrthoHeight,
                ZScale,
                ZOffset
                );      
        }
        else
        {
            const float ClippingPlane = ViewInfo.GetFinalPerspectiveNearClipPlane();
            InOutProjectionData.ProjectionMatrix = FReversedZPerspectiveMatrix(
                MatrixHalfFOV,
                MatrixHalfFOV,
                XAxisMultiplier,
                YAxisMultiplier,
                ClippingPlane,
                ClippingPlane
            );
        }
    }

    if (!ViewInfo.OffCenterProjectionOffset.IsZero())
    {
        const float Left = -1.0f + ViewInfo.OffCenterProjectionOffset.X;
        const float Right = Left + 2.0f;
        const float Bottom = -1.0f + ViewInfo.OffCenterProjectionOffset.Y;
        const float Top = Bottom + 2.0f;
        InOutProjectionData.ProjectionMatrix.M[2][0] = (Left + Right) / (Left - Right);
        InOutProjectionData.ProjectionMatrix.M[2][1] = (Bottom + Top) / (Bottom - Top);
    }
}

===END===

保护性设置

    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运行敲一下,看下效果就知道了

    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);

然后 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;

CalcSceneView End

回退到这里了
void FEditorViewportClient::Draw(FViewport InViewport, FCanvas Canvas)

上面一堆是对于 FSceneViewFamilyContext ViewFamily 做初始化
然后遍历 NumViews 根据是否VR,VR有两。
CalcSceneView 我们创建了 FSceneView 然后继续

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

    // 上面一堆是对于 FSceneViewFamilyContext ViewFamily 做初始化

    // 然后创建 View
    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);

        // 。。。
    }
}

SetupViewForRendering

void FLevelEditorViewportClient::SetupViewForRendering( FSceneViewFamily& ViewFamily, FSceneView& View )
{
    FEditorViewportClient::SetupViewForRendering( ViewFamily, View );

    // 。。。
}

看父类

void FEditorViewportClient::SetupViewForRendering(FSceneViewFamily& ViewFamily, FSceneView& View)
{
    // View 一些参数设置,像素查看器的设置。 略

}
void FLevelEditorViewportClient::SetupViewForRendering( FSceneViewFamily& ViewFamily, FSceneView& View )
{
    FEditorViewportClient::SetupViewForRendering( ViewFamily, View );

    ViewFamily.bDrawBaseInfo = bDrawBaseInfo;
    ViewFamily.bCurrentlyBeingEdited = bCurrentlyEditingThroughMovementWidget;

    // Don't use fading or color scaling while we're in light complexity mode, since it may change the colors!
    // 复杂灯光情况下的设置
    if(!ViewFamily.EngineShowFlags.LightComplexity)
    {
        if(bEnableFading)
        {
            View.OverlayColor = FadeColor;
            View.OverlayColor.A = FMath::Clamp(FadeAmount, 0.f, 1.f);
        }

        if(bEnableColorScaling)
        {
            View.ColorScale = FLinearColor(FVector3f{ ColorScale });
        }
    }

    // 拖拽
    TSharedPtr<FDragDropOperation> DragOperation = FSlateApplication::Get().GetDragDroppingContent();
    if (!(DragOperation.IsValid() && DragOperation->IsOfType<FBrushBuilderDragDropOp>()))
    {
        // Hide the builder brush when not in geometry mode
        ViewFamily.EngineShowFlags.SetBuilderBrush(false);
    }

    // Update the listener.
    // 播放的时候是否要播音乐
    if (bHasAudioFocus)
    {
        UpdateAudioListener(View);
    }
}

回来

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

    // 上面一堆是对于 FSceneViewFamilyContext ViewFamily 做初始化

    // 然后创建 View
    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

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);
        }
    }

UWorld::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;
}

这里其实在进入 UpdateSingleViewportClient 之前,就已经 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;
    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);
    }

看 GTWork

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.
        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);

        for (UActorComponent* Component : DeferredUpdates)
        {
            Component->DoDeferredRenderUpdates_Concurrent();
        }
    };

然后对刚刚的array执行 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();
        }
    }
}

比如

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

    // 。。。
}

UPrimitiveComponent 叫基元组件

包围盒从这里开始看。

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
                        );
                    }
                );
            }
        }
    }

这个必须要在渲染线程里面执行

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);
    }

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());
    }
}

回到 void UWorld::SendAllEndOfFrameUpdates()

    else
    {
        GTWork();
        ParallelFor(LocalComponentsThatNeedEndOfFrameUpdate.Num(), ParallelWork);
    }

看 ParallelFor,用于在多线程中执行相同的任务

多线程中执行一些延迟渲染的更新任务

    auto ParallelWork = [IsUsingParallelNotifyEvents](int32 Index)
    {
        TRACE_CPUPROFILER_EVENT_SCOPE(DeferredRenderUpdates);
        FOptionalTaskTagScope Scope(ETaskTag::EParallelGameThread);
#if WITH_EDITOR
        if (!IsInParallelGameThread() && IsInGameThread() && IsUsingParallelNotifyEvents)
        {
            FObjectCacheEventSink::ProcessQueuedNotifyEvents();
        }
#endif
        UActorComponent* NextComponent = LocalComponentsThatNeedEndOfFrameUpdate[Index];
        if (NextComponent)
        {
            if (NextComponent->IsRegistered() && !NextComponent->IsTemplate() && IsValid(NextComponent))
            {
                NextComponent->DoDeferredRenderUpdates_Concurrent();
            }
            check(!IsValid(NextComponent) || NextComponent->GetMarkedForEndOfFrameUpdateState() == EComponentMarkedForEndOfFrameUpdateState::Marked);
            FMarkComponentEndOfFrameUpdateState::Set(NextComponent, INDEX_NONE, EComponentMarkedForEndOfFrameUpdateState::Unmarked);
        }
    };

DeferredUpdateRenderState

材质参数
然后 结束 UWorld::SendAllEndOfFrameUpdates

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

    LocalComponentsThatNeedEndOfFrameUpdate.Reset();

    EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates);
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

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

UniformBuffer 统一buff,可以简单理解为常量缓冲区

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);
}

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

RHICreateUniformBuffer

可以看下D3D11的

FUniformBufferRHIRef FD3D12DynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
{
    SCOPE_CYCLE_COUNTER(STAT_D3D12UpdateUniformBufferTime);

    if (Contents && Validation == EUniformBufferValidation::ValidateResources)
    {
        ValidateShaderParameterResourcesRHI(Contents, *Layout);
    }

    // 。。。

首先 ValidateShaderParameterResourcesRHI 会验证渲染参数

void ValidateShaderParameterResourcesRHI(const void* Contents, const FRHIUniformBufferLayout& Layout)
{
    for (int32 Index = 0, Count = Layout.Resources.Num(); Index < Count; ++Index)
    {
        const auto Parameter = Layout.Resources[Index];

        FRHIResource* Resource = GetShaderParameterResourceRHI(Contents, Parameter.MemberOffset, Parameter.MemberType);

        const bool bSRV =
            Parameter.MemberType == UBMT_SRV ||
            Parameter.MemberType == UBMT_RDG_TEXTURE_SRV ||
            Parameter.MemberType == UBMT_RDG_BUFFER_SRV;

        // Allow null SRV's in uniform buffers for feature levels that don't support SRV's in shaders
        if (GMaxRHIFeatureLevel <= ERHIFeatureLevel::ES3_1 && bSRV)
        {
            continue;
        }

        checkf(Resource, TEXT("Null resource entry in uniform buffer parameters: %s.Resources[%u], ResourceType 0x%x."), *Layout.GetDebugName(), Index, Parameter.MemberType);
    }
}

看看 FRHIUniformBufferLayout,在便利这个资源

/** The layout of a uniform buffer in memory. */
struct FRHIUniformBufferLayout : public FRHIResource
{

    /** The list of all resource inlined into the shader parameter structure. */
    const TArray<FRHIUniformBufferResource> Resources; // 统一buff的资源

然后 GetShaderParameterResourceRHI 来获取 FRHIResource
这里的 Contents就是,这里的 ParameterData。一路传进来的

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;
}

遍历Resources,通过 GetShaderParameterResourceRHI 创建 FRHIResource。
Contents 是材质参数

这个 Parameter.MemberType 是这样的

/** The base type of a value in a shader parameter structure. */
enum EUniformBufferBaseType : uint8
{
    UBMT_INVALID,

    // Invalid type when trying to use bool, to have explicit error message to programmer on why
    // they shouldn't use bool in shader parameter structures.
    UBMT_BOOL,

    // Parameter types.
    UBMT_INT32,
    UBMT_UINT32,
    UBMT_FLOAT32,

    // RHI resources not tracked by render graph.
    UBMT_TEXTURE,
    UBMT_SRV,
    UBMT_UAV,
    UBMT_SAMPLER,

    // Resources tracked by render graph.
    UBMT_RDG_TEXTURE,
    UBMT_RDG_TEXTURE_ACCESS,
    UBMT_RDG_TEXTURE_ACCESS_ARRAY,
    UBMT_RDG_TEXTURE_SRV,
    UBMT_RDG_TEXTURE_UAV,
    UBMT_RDG_BUFFER_ACCESS,
    UBMT_RDG_BUFFER_ACCESS_ARRAY,
    UBMT_RDG_BUFFER_SRV,
    UBMT_RDG_BUFFER_UAV,
    UBMT_RDG_UNIFORM_BUFFER,

    // Nested structure.
    UBMT_NESTED_STRUCT,

    // Structure that is nested on C++ side, but included on shader side.
    UBMT_INCLUDED_STRUCT,

    // GPU Indirection reference of struct, like is currently named Uniform buffer.
    UBMT_REFERENCED_STRUCT,

    // Structure dedicated to setup render targets for a rasterizer pass.
    UBMT_RENDER_TARGET_BINDING_SLOTS,

    EUniformBufferBaseType_Num,
    EUniformBufferBaseType_NumBits = 5,
};

然后是特性级别的判断。

回到

FUniformBufferRHIRef FD3D11DynamicRHI::RHICreateUniformBuffer(const void* Contents, const FRHIUniformBufferLayout* Layout, EUniformBufferUsage Usage, EUniformBufferValidation Validation)
{
    if (Contents && Validation == EUniformBufferValidation::ValidateResources)
    {
        ValidateShaderParameterResourcesRHI(Contents, *Layout);
    }

    FD3D11UniformBuffer* NewUniformBuffer = nullptr;
    const uint32 NumBytes = Layout->ConstantBufferSize;
    if (NumBytes > 0)
    {
        // Constant buffers must also be 16-byte aligned.
        // 字节对齐校验,字节大小校验
        check(Align(NumBytes,16) == NumBytes);
        check(Align(Contents,16) == Contents);
        check(NumBytes <= D3D11_REQ_CONSTANT_BUFFER_ELEMENT_COUNT * 16);
        check(NumBytes < (1 << NumPoolBuckets));

        SCOPE_CYCLE_COUNTER(STAT_D3D11UpdateUniformBufferTime);

PRAGMA_DISABLE_DEPRECATION_WARNINGS
        if (IsPoolingEnabled() && (IsInRenderingThread() || IsInRHIThread()) && !UE::Tasks::Private::IsThreadRetractingTask()) // 启用缓冲池,渲染线程,RHI线程
PRAGMA_ENABLE_DEPRECATION_WARNINGS
        {
            const bool bAllocatedFromPool = true;

            if (ShouldNotEnqueueRHICommand()) // 是否把RHI命令加入队列
            {
                TRefCountPtr<ID3D11Buffer> UniformBufferResource = CreateAndUpdatePooledUniformBuffer( // 申请一个DX11的Buffer,并且是从缓冲池
                    Direct3DDevice.GetReference(),
                    Direct3DDeviceIMContext.GetReference(),
                    Contents,
                    NumBytes);

                NewUniformBuffer = new FD3D11UniformBuffer(this, Layout, UniformBufferResource, FRingAllocation(), bAllocatedFromPool); // 新buff
            }
            else
            {   
                NewUniformBuffer = new FD3D11UniformBuffer(this, Layout, nullptr, FRingAllocation(), bAllocatedFromPool);

                void* CPUContent = nullptr;

                if (Contents)
                {
                    CPUContent = FMemory::Malloc(NumBytes);
                    FMemory::Memcpy(CPUContent, Contents, NumBytes);
                }

                // 然后在RIH线程中
                RunOnRHIThread(
                    [NewUniformBuffer, CPUContent, NumBytes]()
                {
                    // 从 CPUContent 里面
                    NewUniformBuffer->Resource = CreateAndUpdatePooledUniformBuffer(
                        D3D11RHI_DEVICE,
                        D3D11RHI_IMMEDIATE_CONTEXT,
                        CPUContent,
                        NumBytes);
                    FMemory::Free(CPUContent);
                });
            }
        }
        else
        {
            // No pooling

            // 。。。
        }
    }
    else
    {
        // This uniform buffer contains no constants, only a resource table.
        const bool bAllocatedFromPool = false;
        NewUniformBuffer = new FD3D11UniformBuffer(this, Layout, nullptr, FRingAllocation(), bAllocatedFromPool);
    }

    // 。。。

    return NewUniformBuffer;
}
static TRefCountPtr<ID3D11Buffer> CreateAndUpdatePooledUniformBuffer(
    FD3D11Device* Device,
    FD3D11DeviceContext* Context,
    const void* Contents,
    uint32 NumBytes)
{

    // 。。。
}

如果没有缓存池

        else
        {
            // No pooling
            D3D11_BUFFER_DESC Desc;
            Desc.ByteWidth = NumBytes;
            Desc.Usage = D3D11_USAGE_DYNAMIC;
            Desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
            Desc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
            Desc.MiscFlags = 0;
            Desc.StructureByteStride = 0;

            D3D11_SUBRESOURCE_DATA ImmutableData;
            ImmutableData.pSysMem = Contents;
            ImmutableData.SysMemPitch = ImmutableData.SysMemSlicePitch = 0;

            TRefCountPtr<ID3D11Buffer> UniformBufferResource;
            VERIFYD3D11RESULT_EX(Direct3DDevice->CreateBuffer(&Desc, Contents ? &ImmutableData : nullptr,UniformBufferResource.GetInitReference()), Direct3DDevice);

            const bool bAllocatedFromPool = false;
            NewUniformBuffer = new FD3D11UniformBuffer(this, Layout, UniformBufferResource, FRingAllocation(), bAllocatedFromPool);

            INC_DWORD_STAT(STAT_D3D11NumImmutableUniformBuffers);
        }

RHIUpdateUniformBuffer

回到这里

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

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

FRHICommandListImmediate::Get().UpdateUniformBuffer(UniformBuffer, Data.GetData());

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

看看这个

void FD3D11DynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
{
    check(UniformBufferRHI);

    // cast buff,获得layout,验证是不是合法的SRV
    FD3D11UniformBuffer* UniformBuffer = ResourceCast(UniformBufferRHI);
    const FRHIUniformBufferLayout& Layout = UniformBufferRHI->GetLayout();
    ValidateShaderParameterResourcesRHI(Contents, Layout);

    const uint32 ConstantBufferSize = Layout.ConstantBufferSize;
    const int32 NumResources = Layout.Resources.Num();

    check(UniformBuffer->GetResourceTable().Num() == NumResources);

    if (RHICmdList.Bypass()) // 命令是否要绕过RHI的命令列表
    {
        UpdateUniformBufferContents(Direct3DDevice, Direct3DDeviceIMContext, UniformBuffer, Contents, ConstantBufferSize);

        // UniformBuffer->GetResourceTable() 到shader参数 做一个映射
        for (int32 Index = 0; Index < NumResources; ++Index)
        {
            const auto Parameter = Layout.Resources[Index];
            UniformBuffer->GetResourceTable()[Index] = GetShaderParameterResourceRHI(Contents, Parameter.MemberOffset, Parameter.MemberType);
        }
    }
    else
    {
        // 。。。
    }
}

拷贝数据

void UpdateUniformBufferContents(FD3D11Device* Direct3DDevice, FD3D11DeviceContext* Context, FD3D11UniformBuffer* UniformBuffer, const void* Contents, uint32 ConstantBufferSize)
{
    // Update the contents of the uniform buffer.
    if (ConstantBufferSize > 0)
    {
        // Constant buffers must also be 16-byte aligned.
        check(Align(Contents, 16) == Contents);

        D3D11_MAPPED_SUBRESOURCE MappedSubresource;
        // Discard previous results since we always do a full update
        VERIFYD3D11RESULT_EX(Context->Map(UniformBuffer->Resource.GetReference(), 0, D3D11_MAP_WRITE_DISCARD, 0, &MappedSubresource), Direct3DDevice);
        check(MappedSubresource.RowPitch >= ConstantBufferSize);
        FMemory::Memcpy(MappedSubresource.pData, Contents, ConstantBufferSize);
        Context->Unmap(UniformBuffer->Resource.GetReference(), 0);
    }
}
void FD3D11DynamicRHI::RHIUpdateUniformBuffer(FRHICommandListBase& RHICmdList, FRHIUniformBuffer* UniformBufferRHI, const void* Contents)
{
    // 。。。

    if (RHICmdList.Bypass())
    {
        // 。。。
    }
    else
    {
        FRHIResource** CmdListResources = nullptr;
        void* CmdListConstantBufferData = nullptr;

        if (NumResources > 0)
        {
            CmdListResources = (FRHIResource**)RHICmdList.Alloc(sizeof(FRHIResource*) * NumResources, alignof(FRHIResource*));

            for (int32 Index = 0; Index < NumResources; ++Index)
            {
                const auto Parameter = Layout.Resources[Index];
                CmdListResources[Index] = GetShaderParameterResourceRHI(Contents, Parameter.MemberOffset, Parameter.MemberType);
            }
        }

        if (ConstantBufferSize > 0)
        {
            CmdListConstantBufferData = (void*)RHICmdList.Alloc(ConstantBufferSize, 16);
            FMemory::Memcpy(CmdListConstantBufferData, Contents, ConstantBufferSize);
        }

        RHICmdList.EnqueueLambda([Direct3DDeviceIMContext = Direct3DDeviceIMContext.GetReference(),
            Direct3DDevice = Direct3DDevice.GetReference(),
            UniformBuffer,
            CmdListResources,
            NumResources,
            CmdListConstantBufferData,
            ConstantBufferSize](FRHICommandListBase&)
        {
            UpdateUniformBufferContents(Direct3DDevice, Direct3DDeviceIMContext, UniformBuffer, CmdListConstantBufferData, ConstantBufferSize);

            // Update resource table.
            for (int32 ResourceIndex = 0; ResourceIndex < NumResources; ++ResourceIndex)
            {
                UniformBuffer->GetResourceTable()[ResourceIndex] = CmdListResources[ResourceIndex];
            }
        });
        RHICmdList.RHIThreadFence(true);
    }
}

总结 UWorld::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 FSceneRenderer::CreateSceneRenderers(TArrayView<const FSceneViewFamily*> InViewFamilies, FHitProxyConsumer* HitProxyConsumer, TArray<FSceneRenderer*>& OutSceneRenderers)
{
    // 校验和准备参数
    OutSceneRenderers.Empty(InViewFamilies.Num());
    if (!InViewFamilies.Num())
    {
        return;
    }
    const FSceneInterface* Scene = InViewFamilies[0]->Scene;
    check(Scene);
    EShadingPath ShadingPath = Scene->GetShadingPath();

    // 对 FSceneViewFamily 创建 SceneRenderer
    for (int32 FamilyIndex = 0; FamilyIndex < InViewFamilies.Num(); FamilyIndex++)
    {
        const FSceneViewFamily* InViewFamily = InViewFamilies[FamilyIndex];

        check(InViewFamily);
        check(InViewFamily->Scene == Scene);

        if (ShadingPath == EShadingPath::Deferred)
        {
            // FDeferredShadingSceneRenderer 混个眼熟
            FDeferredShadingSceneRenderer* SceneRenderer = new FDeferredShadingSceneRenderer(InViewFamily, HitProxyConsumer);
            OutSceneRenderers.Add(SceneRenderer);
        }
        else
        {
            check(ShadingPath == EShadingPath::Mobile);
            OutSceneRenderers.Add(new FMobileSceneRenderer(InViewFamily, HitProxyConsumer));
        }

        OutSceneRenderers.Last()->bIsFirstSceneRenderer = (FamilyIndex == 0);
        OutSceneRenderers.Last()->bIsLastSceneRenderer = (FamilyIndex == InViewFamilies.Num() - 1);
    }

#if RHI_RAYTRACING
    // Update ray tracing flags.  We need to do this on the render thread, since "AnyRayTracingPassEnabled" accesses CVars which
    // are only visible on the render thread.
    if (ShadingPath == EShadingPath::Deferred)
    {
        // 射线最终相关内容
        ENQUEUE_RENDER_COMMAND(InitializeMultiRendererRayTracingFlags)(
            [SceneRenderers = CopyTemp(OutSceneRenderers)](FRHICommandList& RHICmdList)
        {
            // For multi-view-family scene rendering, we need to determine which scene renderer will update the ray tracing
            // scene.  This will be the first view family that uses ray tracing, and subsequent families that use ray
            // tracing can skip that step.  If the optimization to update the ray tracing scene once is disabled, we'll
            // update it for all scene renders.
            bool bShouldUpdateRayTracingScene = true;
            const bool bRayTracingSceneUpdateOnce = CVarRayTracingSceneUpdateOnce.GetValueOnRenderThread() != 0;

            // 遍历 SceneRenderers
            for (int32 RendererIndex = 0; RendererIndex < SceneRenderers.Num(); RendererIndex++)
            {
                FDeferredShadingSceneRenderer* SceneRenderer = (FDeferredShadingSceneRenderer*)SceneRenderers[RendererIndex];
                SceneRenderer->InitializeRayTracingFlags_RenderThread(); // 对于每一个跑初始化
                if (SceneRenderer->bAnyRayTracingPassEnabled)
                {
                    SceneRenderer->bShouldUpdateRayTracingScene = bShouldUpdateRayTracingScene;
                    if (bRayTracingSceneUpdateOnce)
                    {
                        bShouldUpdateRayTracingScene = false;
                    }
                }
            }

            // Clear flag that tracks whether ray tracing was used this frame
            SceneRenderers[0]->Scene->RayTracingScene.bUsedThisFrame = false;
        });
    }
#endif  // RHI_RAYTRACING
}

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

    if (Scene)
    {       
        // 。。。

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

        // 是否启用了命中代理
        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()); // 强制唤醒渲染线程
        }
    }
}
暂无评论

发送评论 编辑评论


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