Reset Async Trace Time有40ms+的性能消耗

Hi,更新到5.5以后,我们某个场景在运行时Game线程开销非常大,我们发现是Reset Async Trace Time的开销很高,这有什么缓解手段么?

​[Image Removed]

unreal insights里面似乎没有太多信息

[Image Removed]

提供一个Insights的文件:

通过网盘分享的文件:20250605_232611.utrace

链接: https://pan.baidu.com/s/1moQmfaLCm6F3tlonPPgJaQ?pwd=xckd 提取码: xckd

Hi,

你好,我会尽快看一下这个问题,​可能会稍微晚几天,请见谅。

另外我们想请教一下,UE有什么异步更新attach的办法么 直接在角色身上挂上ChildActor 上面挂了几个物理场的Field 开销有点大

[Image Removed] 只绑定几个field其实无所谓的 主要它上面有太多可视化用的组件了 每个都要更新 等于多了几十个可移动渲染的组件挂在了boss上 不然只能选择加一个开关 运行时直接把这些组件都detach掉了。

其他问题应该就剩第一个回复里面那个GeomOverlapMultiple和UnknownSceneQuery的开销问题了。如果不连接WaitForInteractableTargets_Visible_Ex的实现,就不会有这个问题,所以想请教下这部分还能怎么优化么。

Hi,

我们看了一下,似乎是我们交互系统影响的。

[Image Removed]我们是交互里面是用了overlap

临时修改了一下

const ECollisionChannel TraceChannel_Interaction = ECollisionChannel::ECC_GameTraceChannel1;

const EObjectTypeQuery Cthu_Object_InteractionCollision = EObjectTypeQuery::ObjectTypeQuery11;

`private void QueryInteractables()
{
AActor ActorOwner = GetAvatarActor();

if (ActorOwner != nullptr)
{
FCollisionQueryParams Params(n"UAbilityTask_GrantNearbyInteraction", false, ActorOwner);

FScriptOverlapDelegate Delegate;
Delegate.BindUFunction(this, n"DealwithAsyncOverlapResults");

FCollisionObjectQueryParams ObjectQueryParams = UCollisionProfile::ObjectTypeToQueryParams(CthuCollision::Cthu_Object_InteractionCollision);

System::AsyncOverlapByObjectType(ActorOwner.GetActorLocation(),
FQuat::Identity,
ObjectQueryParams,
FCollisionShape::MakeSphere(InteractionScanRange), Params, InDelegate = Delegate);
}
}`

[Image Removed]修改以后 Reset Async Trace是正常了

[Image Removed]但是还有一点其他的开销。

[Image Removed]应该是这里导致的 (上面的代码是修改的下图中第一个节点的实现)

[Image Removed]这个也是异步的检测

[Image Removed]每帧都进行 先判断是否在范围内 然后再测试能否通过同步的射线检测 异步overlap+同步射线检测

