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

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

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

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

 

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

 

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