当前位置: 首页 > news >正文

ue5 motion matching

ue5.5   gameanimationsample

先看动画蓝图 核心两个node

第一个是根据数据选择当前的pose

 第二个是缓存一段历史记录,为第一个node选择的时候提供数据。

在animinstance的update方法中 每帧都更新这个函数,每帧更新trajectory的数据

看看第一个node的执行顺序

void FAnimNode_MotionMatching::UpdateAssetPlayer(const FAnimationUpdateContext& Context)void UPoseSearchLibrary::UpdateMotionMatchingState(const FAnimationUpdateContext& Context,const TArray<TObjectPtr<const UPoseSearchDatabase>>& Databases,float BlendTime,int32 MaxActiveBlends,const FFloatInterval& PoseJumpThresholdTime,float PoseReselectHistory,float SearchThrottleTime,const FFloatInterval& PlayRate,FMotionMatchingState& InOutMotionMatchingState,EPoseSearchInterruptMode InterruptMode,bool bShouldSearch,bool bShouldUseCachedChannelData,bool bDebugDrawQuery,bool bDebugDrawCurResult){...........//这里每帧都监听第二个节点,把history trajectory传过来const IPoseHistory* PoseHistory = nullptr;if (FPoseHistoryProvider* PoseHistoryProvider = Context.GetMessage<FPoseHistoryProvider>()){PoseHistory = &PoseHistoryProvider->GetPoseHistory();}FMemMark Mark(FMemStack::Get());const UAnimInstance* AnimInstance = Cast<const UAnimInstance>(Context.AnimInstanceProxy->GetAnimInstanceObject());check(AnimInstance);const UPoseSearchDatabase* CurrentResultDatabase = InOutMotionMatchingState.CurrentSearchResult.Database.Get();if (IsInvalidatingContinuingPose(InterruptMode, CurrentResultDatabase, Databases)){InOutMotionMatchingState.CurrentSearchResult.Reset();}FSearchContext SearchContext(0.f, &InOutMotionMatchingState.PoseIndicesHistory, InOutMotionMatchingState.CurrentSearchResult, PoseJumpThresholdTime);//addSearchContext.AddRole(DefaultRole, AnimInstance, PoseHistory);.........const FSearchResult NewSearchResult = Database->Search(SearchContext);.........
}UE::PoseSearch::FSearchResult UPoseSearchDatabase::Search(UE::PoseSearch::FSearchContext& SearchContext) const{.........Result = SearchPCAKDTree(SearchContext);.........
}UE::PoseSearch::FSearchResult UPoseSearchDatabase::SearchPCAKDTree(UE::PoseSearch::FSearchContext& SearchContext) const
{.........//channelTConstArrayView<float> QueryValues = SearchContext.GetOrBuildQuery(Schema);.........
}TConstArrayView<float> FSearchContext::GetOrBuildQuery(const UPoseSearchSchema* Schema)
{QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseSearch_GetOrBuildQuery);check(Schema);if (const FCachedQuery* FoundCachedQuery = CachedQueries.FindByPredicate([Schema](const FCachedQuery& CachedQuery){return CachedQuery.GetSchema() == Schema;})){return FoundCachedQuery->GetValues();}return Schema->BuildQuery(*this);
}TConstArrayView<float> UPoseSearchSchema::BuildQuery(UE::PoseSearch::FSearchContext& SearchContext) const
{QUICK_SCOPE_CYCLE_COUNTER(STAT_PoseSearch_BuildQuery);SearchContext.AddNewFeatureVectorBuilder(this);for (const TObjectPtr<UPoseSearchFeatureChannel>& ChannelPtr : GetChannels()){ChannelPtr->BuildQuery(SearchContext);}return SearchContext.EditFeatureVector();
}void UPoseSearchFeatureChannel_GroupBase::BuildQuery(UE::PoseSearch::FSearchContext& SearchContext) const
{for (const TObjectPtr<UPoseSearchFeatureChannel>& SubChannelPtr : GetSubChannels()){if (const UPoseSearchFeatureChannel* SubChannel = SubChannelPtr.Get()){SubChannel->BuildQuery(SearchContext);}}
}//subchannel就是各种channel 的数组 比如position channel,velocity channelvoid UPoseSearchFeatureChannel_Position::BuildQuery(UE::PoseSearch::FSearchContext& SearchContext) const
{..............const FVector BonePosition = SearchContext.GetSamplePosition(SampleTimeOffset, OriginTimeOffset, SchemaBoneIdx, SchemaOriginBoneIdx, SampleRole, OriginRole, PermutationTimeType, &BonePositionWorld);...........}FVector FSearchContext::GetSamplePosition(float SampleTimeOffset, float OriginTimeOffset, int8 SchemaSampleBoneIdx, int8 SchemaOriginBoneIdx, const FRole& SampleRole, const FRole& OriginRole, EPermutationTimeType PermutationTimeType, const FVector* SampleBonePositionWorldOverride)
{float PermutationSampleTimeOffset = 0.f;float PermutationOriginTimeOffset = 0.f;UPoseSearchFeatureChannel::GetPermutationTimeOffsets(PermutationTimeType, DesiredPermutationTimeOffset, PermutationSampleTimeOffset, PermutationOriginTimeOffset);const float SampleTime = SampleTimeOffset + PermutationSampleTimeOffset;const float OriginTime = OriginTimeOffset + PermutationOriginTimeOffset;return GetSamplePositionInternal(SampleTime, OriginTime, SchemaSampleBoneIdx, SchemaOriginBoneIdx, SampleRole, OriginRole, SampleBonePositionWorldOverride);
}FVector FSearchContext::GetSamplePositionInternal(float SampleTime, float OriginTime, int8 SchemaSampleBoneIdx, int8 SchemaOriginBoneIdx, const FRole& SampleRole, const FRole& OriginRole, const FVector* SampleBonePositionWorldOverride)
{if (SampleBonePositionWorldOverride){const FTransform RootBoneTransform = GetWorldBoneTransformAtTime(OriginTime, OriginRole, RootSchemaBoneIdx);if (SchemaOriginBoneIdx == RootSchemaBoneIdx){return RootBoneTransform.InverseTransformPosition(*SampleBonePositionWorldOverride);}// @todo: validate this still works for when root bone is not Identityconst FTransform OriginBoneTransform = GetWorldBoneTransformAtTime(OriginTime, OriginRole, SchemaOriginBoneIdx);const FVector DeltaBoneTranslation = *SampleBonePositionWorldOverride - OriginBoneTransform.GetTranslation();return RootBoneTransform.InverseTransformVector(DeltaBoneTranslation);}const FTransform RootBoneTransform = GetWorldBoneTransformAtTime(OriginTime, OriginRole, RootSchemaBoneIdx);const FTransform SampleBoneTransform = GetWorldBoneTransformAtTime(SampleTime, SampleRole, SchemaSampleBoneIdx);if (SchemaOriginBoneIdx == RootSchemaBoneIdx){return RootBoneTransform.InverseTransformPosition(SampleBoneTransform.GetTranslation());}const FTransform OriginBoneTransform = GetWorldBoneTransformAtTime(OriginTime, OriginRole, SchemaOriginBoneIdx);const FVector DeltaBoneTranslation = SampleBoneTransform.GetTranslation() - OriginBoneTransform.GetTranslation();return RootBoneTransform.InverseTransformVector(DeltaBoneTranslation);
}//获得过程终于用到了const IPoseHistory* PoseHistory = GetPoseHistory(SampleRole);
FTransform FSearchContext::GetWorldBoneTransformAtTime(float SampleTime, const FRole& SampleRole, int8 SchemaBoneIdx)
{// CachedQueries.Last is the query we're building check(!CachedQueries.IsEmpty());const UPoseSearchSchema* Schema = CachedQueries.Last().GetSchema();check(Schema);TConstArrayView<FBoneReference> BoneReferences = Schema->GetBoneReferences(SampleRole);check(BoneReferences[SchemaBoneIdx].HasValidSetup());const FBoneIndexType BoneIndexType = BoneReferences[SchemaBoneIdx].BoneIndex;const uint32 SampleTimeHash = GetTypeHash(SampleTime);const uint32 SampleRoleHash = GetTypeHash(SampleRole);const uint32 SampleTimeAndRoleHash = HashCombineFast(SampleTimeHash, SampleRoleHash);const uint32 BoneIndexTypeHash = GetTypeHash(BoneIndexType);const uint32 BoneCachedTransformKey = HashCombineFast(SampleTimeAndRoleHash, BoneIndexTypeHash);if (const FTransform* CachedTransform = CachedTransforms.Find(BoneCachedTransformKey)){return *CachedTransform;}FTransform WorldBoneTransform;if (BoneIndexType == RootBoneIndexType){// we already tried querying the CachedTransforms so, let's search in TrajectoryWorldBoneTransform = GetWorldRootBoneTransformAtTime(SampleTime, SampleRole);}else // if (BoneIndexType != RootBoneIndexType){// searching for RootBoneIndexType in CachedTransformsstatic const uint32 RootBoneIndexTypeHash = GetTypeHash(RootBoneIndexType); // Note: static const, since RootBoneIndexType is a constantconst uint32 RootBoneCachedTransformKey = HashCombineFast(SampleTimeAndRoleHash, RootBoneIndexTypeHash);if (const FTransform* CachedTransform = CachedTransforms.Find(RootBoneCachedTransformKey)){WorldBoneTransform = *CachedTransform;}else{WorldBoneTransform = GetWorldRootBoneTransformAtTime(SampleTime, SampleRole);}// collecting the local bone transforms from the IPoseHistoryconst IPoseHistory* PoseHistory = GetPoseHistory(SampleRole);#if WITH_EDITORif (!PoseHistory){UE_LOG(LogPoseSearch, Error, TEXT("FSearchContext::GetWorldBoneTransformAtTime - Couldn't search for bones requested by %s, because no IPoseHistory has been found!"), *Schema->GetName());}else#endif // WITH_EDITOR{check(PoseHistory);const USkeleton* Skeleton = Schema->GetSkeleton(SampleRole);FTransform LocalBoneTransform;if (!PoseHistory->GetTransformAtTime(SampleTime, LocalBoneTransform, Skeleton, BoneIndexType, RootBoneIndexType)){if (Skeleton){if (!PoseHistory->IsEmpty()){UE_LOG(LogPoseSearch, Warning, TEXT("FSearchContext::GetWorldBoneTransformAtTime - Couldn't find BoneIndexType %d (%s) requested by %s"), BoneIndexType, *Skeleton->GetReferenceSkeleton().GetBoneName(BoneIndexType).ToString(), *Schema->GetName());}}else{UE_LOG(LogPoseSearch, Warning, TEXT("FSearchContext::GetWorldBoneTransformAtTime - Schema '%s' Skeleton is not properly set"), *Schema->GetName());}}WorldBoneTransform = LocalBoneTransform * WorldBoneTransform;}}CachedTransforms.Add(BoneCachedTransformKey) = WorldBoneTransform;return WorldBoneTransform;
}//这里就是第二个节点pose history  里面有 trajectory
bool FPoseHistory::GetTransformAtTime(float Time, FTransform& OutBoneTransform, const USkeleton* BoneIndexSkeleton, FBoneIndexType BoneIndexType, FBoneIndexType ReferenceBoneIndexType, bool bExtrapolate) const
{CheckThreadSafetyRead(ReadPoseDataThreadSafeCounter);static_assert(RootBoneIndexType == 0 && ComponentSpaceIndexType == FBoneIndexType(-1) && WorldSpaceIndexType == FBoneIndexType(-2)); // some assumptionscheck(BoneIndexType != ComponentSpaceIndexType && BoneIndexType != WorldSpaceIndexType);bool bSuccess = false;const bool bApplyComponentToWorld = ReferenceBoneIndexType == WorldSpaceIndexType;FTransform ComponentToWorld = FTransform::Identity;if (bApplyComponentToWorld){//就是这个和trajectory 联系上了ComponentToWorld = Trajectory.GetSampleAtTime(Time, bExtrapolate).GetTransform();ReferenceBoneIndexType = ComponentSpaceIndexType;}const FPoseData& ReadPoseData = GetReadPoseData();const int32 NumEntries = ReadPoseData.Entries.Num();if (NumEntries > 0){int32 NextIdx = 0;int32 PrevIdx = 0;if (NumEntries > 1){const int32 LowerBoundIdx = LowerBound(ReadPoseData.Entries.begin(), ReadPoseData.Entries.end(), Time, [](const FPoseHistoryEntry& Entry, float Value) { return Value > Entry.AccumulatedSeconds; });NextIdx = FMath::Clamp(LowerBoundIdx, 1, NumEntries - 1);PrevIdx = NextIdx - 1;}const FPoseHistoryEntry& PrevEntry = ReadPoseData.Entries[PrevIdx];const FPoseHistoryEntry& NextEntry = ReadPoseData.Entries[NextIdx];bSuccess = LerpEntries(Time, bExtrapolate, PrevEntry, NextEntry, BoneIndexSkeleton, ReadPoseData.LastUpdateSkeleton.Get(), ReadPoseData.BoneToTransformMap, BoneIndexType, ReferenceBoneIndexType, OutBoneTransform);if (bApplyComponentToWorld){OutBoneTransform *= ComponentToWorld;}}else{OutBoneTransform = ComponentToWorld;}return bSuccess;
}

关于pose history

FPoseHistory 里面有    FPoseSearchQueryTrajectory Trajectory;

就是

void FAnimNode_PoseSearchHistoryCollector::Evaluate_AnyThread(FPoseContext& Output)
{DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(Evaluate_AnyThread);ANIM_MT_SCOPE_CYCLE_COUNTER_VERBOSE(PoseSearchHistoryCollector, !IsInGameThread());check(Output.AnimInstanceProxy);Super::Evaluate_AnyThread(Output);Source.Evaluate(Output);const bool bNeedsReset = bResetOnBecomingRelevant && UpdateCounter.HasEverBeenUpdated() && !UpdateCounter.WasSynchronizedCounter(Output.AnimInstanceProxy->GetUpdateCounter());FCSPose<FCompactPose> ComponentSpacePose;ComponentSpacePose.InitPose(Output.Pose);TArray<FBoneIndexType> RequiredBones;if (bCacheBones){RequiredBones = GetRequiredBones(Output.AnimInstanceProxy);}PoseHistory.EvaluateComponentSpace_AnyThread(Output.AnimInstanceProxy->GetDeltaSeconds(), ComponentSpacePose, bStoreScales,RootBoneRecoveryTime, RootBoneTranslationRecoveryRatio, RootBoneRotationRecoveryRatio, bNeedsReset, bCacheBones, RequiredBones, Output.Curve, MakeConstArrayView(CollectedCurves));bCacheBones = false;#if ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUGFColor Color;
#if WITH_EDITORONLY_DATAColor = DebugColor.ToFColor(true);
#else // WITH_EDITORONLY_DATAColor = FLinearColor::Red.ToFColor(true);
#endif // WITH_EDITORONLY_DATAPoseHistory.DebugDraw(*Output.AnimInstanceProxy, Color);
#endif // ENABLE_DRAW_DEBUG && ENABLE_ANIM_DEBUG
}

FPoseHistory中Trajectory能确定控件的世界空间下的 pos和rotation

再配合CollectedBones确定的骨骼的历史记录

就能确定某个骨骼的历史 的世界坐标

来作为选择某个pose的支撑数据

在FSearchContext中

const IPoseHistory* GetPoseHistory(const FRole& Role) const { return PoseHistories[RoleToIndex[Role]]; }

资产方面 最重要的是要建立 pose search database 

schema最重要的是定义channel,从不同的维度去解析查找符合预期的pose

各种channel的定义

http://www.lryc.cn/news/501064.html

相关文章:

  • 【从零开始的LeetCode-算法】383. 赎金信
  • 记录模板学习(持续更新)
  • Android hid 数据传输(device 端 )
  • MaxEnt模型在物种分布模拟中如何应用?R语言+MaxEnt模型融合物种分布模拟、参数优化方法、结果分析制图与论文写作
  • 第3章:文本样式 --[CSS零基础入门]
  • mysql 架构详解
  • 无代码探索AI大模型:腾讯云函数计算的卓越实践
  • 解决Ubuntu在VMware关机时,老是一个光标在那里闪动几分钟,才能关机的问题
  • word poi-tl 图表功能增强,插入图表折线图、柱状图、饼状图
  • 常见网络钓鱼类型
  • 数字图像处理考研考点(持续更新)
  • Spring Cloud Alibaba:一站式微服务解决方案
  • ubuntu16.04部署dify教程
  • JavaWeb文件上传
  • 软件工程——期末复习(3)
  • apache的BeanUtils的Converter被相互污染覆盖问题
  • TCP的“可靠性”(上)
  • 超标量处理器设计笔记(5)虚拟存储器、地址转换、page fault
  • SparkSQL 读写数据攻略:从基础到实战
  • react 使用状态管理调用列表接口渲染列表(包含条件查询,统一使用查询按钮,重置功能),避免重复多次调用接口的方法
  • Stable Audio Open模型部署教程:用AI打造独家节拍,让声音焕发新活力!
  • 加油站-(贪心算法)
  • k8s-持久化存储PV与PVC(1)
  • Linux Red Hat Enterprise
  • 《中型 Vue 项目:挑战与成长》
  • 配置 DNS over HTTPS阻止DNS污染
  • Facebook广告文案流量秘诀
  • 22. 五子棋小游戏
  • fastadmin框架同时使用 阿里云oss和阿里云点播
  • Java-JMX 组件架构即详解