`UCLASS()
class UAbilityTask_WaitForInteractableTargets_Visible_Ex : UAbilityTask_WaitForInteractableTargets_Base
{
default IsTickingTask = true;

FTraceHandle AsyncOverlapHandle;

EObjectTypeQuery ObjectTypeQuery = CthuCollision::Cthu_Object_InteractionCollision;

FGameplayAbilityTargetingLocationInfo StartLocation;

FCollisionShape CollisionShape;

bool bMultiTarget;

FInteractionQuery InteractionQuery;

// float InteractionScanRate = 0.100;
bool bShowDebug = false;

float TestAngle = 12;

UFUNCTION(BlueprintOverride)
void Activate()
{
}

UFUNCTION(BlueprintOverride)
void TickTask(float DeltaTimeSecs)
{
if (!bIsEnableDetection)
{
return;
}

if (!IsValid(AvatarActor))
{
return;
}

// 使用上一帧发起的异步检测的Overlap数据.
FOverlapDatum OverlapData;
if (System::QueryOverlapData(AsyncOverlapHandle, OverlapData))
{
const FVector Pos = StartLocation.GetTargetingTransform().GetLocation();

// 异步检测,存在当前帧已经销毁的情况.
TArray ValidatedOverlapResults = GetValidatedOverlapResults(OverlapData.OutOverlaps);

TArray OverlapResults = ConvertOverlapResultsToSortedHitResults(Pos, ValidatedOverlapResults);

DealwithOverlapResults(OverlapResults);
}
// AsyncTraceHandle = FTraceHandle();

// 开始这一帧的异步检测
FCollisionQueryParams Params(n"UAbilityTask_WaitForInteractableTargets_Visible_Ex", false, AvatarActor);

FVector TraceStart = StartLocation.GetTargetingTransform().GetLocation();
FQuat Rotation = AvatarActor.GetActorQuat();

AsyncOverlapHandle = AsyncDoOverlap(TraceStart, Rotation, ObjectTypeQuery, CollisionShape, Params);
}

void DealwithOverlapResults(TArray InOverlapResults)
{
TArray OverlapResults = InOverlapResults;

RemoveNotGoodHit(OverlapResults);

if (!bMultiTarget)
{
if (OverlapResults.IsValidIndex(0)) // 只使用第一个.也就是最近的那个.
{
auto FirstItem = OverlapResults[0];

OverlapResults.Empty();
OverlapResults.Add(FirstItem);
}
}

UpdateInteractableOptions(InteractionQuery, OverlapResults);
}

UFUNCTION(BlueprintOverride)
void OnDestroy(bool bInOwnerFinished)
{
Super::OnDestroy(bInOwnerFinished);
}

void SetStartLocation(FGameplayAbilityTargetingLocationInfo InStartLocation)
{
StartLocation = InStartLocation;

ThrowIf(!StartLocation.IsValid(), “Seted StartLocation is not valid”);
}

void RemoveNotGoodHit(TArray& HitResults)
{
FVector ViewStart;
FRotator ViewRot;

APlayerController PC = Ability.ActorInfo.PlayerController;
ThrowIf(!IsValid(PC), “PC is not valid”);

PC.GetPlayerViewPoint(ViewStart, ViewRot);

const FVector FaceToDir = ViewRot.Vector();

for (int32 i = HitResults.Num() - 1; i >= 0; i–)
{
const FHitResult& Hit = HitResults[i];
const FVector TestTargetLoc = Hit.Component.GetWorldLocation();
const FVector ViewDirToTarget = (TestTargetLoc - ViewStart).GetSafeNormal();

const float DotProduct = ViewDirToTarget.DotProduct(FaceToDir);
const float FaceTo_Deg = Math::ACosDeg(DotProduct);

float IncludedAngle = TestAngle;

AInteractable_Proxy ProxyActor = Cast<AInteractable_Proxy>(Hit.Actor);
if (ProxyActor != nullptr)
{
if (ProxyActor.bShouldOverrideTraceParam)
{
if (ProxyActor.ViewIncludedAngle > 0)
{
IncludedAngle = ProxyActor.ViewIncludedAngle;
}
}
}

// if (Hit.Actor.GetFullName().Contains(“Ladder”))
// {
// PrintToScreen(f"Ladder FaceTo_Deg: {FaceTo_Deg} IncludedAngle: {IncludedAngle}", 0.12, FLinearColor::Red);
// }

if (!Math::InRange(FaceTo_Deg, 0, IncludedAngle))
{
HitResults.RemoveAt(i);
continue;
}

FCollisionQueryParams QueryParams(n"LineOfSight", true, AvatarActor);

float SweepRadius = 5;
if (ProxyActor != nullptr)
{
if (ProxyActor.bShouldOverrideTraceParam)
{
if (ProxyActor.SweepSphereRadius >= 0)
{
SweepRadius = ProxyActor.SweepSphereRadius;
}
}

QueryParams.AddIgnoredActors(ProxyActor.IgnoreCollisionActors);
}

FHitResult OutHitResult;
DoTrace_Single(OutHitResult, ViewStart, TestTargetLoc, SweepRadius, CthuCollision::TraceChannel_Interaction, QueryParams);

// 在能射线命中InteractableActor的情况下,才合理.
if (OutHitResult.Actor != Hit.Actor)
{
// Print(f"{OutHitResult.Actor}");

HitResults.RemoveAt(i);
continue;
}
}
}
}

UFUNCTION(BlueprintCallable, Category = “as|task”, meta = (HidePin = “OwningAbility”, DefaultToSelf = “OwningAbility”, BlueprintInternalUseOnly = “True”))
UAbilityTask_WaitForInteractableTargets_Visible_Ex WaitForInteractableTargets_Visible_Ex(UGameplayAbility OwningAbility,
FInteractionQuery InteractionQuery, bool bMultiTarget,
FGameplayAbilityTargetingLocationInfo StartLocation, float SphereRadius, float TestAngle = 12,
EInteractableTargetFilterType InTargetFilter = EInteractableTargetFilterType::CanInteractTo,
// float InteractionScanRate = 0.100,
bool bShowDebug = false)
{
UAbilityTask_WaitForInteractableTargets_Visible_Ex MyObj =
UAngelscriptAbilityTask::CreateAbilityTask_Ex(UAbilityTask_WaitForInteractableTargets_Visible_Ex, OwningAbility);

MyObj.CollisionShape = FCollisionShape::MakeSphere(SphereRadius);
// MyObj.InteractionScanRate = InteractionScanRate;

MyObj.bMultiTarget = bMultiTarget;

MyObj.TargetFilter = InTargetFilter;

MyObj.TestAngle = TestAngle;

MyObj.SetStartLocation(StartLocation);

MyObj.InteractionQuery = InteractionQuery;
// MyObj->TraceTypeQuery = TraceTypeQuery;
MyObj.bShowDebug = bShowDebug;

return MyObj;
}` 这里感觉主要也是异步overlap的开销。还能怎么优化么?同步射线检测,进行少量的检测,应该几乎不影响性能。

