动画更新率优化URO(Update Rate Optimization)

官方动画优化文档,建议认真查看:https://docs.unrealengine.com/en-us/Engine/Animation/Optimization

 

https://gameinstitute.qq.com/course/detail/10131

引用 来自Epic Games 工程师王祢语录:

 

URO(Update Rate Optimization),我们其实没有必要对所有的角色在每一帧都做骨骼计算。比如画面中一个角色的POSE上半身动作是怎么样,下半身动作是怎么样,是否需要融合,什么频率融合,中间是不是要插值,这些设置可以非常大程度决定骨骼更新的计算量。大家可以看到下面的图,左一是每一帧都更新;左边二是每四帧更新一次,中间用插值;第三张图是每十帧更新一次,中间用插值;最后一张图是每四帧更新一次,不用插值。大家可以看到当角色占屏面积比较小,离得比较远的时候其实是没有大差别的。

找到对应相关关键代码,抛砖引玉:

SkinnedMeshComponent如下代码

 

 

// Update Rate
    /** if TRUE, Owner will determine how often animation will be updated and evaluated. See AnimUpdateRateTick() 
     * This allows to skip frames for performance. (For example based on visibility and size on screen). */
    UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category=Optimization)
    uint8 bEnableUpdateRateOptimizations:1;

     bool ShouldUseUpdateRateOptimizations() const;//是否使用更新率优化 基于这个函数跟进代码会看到很多内容


    /** Animation Update Rate optimization parameters. */
    struct FAnimUpdateRateParameters* AnimUpdateRateParams;//重点:动画更新结构体参数,可以修改里面的参数优化

 

//以下控制台查看命令

static TAutoConsoleVariable<int32> CVarEnableAnimRateOptimization(
    TEXT("a.URO.Enable"),
    1,
    TEXT("True to anim rate optimization."));

static TAutoConsoleVariable<int32> CVarDrawAnimRateOptimization(
    TEXT("a.URO.Draw"),
    0,
    TEXT("True to draw color coded boxes for anim rate."));

 

 

SkeletalMeshComponent如下代码

    /** @return whether we should tick animation (we may want to skip it due to URO) */
    bool ShouldTickAnimation() const;

    /** Used to scale speed of all animations on this skeletal mesh. */
    UPROPERTY(EditAnywhere, AdvancedDisplay, BlueprintReadWrite, Category=Animation)
    float GlobalAnimRateScale;

void USkeletalMeshComponent::TickAnimation(float DeltaTime, bool bNeedsValidRootMotion)
{
    SCOPED_NAMED_EVENT(USkeletalMeshComponent_TickAnimation, FColor::Yellow);
    SCOPE_CYCLE_COUNTER(STAT_AnimGameThreadTime);
    SCOPE_CYCLE_COUNTER(STAT_AnimTickTime);
    if (SkeletalMesh != nullptr)
    {
        // We're about to UpdateAnimation, this will potentially queue events that we'll need to dispatch.
        bNeedsQueuedAnimEventsDispatched = true;

        // We update sub instances first incase we're using either root motion or non-threaded update.
        // This ensures that we go through the pre update process and initialize the proxies correctly.
        for(UAnimInstance* SubInstance : SubInstances)
        {
            SubInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, false);
        }

        if (AnimScriptInstance != nullptr)
        {
            // Tick the animation
            AnimScriptInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, bNeedsValidRootMotion);
        }

        if(ShouldUpdatePostProcessInstance())
        {
            PostProcessAnimInstance->UpdateAnimation(DeltaTime * GlobalAnimRateScale, false);
        }

        /**
            If we're called directly for autonomous proxies, TickComponent is not guaranteed to get called.
            So dispatch all queued events here if we're doing MontageOnly ticking.
        */
        if (ShouldOnlyTickMontages(DeltaTime))
        {
            ConditionallyDispatchQueuedAnimEvents();
        }
    }
}
 


void USkeletalMeshComponent::TickPose(float DeltaTime, bool bNeedsValidRootMotion)
{
    Super::TickPose(DeltaTime, bNeedsValidRootMotion);

    if (ShouldTickAnimation())
    {
        // Don't care about roll over, just care about uniqueness (and 32-bits should give plenty).
        LastPoseTickFrame = static_cast<uint32>(GFrameCounter);

        const bool bUseUpdateRateOptimizations = ShouldUseUpdateRateOptimizations();
        float TimeAdjustment = bUseUpdateRateOptimizations ? AnimUpdateRateParams->GetTimeAdjustment() : 0.0f;
        TickAnimation(DeltaTime + TimeAdjustment, bNeedsValidRootMotion);
        if (CVarSpewAnimRateOptimization.GetValueOnGameThread() > 0 && Ticked.Increment()==500)
        {
            UE_LOG(LogTemp, Display, TEXT("%d Ticked %d NotTicked"), Ticked.GetValue(), NotTicked.GetValue());
            Ticked.Reset();
            NotTicked.Reset();
        }
    }
    else
    {
        if (AnimScriptInstance)
        {
            AnimScriptInstance->OnUROSkipTickAnimation();
        }

        for(UAnimInstance* SubInstance : SubInstances)
        {
            SubInstance->OnUROSkipTickAnimation();
        }

        if(PostProcessAnimInstance)
        {
            PostProcessAnimInstance->OnUROSkipTickAnimation();
        }

        if (CVarSpewAnimRateOptimization.GetValueOnGameThread())
        {
            NotTicked.Increment();
        }
    }
}

