游戏启动初始化
ue4引擎通过传入不同命令行参数来充当很多角色。它可以是编辑器,用于集成、制作、编辑、管理游戏资源。可以是ds服务器,用于管理玩家连接,同步游戏状态,校验玩家操作等
可以是游戏客户端,接受输入,呈现精美的画面,给玩家带来全方面的游戏体验。还也可以是commandlet工具,提供cook、打包、分发版本等自动化能力
// 各种平台的main,流程大同小异,注意ios, android会在这里创建UE4游戏线程, UE4游戏线程不是App的主线程 GEngineLoop.PreInit // FEngineLoop::PreInit(const TCHAR* CmdLine) 解析CmdLine,初始化是否编辑器 FEngineLoop::PreInitPreStartupScreen GLog->AddOutputDevice(GScopedStdOut.Get()) LLM(FLowLevelMemTracker::Get().ProcessCommandLine(CmdLine)) // LLM初始化 GError = FPlatformApplicationMisc::GetErrorOutputDevice() GWarn = FPlatformApplicationMisc::GetFeedbackContext() IFileManager::Get().ProcessCommandLineOptions() // 初始化文件系统 FTaskGraphInterface::Startup() // TaskGraph初始化,并根据当前机器cpu的核数来创建工作线程 FTaskGraphInterface::Get().AttachToThread(ENamedThreads::GameThread) // 将GameThread加入到TaskGraph中 FThreadStats::StartThread() LoadCoreModules: CoreUObject FQueuedThreadPool: GThreadPool GBackgroundPriorityThreadPool GLargeThreadPool创建和初始化 GLogConsole = GScopedLogConsole.Get() // 带-log参数显示出来的命令行窗口 LoadPreInitModules: Engine Renderer AnimGraphRuntime 平台RHI SlateRHIRenderer Landscape RenderCore FCsvProfiler::Get()->Init() AppLifetimeEventCapture::Init() FTracingProfiler::Get()->Init() AppInit() BeginInitTextLocalization() FPlatformMisc::PlatformPreInit() FPlatformApplicationMisc::PreInit() FConfigCacheIni::InitializeConfigSystem() ProjectManager.LoadModulesForProject: ELoadingPhase::EarliestPossible ELoadingPhase::EarliestPossible ProjectManager.CheckModuleCompatibility ProjectManager.LoadModulesForProject: ELoadingPhase::PostConfigInit FQueuedThreadPool: GIOThreadPool创建和初始化 GSystemSettings.Initialize Scalability::InitScalabilitySystem() FConfigCacheIni::LoadConsoleVariablesFromINI // 读取%EngineDir%EngineConfigConsoleVariables.ini中Startup标签下的命令并执行 FPlatformMisc::PlatformInit() FPlatformApplicationMisc::Init() FPlatformMemory::Init() InitializeStdOutDevice() GLog->AddOutputDevice(GScopedStdOut.Get()) InitGamePhys() InitEngineTextLocalization() FSlateApplication::Create() FShaderParametersMetadata::InitializeAllUniformBufferStructs() RHIInit(bHasEditorToken) RenderUtilsInit() FShaderCodeLibrary::InitForRuntime FShaderPipelineCache::Initialize CreateMoviePlayer() PostInitRHI() StartRenderingThread() FSlateApplication: InitializeRenderer LoadModulesForProject: ELoadingPhase::PostSplashScreen FPreLoadScreenManager初始化和播放 FEngineloop:PreInitPostStartupScreen GetMoviePlayer()->SetupLoadingScreenFromIni() LoadModulesForProject: ELoadingPhase::PreEarlyLoadingScreen GetMoviePlayer()->Initialize GetMoviePlayer()->PlayEarlyStartupMovies() // 播放启动视频 FPlatformMisc::PlatformHandleSplashScreen(true) FCoreDelegates::OnMountAllPakFiles.Execute(PakFolders) // 挂载pak LoadModulesForEnabledPlugins: ELoadingPhase::PreEarlyLoadingScreen FShaderCodeLibrary::OpenLibrary FShaderPipelineCache::OpenPipelineFileCache InitGameTextLocalization() LoadModule: AssetRegistry ProcessNewlyLoadedUObjects() // 注册所有UObject类和初始化缺省属性 UMaterialInterface::InitDefaultMaterials() // 加载缺省材质 IStreamingManager::Get() // 初始化texture streaming系统 FModuleManager::Get().StartProcessingNewlyLoadedObjects() LoadStartupCoreModules() // Core Networking Messaging SlateCore Slate UMG等 LoadModulesForProject: ELoadingPhase::PreLoadingScreen FPreLoadScreenManager::Get()->Initialize(*Renderer) PostInitRHI() StartRenderingThread() FPreLoadScreenManager::Get()->PlayFirstPreLoadScreen FPlatformMisc::PlatformHandleSplashScreen // 显示splash screen LoadStartupModules LoadModulesForProject: ELoadingPhase::PreDefault LoadModulesForProject: ELoadingPhase::Default LoadModulesForProject: ELoadingPhase::PostDefault if (!bIsRegularClient) // 非客户端时 GEngine = NewObject<UEngine>(GetTransientPackage(), EngineClass) // 创建GEngine GEngine->ParseCommandline() GEngine->Init(this) FCoreDelegates::OnPostEngineInit.Broadcast() LoadModulesForProject: ELoadingPhase::PostEngineInit LoadModulesForEnabledPlugins: ELoadingPhase::PostEngineInit UCommandlet* Commandlet = NewObject<UCommandlet>(GetTransientPackage(), CommandletClass) // 创建Commandlet Commandlet->AddToRoot() Commandlet->ParseParms( CommandletCommandLine ) // 执行Commadlet GetHighResScreenshotConfig().Init() // 初始化高清截图系统 InitEngineTextLocalization() InitGameTextLocalization() FPlatformApplicationMisc::PostInit() FAutomationTestFramework::Get().RunSmokeTests() PreInitContext.Cleanup() GEngineLoop.Init // FEngineLoop::Init() GEngine = NewObject<>(GetTransientPackage(), EngineClass) GetMoviePlayer()->PassLoadingScreenWindowBackToGame() FPreLoadScreenManager::Get()->PassPreLoadScreenWindowBackToGame() GEngine->ParseCommandline() InitTime() GEngine->Init() // 游戏用UGameEngine,编辑器用UEditorEngine UEngine->Init() //基类,读Engine Config,加载默认资源等| GameInstance = NewObject<UGameInstance>(this, GameInstanceClass) GameInstance->InitializeStandalone() CreateNewWorldContext(EWorldType::Game) UWorld* DummyWorld = UWorld::CreateWorld(EWorldType::Game) WorldContext->SetCurrentWorld(DummyWorld) Init() ViewportClient = NewObject<UGameViewportClient>(this, GameViewportClientClass) ViewportClient->Init(*GameInstance->GetWorldContext(), GameInstance) GameViewportWindow = CreateGameWindow() // GameViewportWindow类型为TWeakPtr<class SWindow> CreateGameViewport( ViewportClient ) CreateGameViewportWidget( GameViewportClient ) // GameViewportClient类型为UGameViewportClient* TSharedRef<SOverlay> ViewportOverlayWidgetRef = SNew( SOverlay ) TSharedRef<SGameLayerManager> GameLayerManagerRef GameLayerManagerRef = SNew(SGameLayerManager) [ViewportOverlayWidgetRef] TSharedRef<SViewport> GameViewportWidgetRef = SNew( SViewport ) [GameLayerManagerRef] SceneViewport = MakeShareable( new FSceneViewport( GameViewportClient, GameViewportWidgetRef ) ) // SceneViewport类型为TSharedPtr<class FSceneViewport> GameViewportWidgetRef->SetViewportInterface( SceneViewport.ToSharedRef() ) FSceneViewport* ViewportFrame = SceneViewport.Get() GameViewport->SetViewportFrame(ViewportFrame) // GameViewport类型为UGameViewportClient* GameViewport->GetGameLayerManager()->SetSceneViewport(ViewportFrame) ViewportClient->SetupInitialLocalPlayer ViewportGameInstance->CreateInitialPlayer UGamelnstane::CreatelLocalPlayer //这里创建玩家,分屏模式就有多个,也会创建多个Vlewport UGameViewportClient::OnViewportCreated().Broadcast() FCoreDelegates::OnPostEngineInit.Broadcast() GEngine->Start() // UGameEngine GameInstance->StartGameInstance() OnStart() // UGameInstance if (HasActivePreLoadScreenType(EPreLoadScreenTypes::EngineLoadingScreen)) FPreLoadScreenManager::Get()->SetEngineLoadingComplete(true) FPreLoadScreenManager::Get()->WaitForEngineLoadingScreenToFinish() else GetMoviePlayer()->WaitForMovieToFinish() FThreadHeartBeat::Get().Start() FShaderPipelineCache::PauseBatching() FCoreDelegates::OnFEngineLoopInitComplete.Broadcast() FShaderPipelineCache::ResumeBatching()
引擎的Shader是在游戏启动非常早的时候就会准备好,处理这块热更要注意
Init函数中会创建Viewport(游戏最终画到的地方),然后调用GameInstance->StartGameInstance()函数来开始业务逻辑的执行
游戏Tick循环
GEngineLoop.Tick() // FEngineLoop::Tick() RenderThread(渲染线程) LLM(FLowLevelMemTracker::Get().UpdateStatsPerFrame())
FThreadHeartBeat::Get().HeartBeat(true)
FGameThreadHitchHeartBeat::Get().FrameStart()
FPlatformMisc::TickHotfixables()
如果不使用渲染线程,在这里会先执行TickRenderingTickables() IConsoleManager::Get().CallAllConsoleVariableSinks() //CVar有变化,这里广播通知 FCoreDelegates::OnBeginFrame.Broadcast()
GLog->FlushThreadedLogs() GEngine->UpdateTimeAndHandleMaxTickRate()//更新CurrentTime和DeltaTime,如果设了最大帧率会在这里等待 SceneComponent在RegisterComponent时候,CreateRenderState就会创建Scenelnfo, 这里会真正更新到渲染线程 遍历WorldContext通知渲染线程UpdateScenePrimitives --------ENQUEUE_RENDER_COMMAND(UpdateScenePrimitives)--------Scene->UpdateAllPrimitiveSceneInfos(RHICmdList) ①处理RemovedLocalPrimitiveSceneInfos这里只是从数组中摘掉,收集到一起等最后再删 ②处理AddedLocalPrimitiveSceneInfos ③处理更新Transform的 FlushRuntimeVirtualTexture SetTransform AddPrimitiveToUpdateGPU,如果支持,会更新GPUScene UpdatedAttachmentRoots UpdatedCustomPrimitiveParams更新Custom数据 然后SetNeedsUniformBufferUpdate DistanceFieldSceneDataUpdates ④删掉前面收集的DeletedSceneInfos
通知渲染线程BeginFrame ------------------------ENQUEUE_RENDER_COMMAND(BeginFrame)---------------------------BeginFrameRenderThread(RHICmdList, CurrentFrameCounter) 遍历WorldContext,遁知渲染线程StartFrame ----------------ENQUEUE_RENDER_COMMAND(SceneStartFrame)---------------Scene->StartFrame(RHICmdList) VelocityData.PrimitiveSceneInfo->SetNeedsUniformBufferUpdate(true) 之前SceneComponenty移动调了Update Transform会加到VelocityData里 这里标记一下要刷UniformBuffer,紧接著10帧都会在这标记刷 GMalloc->UpdateStats()
FStats::AdvanceFrame( false, FStats::FOnAdvanceRenderingThreadStats::CreateStatic( &AdvanceRenderingThreadStatsGT )
CalculateFPSTimings() // 计算平均fps/ms 通知渲染线程ResetDeferredUpdates ----------------ENQUEUE_RENDER_COMMAND(ResetDeferredUpdates)------------------FDeferredUpdateResource::ResetNeedsUpdate() FlushPendingDeleteRHIResources_RenderThread() 这里会把渲染线程准备要删的RHIResource都删了, 如果drawcall太多或大量销毁资源这里会卡 FlushRHI drawcall太多这里就会卡着等刷完 BlockUntilGPUIdle delete... FPlatformApplicationMisc::PumpMessages(true)//处理windows消息循环 如果是IdleMode = ShouldUseIdleMode()会在这里Sleep 0.1秒 处理输入:FCoreDelegates::OnSamplingInput.Broadcast() SlateApp.PollGameDeviceState() SlateApp.FinishedInputThisFrame() MediaModule->TickPreEngine() GEngine->Tick(FApp::GetDeltaTime(), bIdleMode) StaticTick(DeltaSeconds, !!GAsyncLoadingUseFullTimeLimit, GAsyncLoadingTimeLimit / 1000.f) // 里面做等待资源加载的一些逻辑 遍历WorldContext调用 TickWorldTravel(Context, DeltaSeconds) //处理关卡加载的一些逻辑 Context.World()->Tick( LEVELTICK_All, DeltaSeconds ) BeginTickDrawEvent()通知渲染线程 --------------ENQUEUE_RENDER_COMMAND(BeginDrawEventCommand)-------------BeginDrawEvent WorldTick FWorldDelegates::OnWorldTickStart.Broadcast //Tick网络 BroadcastTickDispatch(DeltaSeconds) BroadcastPostTickDispatch() TickNetClient( DeltaSeconds ) 设了高优先级加载,并且在无缝切图中,执行ProcessAsyncLoading 设原点偏移SetNewWorldOrigin NavigationSystem->Tick(DeltaSeconds) //导航系统Tick,里面处理NavMesh FWorldDelegates::OnWorldPreActorTick.Broadcast MovieSceneSequenceTick.Broadcast 追历LevelCollections,收集需要Tick的 RunTickGroup(TG_PrePhysics) 这些是在物理线程前需要Tick的 Tick这个阶段的Actor和Component EnsureCollisionTreeIsBuilt()//这里会阻塞等物理 RunTickGroup(TG_StartPhysics) Tick这个阶段的Actor和Component RunTickGroup(TG_DuringPhysics, false) 物理线程执行的时候,这些组件Tick,不依赖物理的组件可以放这里 Tick这个阶段的Actor和Component RunTickGroup(TG_EndPhysics) Tick这个阶段的Actor和Component RunTickGroup(TG_PostPhysics) //物理做完后,需要Tick的放这里,一般这些组件依赖物理的结果 Tick这个阶段的Actor和Component if (LevelCollections[i].GetType() == ELevelCollectionType::DynamicSourceLevels) CurrentLatentActionManager.ProcessLatentActions GetTimerManager().Tick(DeltaSeconds)//业务的Timer会在这里触发 FTickableGameObject::TickObjects PlayerController->UpdateCameraManager//这里会更新相机 ProcessLevelStreamingVolumes() WorldComposition->UpdateStreamingState() RunTickGroup(TG_PostUpdateWork) Tick这个阶段的Actor和Component RunTickGroup(TG_LastDemotable) Tick这个阶段的Actor和Component FTickTaskManagerInterface::Get().EndFrame() FWorldDelegates::OnWorldPostActorTick.Broadcast FinishAsyncTrace()//异步的物理查询在这出结果 Flush网络 BroadcastTickFlush(RealDeltaSeconds) BroadcastPostTickFlush(RealDeltaSeconds) Scene->UpdateSpeedTreeWind(TimeSeconds) //这是个摇树的组件 FXSystem->Tick(DeltaSeconds)//特效或粒子系统 GEngine->ConditionalCollectGarbage()//尝试垃圾回收
GEngine->AddOnScreenDebugMessage
编辑器:UpdateCullDistanceVolumes() EndTickDrawEvent(TickDrawEvent) ---------------ENQUEUE_RENDER_COMMAND(EndDrawEventCommand)--------------EndDrawEvent 这样渲染线程就知道了卡在这之间的指令都是WorldTick的 如果非DS,关卡都加载好了,就更新 USkyLightComponent::UpdateSkyCaptureContents(Context.World()) UReflectionCaptureComponent::UpdateReflectionCaptureContents(Context.World()) UpdateTransitionType(Context.World()) BlockTillLevelStreamingCompleted(Context.World()) 服务器 Context.World()->UpdateLevelStreaming() ConditionalCommitMapChange(Context) FTickableGameObject::TickObjects(nullptr, LEVELTICK_All, false, DeltaSeconds) MediaModule->TickPostEngine() GameViewport->Tick(DeltaSeconds)//GameViewport类型为UGameViewportClient* RedrawViewports() GameViewport->Viewport->Draw(bShouldPresent)//这个FViewport继承的是RenderTarget 前面初始化时候,这里被设为了FSceneViewport EnqueueBeginRenderFrame(bShouldPresent) ----------ENQUEUE_RENDER_COMMAND(BeginDrawingCommand)-----------RHICmdList.BeginDrawingViewport(GetViewportRHI(), FTextureRHIRef()); UpdateRenderTargetSurfaceRHIToCurrentBackBuffer(); 更新RT到BackBuffer上
Canvas.SetRenderTargetRect(FIntRect(0, 0, SizeX, SizeY)) ViewportClient->Draw(this, &Canvas) 游戏实际调用UGameViewportClient::Draw BeginDrawDelegate.Broadcast CanvasObject->Canvas = SceneCanvas 设到UCanvas上 FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(InViewport,MyWorld->Scene,EngineShowFlags).SetRealtimeUpdate(true))//构造FSceneViewFamilyContext ViewFamily 遍历所有ULocalPlayer,拿到APlayerController因为相机在这里 如果是StereoRendering就有多个FSceneView,否则就1个,初始化一堆参数 给VR游戏用的左右眼,所以是多个View IStreamingManager::Get().AddViewInformation 把View信息告诉资源加载模块,看注释FOV较小时可以5倍速加载 FinalizeViews(&ViewFamily, PlayerViewMap) 根据所有View计算一个最大包围盒 GetRendererModule().BeginRenderingViewFamily 实际调用的是FRendererModule::BeginRenderingViewFamily
World->SendAllEndOfFrameUpdates()
BeginSendEndOfFrameUpdatesDrawEvent(Scene ? Scene->GetGPUSkinCache() : nullptr) -----ENQUEUE_RENDER_COMMAND(BeginDrawEventCommand)----SendAllEndOfFrameUpdates->GPUSkinCache->BeginBatchDispatch(RHICmdList)
EndSendEndOfFrameUpdatesDrawEvent(SendAllEndOfFrameUpdates)----------------ENQUEUE_RENDER_COMMAND(EndDrawEventCommand)----------------SendAllEndOfFrameUpdates->GPUSkinCache->EndBatchDispatch(RHICmdList)
SendAllEndOfFrameUpdates->GPUSkinCache->TransitionAllToReadable(RHICmdList)
delete SendAllEndOfFrameUpdates
FSceneRenderer* SceneRenderer = FSceneRenderer::CreateSceneRenderer(ViewFamily, Canvas->GetHitProxyConsumer()) 创建一个SceneRenderer, 里面会根据ShadingPath决定是Deferred渲染器还是Mobile渲染器 (Mobile里也可以DeferredShading) ENQUEUE_RENDER_COMMAND(FInitFXSystemCommand) USceneCaptureComponent::UpdateDeferredCaptures ENQUEUE_RENDER_COMMAND(FViewExtensionPreDrawCommand) 遍历场景中的PlanarReflections做SceneRenderer->Scene->UpdatePlanarReflectionContents SceneRenderer->ViewFamily.DisplayInternalsData.Setup(World) 提交FDrawSceneCommand这里开始让渲染线程画场景,SceneRenderer作为lambda传给了渲染线程----ENQUEUE_RENDER_COMMAND(FDrawSceneCommand)-----RenderViewFamily_RenderThread(RHICmdList, SceneRenderer)| SceneRenderer->Render FDeferredShadingSceneRenderer::Render FMobileSceneRenderer::Render 前面的2选1决定这里用哪个,这个函数就是UE4的整个渲染管线 后续填坑,这里不细说了 FlushPendingDeleteRHIResources_RenderThread()
遍历所有PlayerController,画HUD,注意这里是ToneMapping后才画,不是在场景RT上画
CanvasObject->Init
CanvasObject->ApplySafeZoneTransform()
PlayerController->MyHUD->SetCanvas(CanvasObject, DebugCanvasObject)
PlayerController->MyHUD->PostRender() SceneCanvas->Flush_GameThread() DrawnDelegate.Broadcast()
PostRender(DebugCanvasObject)
DebugCanvasObject->Init
DrawStatsHUD
ViewportConsole->PostRender_Console(DebugCanvasObject) EndDrawDelegate.Broadcast() Canvas.Flush_GameThread() SetRequiresVsync(bLockToVsync) EnqueueEndRenderFrame(bLockToVsync, bShouldPresent)
FViewport::EnqueueEndRenderFrame(bLockToVsync, bShouldPresent) -----------ENQUEUE_RENDER_COMMAND(EndDrawingCommand)------------ViewportEndDrawing(RHICmdList, Params)
Parameters.Viewport->EndRenderFrame GetRendererModule().PostRenderAllViewports() IStreamingManager::Get().Tick 通知渲染线程TickRenderingTimer----------------ENQUEUE_RENDER_COMMAND(TickRenderingTime)-----------------这里就是更新RT池 GRenderingRealtimeClock.Tick(DeltaSeconds)
GRenderTargetPool.TickPoolElements() FRDGBuilder::TickPoolElements() ICustomResourcePool::TickPoolElements(RHICmdList) 编辑器:BroadcastPostEditorTick(DeltaSeconds); FAssetRegistryModule::TickAssetRegistry(DeltaSeconds)
PreLoadScreenManager相关,播视频时候会阻塞在这里直到完成再向下执行 GShaderCompilingManager->ProcessAsyncResults GDistanceFieldAsyncQueue->ProcessAsyncTasks MediaModule->TickPreSlate() FSlateApplication::Get().Tick(ESlateTickType::PlatformAndInput) CurrentDemoNetDriver可能会建-个ConcurrentTask执行TickFlushAsyncEndOfFrame FSlateApplication::Get().Tick(ESlateTickType::TimeAndWidgets) TickTime()
TickAndDrawWidgets(DeltaTime)
PreTickEvent.Broadcast(DeltaTime)
DrawWindows()
PrivateDrawWindows()
DrawPrepass( DrawOnlyThisWindow )
DrawWindowAndChildren
Renderer->DrawWindows( DrawWindowArgs.OutDrawBuffer )
SlateBeginDrawingWindowsCommand -----------ENQUEUE_RENDER_COMMAND(SlateBeginDrawingWindowsCommand)------------Policy->BeginDrawingWindows()
SlateDrawWindowsCommand ----------------ENQUEUE_RENDER_COMMAND(SlateDrawWindowsCommand)---------------Params.Renderer->DrawWindow_RenderThread
SlateWindowRendered.Broadcast
SlateEndDrawingWindowsCommand ------------ENQUEUE_RENDER_COMMAND(SlateEndDrawingWindowsCommand)-------------FSlateEndDrawingWindowsCommand::EndDrawingWindows
if (DeferredUpdateContexts.Num() > 0)
DrawWidgetRendererImmediate --------------ENQUEUE_RENDER_COMMAND(DrawWidgetRendererImmediate)-------------循环FRenderThreadUpdateContext,执行Context.Renderer->DrawWindowToTarget_RenderThread(RHICmdList, Context)
PostTickEvent.Broadcast(DeltaTime)
FTaskGraphInterface::Get().WaitUntilTaskCompletes(ConcurrentTask) 通知渲染线程WaitForOutstandingTasks---ENQUEUE_RENDER_COMMAND(WaitForOutstandingTasksOnly_for_DelaySceneRenderCompletion)---FRHICommandListExecutor::GetImmediateCommandList().ImmediateFlush(EImmediateFlushType::WaitForOutstandingTasksOnly) RHITick( FApp::GetDeltaTime() ) GFrameCounter++; TotalTickTime += FApp::GetDeltaTime() FrameEndSync.Sync//等渲染线程的同步 delete PreviousPendingCleanupObjects DeleteLoaders() FTicker::GetCoreTicker().Tick(FApp::GetDeltaTime()) FThreadManager::Get().Tick() GEngine->TickDeferredCommands() MediaModule->TickPostRender() FCoreDelegates::OnEndFrame.Broadcast() 通知渲染线程EndFrame---------------------------ENQUEUE_RENDER_COMMAND(EndFrame)--------------------------EndFrameRenderThread(RHICmdList, CurrentFrameCounter) GEngine->SetGameLatencyMarkerEnd(CurrentFrameCounter)
在Tick中会先执行游戏逻辑,调用World的Tick,然后Tick所有注册需要Tick的Actor和Component,这里会根据注册的阶段分别在不同时期Tick。
结束之后会进入绘制视口,会先画场景,在画场景时才相当于是渲染线程这帧真正开始了,然后画UI。
然后中间很多地方都穿插着多线程调度。最终我们看到引擎执行一帧大概如下图所示:
注1:由于UI在场景之后绘制,假如UI遮挡住了大部分场景,被遮挡住的部分就白画了。
所以如果能修改引擎代码的话,可以考虑在绘制开始阶段,先在场景的RT上UI对应的位置写上深度(需要额外处理半透明)或者建一些对应轮廓面片放在镜头近平面上挡住场景对应区域,这样就可以跳过这些像素的绘制。
注2:如果游戏线程做的事情很少,基本上会阻塞在最后的FrameEndSync.Sync上。
当你有一些很重的工作,但是又和渲染无关,比如网络游戏的解包或其他比较重的逻辑,就可以考虑在绘制这一阶段期间开启一个单独的线程,让子线程去做这些工作,而不是放在前面的Tick阶段。
在Tick开始处,在Scene->UpdateAllPrimitiveSceneInfos(RHICmdList)中会先把场景数据的各种信息比如,Transform在渲染线程上刷一遍(因为很多东西是会动的)。
然后引擎开始Tick World。这里比较重要的一点是,我们可以看到Tick的对象有很多阶段,平常用的比较多的是PrePhysics(Tick默认为该类型),DuringPhysics,PostPhysics这3个地方。
为什么要区分这些阶段呢?这是因为UE4是个多线程的引擎,物理是一个很重的计算流程,物理的计算发生在一个单独的线程上,因此将Tick拆分成这些阶段,就可以让业务代码选择在什么时期执行。
因为大部分的组件都是需要先准备好数据,交给物理线程来执行,所以UE4把Tick默认都放在了PrePhysics上,这样当所有组件Tick完,物理线程得到的数据就是最新的。
但是考虑到假如你的组件或Actor和物理没任何关系,那么物理线程就会等待逻辑执行,在物理线程开始执行后,由于DurningPhysics基本没事情做,又反过来等待物理线程,这样游戏线程的总耗时就会被拉长。
因此可以把一些不需要依赖物理的组件放在其他阶段,说不定能起到很好的优化效果。
注:Actor和ActorComponent的Tick是分别注册且互相独立的,互相不存在依赖关系。所以当不需要Tick Component时,关掉Actor是不够的,Component也要单独关闭。
参考