您好,

我看默认物理场上的这些可视化组件都是Hidden In Game的, 应该不会是因为这些产生Send Render Data的开销, 请问方便上传一个简要工程看下现在物理场的用法么?

Hi,

我看了一下,是我们为了调试方便,没有设置Hidden In Game。

蓝图中物理场那个实现是逐帧创建组件,我们感觉不太友好。所以使用as脚本重新实现了一下,但是没有加Hidden In Game,应该是这个原因导致的Send Render Data的开销。

附件中加了一个我们实现的Field,是用Angelscript的脚本实现的 应该把这行补上,行为就和BP里面是一样的了

[Image Removed]

您好, 只看现在的trace看不太出来问题, 方便加一下 -task 重新trace一下交互系统修改后的情况么, 我们再详细看下, 谢谢 !

Hi,

我加了-task,重新trace了一下。在场景更复杂的情况下,GeomOverlapMultiple和UnknownSceneQuery的开销似乎更大了。

[Image Removed]这是相关的trace文件:

​通过网盘分享的文件:20250610_195431.utrace

链接: https://pan.baidu.com/s/1m2pj3zYmHr0ZQoJRutaYsw?pwd\=68uq 提取码: 68uq

您好,

我把trace也给程序同事一起看了下, 现在看起来确实是异步的overlap检测, 不会阻塞game thread.

继续优化scene query的开销的话, 主要看下场景碰撞的复杂度, steaming范围内的碰撞体有多少, 是不是简单碰撞, 如果是凸包或者复杂碰撞的话, 碰撞体面数会不会太大.

可以用Collision.ListObjectsWithCollisionComplexity usecomplexassimple 这个命令检查下场景里多少物体用了复杂碰撞, 是否可以转成简单碰撞.

好的,我们去检查下场景中的复杂碰撞。​

好的, 有问题再沟通

Hi,

我们详细Debug了一下交互系统

我的检测是单独的一个通道 这样把他这个拷贝出来的

[Image Removed]默认应该都是ignore 就按理来说不可能和不相干的通道有交集

[Image Removed]

实际Debug的时候也挺正常

[Image Removed]发现检测并没有返回大量不相干的结果,都非常正常。

似乎单纯是ue自己,即使在设置了明确的碰撞通道情况下。大范围查询依旧极为耗时。按正常来说 碰撞通道不相干,应该连aabb都不应该测试吧,更不需要测试具体几何体了。

您好, collision filter是在碰撞检测的broad phase之后, 设置了碰撞通道, aabb测试仍需要做, 大范围查询场景内的collision shape数量, 还有trace范围内碰撞体的复杂度是会影响性能的.

5.5上可以用CVD录制一下, 辅助检查下场景里的trace, trace的数量, collision的shape type数量等等. 方便的话也可以发CVD文件过来看下.

:joy: 原来如此,明白了。

我们按您说的,用CVD录制了一份Trace,但是可能由于场景过于复杂,CVD文件似乎录制不出来。

[Image Removed]加载直接失败了

[Image Removed]似乎目前只能先考虑把场景中的UseComplexAsSimple改成UseSimpleAsComplex看看情况。

场景中确实有不少UseComplexAsSimple的设置。

[Image Removed]如果改完以后,还有问题的话,可能再考虑关闭大部分的DataLayer,留个比较小的场景分块测试CVD吧。场景太大的情况下,录制出来根本无法读取。:face_without_mouth: 或者可能就得改下引擎代码了,chaos的stats不是很细,可能考虑自己再加一些Trace,看看能不能拿到更多信息了。

我没有碰到过上面的这个CVD加载问题, ​建议可以先批量修改下资产的碰撞复杂度, 可以参考下这个视频用modeling工具生成简化的碰撞体. 有进一步信息我们再沟通.

好的,我们先修改碰撞试试看。

CVD加载这个问题,我和其他公司的朋友也沟通过,他们也遇到了。只要场景的复杂度很高,这个加载就一定几率会失败,估计是因为数据量太大导致的。