diff --git a/.obsidian/plugins/various-complements/data.json b/.obsidian/plugins/various-complements/data.json index 6385330..1c7646c 100644 --- a/.obsidian/plugins/various-complements/data.json +++ b/.obsidian/plugins/various-complements/data.json @@ -78,6 +78,22 @@ "lastUpdated": 1699258527676 } } + }, + "使用最终finalyOffset": { + "使用最终finalyOffset": { + "currentFile": { + "count": 1, + "lastUpdated": 1699348533203 + } + } + }, + "Target": { + "Target": { + "currentFile": { + "count": 1, + "lastUpdated": 1699348599323 + } + } } } } \ No newline at end of file diff --git a/03-UnrealEngine/Animation/MotionMarch/UE5 MotionMatchiing 教程.md b/03-UnrealEngine/Animation/MotionMarch/UE5 MotionMatching 教程.md similarity index 88% rename from 03-UnrealEngine/Animation/MotionMarch/UE5 MotionMatchiing 教程.md rename to 03-UnrealEngine/Animation/MotionMarch/UE5 MotionMatching 教程.md index e9dcad7..7a35209 100644 --- a/03-UnrealEngine/Animation/MotionMarch/UE5 MotionMatchiing 教程.md +++ b/03-UnrealEngine/Animation/MotionMarch/UE5 MotionMatching 教程.md @@ -1,5 +1,5 @@ --- -title: Untitled +title: UE5 MotionMatching 教程 date: 2023-07-07 10:56:07 excerpt: 摘要 tags: diff --git a/03-UnrealEngine/Animation/UE5商城动画重定向插件笔记.md b/03-UnrealEngine/Animation/UE5商城动画重定向插件笔记.md index 237cf0e..30fd8fb 100644 --- a/03-UnrealEngine/Animation/UE5商城动画重定向插件笔记.md +++ b/03-UnrealEngine/Animation/UE5商城动画重定向插件笔记.md @@ -200,3 +200,177 @@ static const char* const kUE4MannequinToMixamo_BoneNamesMapping[] = { //"lowerarm_twist_01_l", nullptr, //"upperarm_twist_01_l", nullptr, //"lowerarm_twist_01_r", nullptr, //"upperarm_twist_01_r", nullptr, //"calf_twist_01_l", nullptr, //"thigh_twist_01_l", nullptr, //"calf_twist_01_r", nullptr, //"thigh_twist_01_r", nullptr, //"ik_foot_root", nullptr, //"ik_foot_l", nullptr, //"ik_foot_r", nullptr, //"ik_hand_root", nullptr, //"ik_hand_gun", nullptr, //"ik_hand_l", nullptr, //"ik_hand_r", nullptr,}; # EasyPose +主要的逻辑都是在MatchPose()中,作者还是编写几个辅助函数以及IKRigRetargetChainDetail结构体。 + +定义srcRtgSkeleton、tgtRtgSkeleton之外还定义了orgTgtRtgSkeleton,主要是为了在最后结算最终的Offset。UE的重定向Pose数据使用分离的方式存储(Maya也是这样,但UE输出的FBX却不是),分为RefPose与RetargetPoseOffset。 + +- 初始化数据 +- 初始化IKRigRetargetChainDetail srcIkRigRtgChainDetail & IKRigRetargetChainDetail tgtIkRigRtgChainDetail结构体以用于之后的计算 +- 遍历骨骼链 + - 根据当前Source & Target ID计算当前长度的百分比,如果TargetID > SourceID,则**srcId++**,并且跳过当前循环。(主要是考虑到骨骼链内骨骼数量不对等的情况) + - 取得当前SourceBoneLocation以及SourceNextBoneLocation来计算Direction + - 重新生成tgtRtgSkeleton、orgTgtRtgSkeleton的重定向Pose(**WorldSpace**) + - **之后是核心MatchPose逻辑** + - 计算Direction与TargetBoneDirection的旋转偏移,转成**LocalSpace**之后应用到tgtRtgSkeleton的对应Target骨骼上。 + - 在X 或者 Y轴对齐的情况下 计算Source ID与Target ID 对应骨骼的旋转Offset,转成**LocalSpace**之后应用到tgtRtgSkeleton的对应Target骨骼上。 + - 计算Direction与TargetBoneDirection的旋转Offset,转成**LocalSpace**之后应用到tgtRtgSkeleton的对应Target骨骼上。(以上上步相同) + - 使用tgtRtgSkeleton、orgTgtRtgSkeleton计算最终的旋转Offset。 + - 使用最终FinalyOffset 应用到 Target的IKRig 的 FIKRetargetPose。 + - **tgtId++** + + +以下是完整的代码: +```c++ +int32 UAutomationUtilityLibrary::MatchPose(UIKRetargeter *iKRetargeter) { + int totalRotationsCount = 0; + if(iKRetargeter == nullptr) return 0; + + auto Target = ERetargetSourceOrTarget::Target; + auto Source = ERetargetSourceOrTarget::Source; + + UIKRetargeterController *ikRtgCtrl = UIKRetargeterController::GetController(iKRetargeter); + if(ikRtgCtrl == nullptr) return 0; + + const UIKRigDefinition* SourceIKRig = iKRetargeter->GetSourceIKRig(); + const UIKRigDefinition* TargetIKRig = iKRetargeter->GetTargetIKRig(); + + const TArray& srcRtgChains = SourceIKRig->GetRetargetChains(); + const TArray& tgtRtgChains = TargetIKRig->GetRetargetChains(); + + const FName& srcRtgRootName = SourceIKRig->GetRetargetRoot(); + const FName& tgtRtgRootName = TargetIKRig->GetRetargetRoot(); + + FRetargetSkeleton srcRtgSkeleton; + FRetargetSkeleton tgtRtgSkeleton, orgTgtRtgSkeleton; + + auto tgtRtgPoseName = iKRetargeter->GetCurrentRetargetPoseName(ERetargetSourceOrTarget::Target); + FIKRetargetPose const *tgtRtgPose = iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Target); + + srcRtgSkeleton.Initialize( + ikRtgCtrl->GetPreviewMesh(Source), + srcRtgChains, + iKRetargeter->GetCurrentRetargetPoseName(ERetargetSourceOrTarget::Source), + iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Source), + srcRtgRootName); + tgtRtgSkeleton.Initialize( + ikRtgCtrl->GetPreviewMesh(Target), + tgtRtgChains, + tgtRtgPoseName, + iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Target), + tgtRtgRootName); + orgTgtRtgSkeleton.Initialize( + ikRtgCtrl->GetPreviewMesh(Target), + tgtRtgChains, + tgtRtgPoseName, + iKRetargeter->GetCurrentRetargetPose(ERetargetSourceOrTarget::Target), + tgtRtgRootName); + + UIKRigProcessor* SourceIKRigProcessor=NewObject(); + SourceIKRigProcessor->Initialize(SourceIKRig,SourceIKRig->GetPreviewMesh()); + UIKRigProcessor* TargetIKRigProcessor=NewObject(); + TargetIKRigProcessor->Initialize(TargetIKRig,TargetIKRig->GetPreviewMesh()); + const FIKRigSkeleton& srcRigSkeleton = SourceIKRigProcessor->GetSkeleton(); + const FIKRigSkeleton& tgtRigSkeleton = TargetIKRigProcessor->GetSkeleton(); + + for(TObjectPtr chainSettings : iKRetargeter->GetAllChainSettings()) { + if(chainSettings == nullptr) continue; + + const FBoneChain *srcBoneChain = SourceIKRig->GetRetargetChainByName(chainSettings->SourceChain); + const FBoneChain *tgtBoneChain = TargetIKRig->GetRetargetChainByName(chainSettings->TargetChain); + if(srcBoneChain == nullptr || tgtBoneChain == nullptr) continue; + auto chainRoot = FName(TEXT("Root")); + if(chainSettings->SourceChain == chainRoot || chainSettings->TargetChain == chainRoot) { + continue; + } + IKRigRetargetChainDetail srcIkRigRtgChainDetail{srcBoneChain, srcRigSkeleton, srcRtgSkeleton}; + IKRigRetargetChainDetail tgtIkRigRtgChainDetail{tgtBoneChain, tgtRigSkeleton, tgtRtgSkeleton}; + + if(srcIkRigRtgChainDetail.ikRigRetargetMap.IsEmpty() || tgtIkRigRtgChainDetail.ikRigRetargetMap.IsEmpty()) { + //UE_LOG(AutomationFunctionLibrary, Log, TEXT("Chain Empty: %s: %d, %s: %d"), *srcIkRigRtgChainDetail.boneChain->ChainName.ToString(), srcIkRigRtgChainDetail.ikRigRetargetMap.Num(), *tgtIkRigRtgChainDetail.boneChain->ChainName.ToString(), tgtIkRigRtgChainDetail.ikRigRetargetMap.Num()); + continue; + } + auto srcWatermarkLoc = srcRtgSkeleton.RetargetGlobalPose[srcIkRigRtgChainDetail.ikRigRetargetMap[0].retargetId].GetTranslation(); + int rotationsCount = 0; + + for(int32 srcId=0,tgtId=0; srcId srcTotalLenToNextPercent) { + srcId++; + continue; + } else { + auto tgtRtgBoneName = tgtRigSkeleton.BoneNames[tgtIkRigId]; + + auto diffLenPercentToNext = 1 - ((srcTotalLenToNextPercent - tgtTotalLenToNextPercent) * srcIkRigRtgChainDetail.totalLength / srcLenToNext); + auto srcCurrentBoneLoc = srcRtgSkeleton.RetargetGlobalPose[srcIkRigRtgChainDetail.ikRigRetargetMap[srcId].retargetId].GetTranslation(); + auto srcNextBoneLoc = srcRtgSkeleton.RetargetGlobalPose[srcIkRigRtgChainDetail.ikRigRetargetMap[srcId+1].retargetId].GetTranslation(); + + auto nextPoint = (srcNextBoneLoc - srcCurrentBoneLoc) * (diffLenPercentToNext) + srcCurrentBoneLoc; + auto direction = (nextPoint - srcWatermarkLoc).GetSafeNormal(); + srcWatermarkLoc = nextPoint; + + //ResetRetargetPose + ikRtgCtrl->ResetRetargetPose(tgtRtgPoseName, TArray{tgtRtgBoneName}, Target); + tgtRtgSkeleton.GenerateRetargetPose(tgtRtgPoseName, tgtRtgPose, tgtRtgRootName); + orgTgtRtgSkeleton.GenerateRetargetPose(tgtRtgPoseName, tgtRtgPose, tgtRtgRootName); + //根据骨骼链中当前SourceBone与下一个SourceBone,计算出Direction(世界坐标) + //GetBoneDirectionOffset 调用GetBoneDirectionOffset()计算出旋转偏移,再转换成LocalSpace旋转,再设置给骨骼。 + auto const &tgtGlobalTransform = tgtRtgSkeleton.RetargetGlobalPose[tgtRtgId]; + auto rotOffset = GetBoneDirectionOffset(tgtRtgSkeleton, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId].retargetId, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId+1].retargetId, direction); + rotOffset = GetBoneSpaceOffset(rotOffset, tgtGlobalTransform); + //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching direction: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString()); + + if(NotEmptyOrIdentity(rotOffset)) { + //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched direction: %s"), *tgtRtgBoneName.ToString()); + ApplyRotationOffset(rotOffset, tgtRtgId, tgtRtgSkeleton); + } + //在X 或者 Y轴对齐的情况下 计算Source与Target 对应骨骼的旋转偏差,在转化成LocalSpace后再应用 + rotOffset = GetXYRotationOffsetIfAligned(tgtRtgSkeleton.RetargetGlobalPose[tgtRtgId].GetRotation(), srcRtgSkeleton.RetargetGlobalPose[srcRtgId].GetRotation()); + rotOffset = GetBoneSpaceOffset(rotOffset, tgtGlobalTransform); + //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching XY: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString()); + + if(NotEmptyOrIdentity(rotOffset)) { + //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched XY: %s"), *tgtRtgBoneName.ToString()); + ApplyRotationOffset(rotOffset, tgtRtgId, tgtRtgSkeleton); + } + //与第一步相同 + rotOffset = GetBoneDirectionOffset(tgtRtgSkeleton, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId].retargetId, tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId+1].retargetId, direction); + rotOffset = GetBoneSpaceOffset(rotOffset, tgtGlobalTransform); + //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching direction 2nd: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString()); + + if(NotEmptyOrIdentity(rotOffset)) { + //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched direction 2nd: %s"), *tgtRtgBoneName.ToString()); + ApplyRotationOffset(rotOffset, tgtRtgId, tgtRtgSkeleton); + } + //计算最终的旋转offset + auto finalOffset = GetRotationDifference(orgTgtRtgSkeleton.RetargetLocalPose[tgtRtgId].GetRotation(), tgtRtgSkeleton.RetargetLocalPose[tgtRtgId].GetRotation()); + + if(NotEmptyOrIdentity(finalOffset)) { + rotationsCount++; + ikRtgCtrl->SetRotationOffsetForRetargetPoseBone(tgtRtgBoneName, finalOffset, Target); + } tgtId++; + } + } + auto [srcIkRigId, srcRtgId, srcLenToNext, srcTotalLenUntilCurrent] = srcIkRigRtgChainDetail.ikRigRetargetMap.Last(); + auto [tgtIkRigId, tgtRtgId, tgtLenToNext, tgtTotalLenUntilCurrent] = tgtIkRigRtgChainDetail.ikRigRetargetMap.Last(); + auto tgtRtgBoneName = tgtRigSkeleton.BoneNames[tgtIkRigId]; + + auto rotOffset = GetRotationOffsetIfAligned(tgtRtgSkeleton.RetargetGlobalPose[tgtRtgId].GetRotation(), srcRtgSkeleton.RetargetGlobalPose[srcRtgId].GetRotation()); + //UE_LOG(AutomationFunctionLibrary, VeryVerbose, TEXT("Matching rotation: %s, %s"), *tgtRtgBoneName.ToString(), *rotOffset.ToString()); + + if(NotEmptyOrIdentity(rotOffset)) { + rotationsCount++; + //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Matched rotation: %s"), *tgtRtgBoneName.ToString()); + ikRtgCtrl->SetRotationOffsetForRetargetPoseBone(tgtRtgBoneName, ikRtgCtrl->GetRotationOffsetForRetargetPoseBone(tgtRtgBoneName, Target) * rotOffset, Target); + } if(rotationsCount > 0) { + //UE_LOG(AutomationFunctionLibrary, Verbose, TEXT("Chain: %s - %s: %d rotations"), *chainSettings->SourceChain.ToString(), *chainSettings->TargetChain.ToString(), rotationsCount); + totalRotationsCount += rotationsCount; + } } /* + if(totalRotationsCount > 0) { UE_LOG(AutomationFunctionLibrary, Log, TEXT("Asset \"%s\": made %d adjustments with pose: \"%s\"."), *iKRetargeter->GetName(), totalRotationsCount, *ikRtgCtrl->GetCurrentRetargetPoseName(Target).ToString()); } else { UE_LOG(AutomationFunctionLibrary, Log, TEXT("Asset \"%s\": pose \"%s\" fit already!"), *iKRetargeter->GetName(), *ikRtgCtrl->GetCurrentRetargetPoseName(Target).ToString()); } */ // ikRtgCtrl->SetRotationOffsetForRetargetPoseBone(MeshRefSkeleton.GetBoneName(EditBoneIndex), Q, ERetargetSourceOrTarget::Target); + return totalRotationsCount; +} +```