virtual void TickPose(float DeltaTime, bool bNeedsValidRootMotion) { TickUpdateRate(DeltaTime, bNeedsValidRootMotion); }

void USkinnedMeshComponent::TickUpdateRate(float DeltaTime, bool bNeedsValidRootMotion)
{
    SCOPE_CYCLE_COUNTER(STAT_TickUpdateRate);
    if (ShouldUseUpdateRateOptimizations())
    {
        if (GetOwner())
        {
            // Tick Owner once per frame. All attached SkinnedMeshComponents will share the same settings.
            FAnimUpdateRateManager::TickUpdateRateParameters(this, DeltaTime, bNeedsValidRootMotion);

#if ENABLE_DRAW_DEBUG
            if ((CVarDrawAnimRateOptimization.GetValueOnGameThread() > 0) || bDisplayDebugUpdateRateOptimizations)
            {
                FColor DrawColor = AnimUpdateRateParams->GetUpdateRateDebugColor();
                DrawDebugBox(GetWorld(), Bounds.Origin, Bounds.BoxExtent, FQuat::Identity, DrawColor, false);

                FString DebugString = FString::Printf(TEXT("%s UpdateRate(%d) EvaluationRate(%d) ShouldInterpolateSkippedFrames(%d) ShouldSkipUpdate(%d) Interp(%d) AdditionalTime(%f)"),
                    *GetNameSafe(SkeletalMesh), AnimUpdateRateParams->UpdateRate, AnimUpdateRateParams->EvaluationRate, 
                    AnimUpdateRateParams->ShouldInterpolateSkippedFrames(), AnimUpdateRateParams->ShouldSkipUpdate(), AnimUpdateRateParams->AdditionalTime);

                GEngine->AddOnScreenDebugMessage(INDEX_NONE, 0.f, FColor::Red, DebugString, false);
            }
#endif // ENABLE_DRAW_DEBUG
        }
    }
}
 

 

void TickUpdateRateParameters(USkinnedMeshComponent* SkinnedComponent, float DeltaTime, bool bNeedsValidRootMotion)
    {
        // Convert current frame counter from 64 to 32 bits.
        const uint32 CurrentFrame32 = uint32(GFrameCounter % MAX_uint32);

        UObject* TrackerIndex = GetMapIndexForComponent(SkinnedComponent);
        FAnimUpdateRateParametersTracker* Tracker = ActorToUpdateRateParams.FindChecked(TrackerIndex);

        if (CurrentFrame32 != Tracker->AnimUpdateRateFrameCount)
        {
            Tracker->AnimUpdateRateFrameCount = CurrentFrame32;
            AnimUpdateRateTick(Tracker, DeltaTime, bNeedsValidRootMotion);
        }
    }

void AnimUpdateRateTick(FAnimUpdateRateParametersTracker* Tracker, float DeltaTime, bool bNeedsValidRootMotion)
    {
        // Go through components and figure out if they've been recently rendered, and the biggest MaxDistanceFactor
        bool bRecentlyRendered = false;
        bool bPlayingNetworkedRootMotionMontage = false;
        bool bUsingRootMotionFromEverything = true;
        float MaxDistanceFactor = 0.f;
        int32 MinLod = MAX_int32;

        const TArray<USkinnedMeshComponent*>& SkinnedComponents = Tracker->RegisteredComponents;
        for (USkinnedMeshComponent* Component : SkinnedComponents)
        {
            bRecentlyRendered |= Component->bRecentlyRendered;
            MaxDistanceFactor = FMath::Max(MaxDistanceFactor, Component->MaxDistanceFactor);
            bPlayingNetworkedRootMotionMontage |= Component->IsPlayingNetworkedRootMotionMontage();
            bUsingRootMotionFromEverything &= Component->IsPlayingRootMotionFromEverything();
            MinLod = FMath::Min(MinLod, Tracker->UpdateRateParameters.bShouldUseMinLod ? Component->MinLodModel : Component->PredictedLODLevel);
        }

        bNeedsValidRootMotion &= bPlayingNetworkedRootMotionMontage;

        // Figure out which update rate should be used.
        AnimUpdateRateSetParams(Tracker, DeltaTime, bRecentlyRendered, MaxDistanceFactor, MinLod, bNeedsValidRootMotion, bUsingRootMotionFromEverything);
    }
 

AnimUpdateRateParams结构体内容优化,动态改写

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值