角色位移本质上就是渲染问题,从这条思路我们去看看角色怎么实现这一部分渲染的
起点:SetActorLocation
蓝图调用的API实际调用的是K2_SetActorLocation,然后调用到此函数
1 return RootComponent->MoveComponent (Delta, GetActorQuat (), bSweep, OutSweepHitResult, MOVECOMP_NoFlags, Teleport);
只看这个关键代码,调用RootComponent
的MoveComponent
USceneComponent MoveComponent
外部调用此函数后里面直接调用到带Impl
后缀的虚函数
这个函数在UPrimitiveComponent内有重写
USceneComponent
内主要调用了函数ConditionalUpdateComponentToWorld
,而UPrimitiveComponent
因为有碰撞等属性所以又多了很多逻辑,这里不多描述
UpdateComponentToWorldWithParent
此函数通过ConditionalUpdateComponentToWorld
调用,后又调用到虚函数UpdateComponentToWorld
,此虚函数在UActorComponent
声明,在USceneComponent
重写
然后再直接调用到此函数
1 2 3 4 5 6 7 8 9 if (Parent && !Parent->bComponentToWorldUpdated){ Parent->UpdateComponentToWorld (); if (bComponentToWorldUpdated) { return ; } }
如果有父级组件,先去处理他
然后调用到函数PropagateTransformUpdate
该函数执行了
更新体积(Bounds)
更新变换(Transform)
更新寻路信息
其中有个关键的函数UActorComponent::MarkRenderTransformDirty
然后经过简单判断以后调用UActorComponent::MarkForNeededEndOfFrameUpdate
然后调用了UWorld
的MarkActorComponentForNeededEndOfFrameUpdate
看主要代码
1 2 3 4 5 6 7 8 void UActorComponent::MarkRenderTransformDirty () { if (IsRegistered () && bRenderStateCreated) { bRenderTransformDirty = true ; MarkForNeededEndOfFrameUpdate (); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void UActorComponent::MarkForNeededEndOfFrameUpdate () { if (bNeverNeedsRenderUpdate) { return ; } UWorld* ComponentWorld = GetWorld (); if (ComponentWorld) { ComponentWorld->MarkActorComponentForNeededEndOfFrameUpdate (this , RequiresGameThreadEndOfFrameUpdates ()); } else if (!IsUnreachable ()) { DoDeferredRenderUpdates_Concurrent (); } }
UWorld MarkActorComponentForNeededEndOfFrameUpdate
代码在LevelTick.cpp L883
主要执行了把组件添加到了等待渲染的列表ComponentsThatNeedEndOfFrameUpdate_OnGameThread
里,当然还有一个非游戏线程的ComponentsThatNeedEndOfFrameUpdate
1 2 3 4 5 6 7 8 9 10 if (bForceGameThread) { FMarkComponentEndOfFrameUpdateState::Set (Component, ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Num (), EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread); ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Add (Component); } else { FMarkComponentEndOfFrameUpdateState::Set (Component, ComponentsThatNeedEndOfFrameUpdate.Num (), EComponentMarkedForEndOfFrameUpdateState::Marked); ComponentsThatNeedEndOfFrameUpdate.Add (Component); }
到此我们大概知道了这一条线的终点了,然后去看看哪里使用到了这个数组
SendAllEndOfFrameUpdates 确认使用上面数组是在此函数内,查找引用发现此函数在很多地方被调用,如SceneRendering.cpp
,UnrealEngine.cpp
大概就是要更新场景最新的渲染的时候就会调用,先不理会,看这个函数内实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (UActorComponent* Component : ComponentsThatNeedEndOfFrameUpdate_OnGameThread) { if (Component) { if (Component->IsRegistered () && !Component->IsTemplate () && !Component->IsPendingKill ()) { FScopeCycleCounterUObject ComponentScope (Component) ; FScopeCycleCounterUObject AdditionalScope (STATS ? Component->AdditionalStatObject() : nullptr ) ; Component->DoDeferredRenderUpdates_Concurrent (); } check (Component->IsPendingKill () || Component->GetMarkedForEndOfFrameUpdateState () == EComponentMarkedForEndOfFrameUpdateState::MarkedForGameThread); FMarkComponentEndOfFrameUpdateState::Set (Component, INDEX_NONE, EComponentMarkedForEndOfFrameUpdateState::Unmarked); } } ComponentsThatNeedEndOfFrameUpdate_OnGameThread.Reset (); ComponentsThatNeedEndOfFrameUpdate.Reset ();
遍历了所有数组成员,主要执行了DoDeferredRenderUpdates_Concurrent
,回头看USceneComponent
内的如果没有UWorld
就直接调用了这个函数
UActorComponent DoDeferredRenderUpdates_Concurrent RecreateRenderState_Concurrent
,SendRenderTransform_Concurrent
,SendRenderDynamicData_Concurrent
告诉引擎需要处理相应的渲染任务
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 void UActorComponent::DoDeferredRenderUpdates_Concurrent () { checkf (!IsUnreachable (), TEXT ("%s" ), *GetFullName ()); checkf (!IsTemplate (), TEXT ("%s" ), *GetFullName ()); checkf (!IsPendingKill (), TEXT ("%s" ), *GetFullName ()); FScopeCycleCounterUObject ContextScope (this ) ; 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); if (bRenderTransformDirty) { SendRenderTransform_Concurrent (); } if (bRenderDynamicDataDirty) { SendRenderDynamicData_Concurrent (); } } }
这个函数在基类只做了基础实现,主要是派生类重写实现主要逻辑,类似的也包括SendRenderDynamicData_Concurrent
,CreateRenderState_Concurrent
1 2 3 4 5 6 7 8 { check (bRenderStateCreated); bRenderTransformDirty = false ; #if LOG_RENDER_STATE UE_LOG (LogActorComponent, Log, TEXT ("SendRenderTransform_Concurrent: %s" ), *GetPathName ()); #endif }
UPrimitiveComponent 紧接上面,我们看看有形状体积等信息的UPrimitiveComponent
1 2 3 4 5 6 7 8 9 10 11 void UPrimitiveComponent::SendRenderTransform_Concurrent () { UpdateBounds (); const bool bDetailModeAllowsRendering = DetailMode <= GetCachedScalabilityCVars ().DetailMode; if ( bDetailModeAllowsRendering && (ShouldRender () || bCastHiddenShadow)) { GetWorld ()->Scene->UpdatePrimitiveTransform (this ); } Super::SendRenderTransform_Concurrent (); }
看到在实现基类逻辑之前执行了FScene
内的函数UpdatePrimitiveTransform
FScene
代码在 RendererScene.cpp中
在UpdatePrimitiveTransform
函数中会判断该Primitive
有没有SceneProxy
,如果有SceneProxy
则判断该Primitive
是否需要重新创建,如果需要重新创建则需要先删除,然后再添加该Primitive
;如果没有SceneProxy
则直接加入到场景中。接着我们具体看一下AddPrimitive
函数的具体实现:
AddPrimitive 此函数主要做了如下几件事情
创建SceneProxy
和PrimitiveSceneInfo
利用FPrimitiveSceneInfo
封装UPrimitiveComponent
的信息,继续在render
线程中进行操作
给渲染线程发送2个命令,分别是设置Primitive
的Transform
信息和将PrimitiveSceneInfo
加入到渲染线程的数据集合中
代码如下
1 2 3 4 5 FPrimitiveSceneProxy* PrimitiveSceneProxy = Primitive->CreateSceneProxy (); Primitive->SceneProxy = PrimitiveSceneProxy; FPrimitiveSceneInfo* PrimitiveSceneInfo = new FPrimitiveSceneInfo (Primitive, this ); PrimitiveSceneProxy->PrimitiveSceneInfo = PrimitiveSceneInfo;
1 2 3 4 5 6 7 8 9 10 11 ENQUEUE_RENDER_COMMAND (CreateRenderThreadResourcesCommand)( [Params](FRHICommandListImmediate& RHICmdList) { FPrimitiveSceneProxy* SceneProxy = Params.PrimitiveSceneProxy; FScopeCycleCounter Context (SceneProxy->GetStatId ()); SceneProxy->SetTransform (Params.RenderMatrix, Params.WorldBounds, Params.LocalBounds, Params.AttachmentRootPosition); SceneProxy->CreateRenderThreadResources (); });
1 2 3 4 5 6 ENQUEUE_RENDER_COMMAND (AddPrimitiveCommand)( [Scene, PrimitiveSceneInfo](FRHICommandListImmediate& RHICmdList) { FScopeCycleCounter Context (PrimitiveSceneInfo->Proxy->GetStatId ()); Scene->AddPrimitiveSceneInfo_RenderThread (RHICmdList, PrimitiveSceneInfo); });
AddPrimitiveSceneInfo_RenderThread AddPrimitiveSceneInfo_RenderThread
函数是在渲染线程执行的,完成PrimitiveBounds
、PrimitiveFlagsCompact
等数据的初始化后,并调用LinkAttachmentGroup
处理primitive
的父子关系,调用LinkLODParentComponent
处理LOD父子关系,然后调用FPrimitiveSceneInfo::AddToScene
AddToScene
最终会把物体添加到列表DrawList
中去完成绘制,这里不继续深挖了,埋个扣子,以后来解开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 PrimitiveSceneInfo->LinkAttachmentGroup (); PrimitiveSceneInfo->LinkLODParentComponent (); const bool bAddToDrawLists = !(CVarDoLazyStaticMeshUpdate.GetValueOnRenderThread () && !WITH_EDITOR);if (bAddToDrawLists){ PrimitiveSceneInfo->AddToScene (RHICmdList, true ); } else { PrimitiveSceneInfo->AddToScene (RHICmdList, true , false ); PrimitiveSceneInfo->BeginDeferredUpdateStaticMeshes (); }
小结
由此我们就大致整理一下思路
设置Actor的位置,其实就是把新旧位置做一个插值,把这个插值传递给继承自USceneComponent
的MoveComponent
方法,然后在下一帧渲染出新的位置
这里也接上了并解释了之前AI_MoveTo 的内容了
上一张流程简图
补充 场景中静态物体的渲染顺序堆栈表参考 1 2 3 4 5 6 7 8 9 10 11 ActorComponet::ExecuteRegisterEvents UPrimitiveComponent::CreateRenderState_Concurrent FScene:: AddPrimitive FScene:: AddPrimitiveSceneInfo_RenderThread FScene:: AddStaticMeshes FStaticMesh::AddToDrawLists FMobileBasePassOpaqueDrawingPolicyFactory::AddStaticMesh ProcessMobileBasePassMesh FDrawMobileBasePassStaticMeshAction:: Process AddMeshToStaticDrawList
最终加入队列 以DrawingPolicy
为key
的map
队列
1 TStaticMeshDrawList<TMobileBasePassDrawingPolicy<FUniformLightMapPolicy, 0 > > MobileBasePassUniformLightMapPolicyDrawList[EBasePass_MAX];