200 lines
8.2 KiB
Markdown
200 lines
8.2 KiB
Markdown
---
|
||
title: UE5商城动画重定向插件笔记
|
||
date: 2023-09-05 12:02:29
|
||
excerpt:
|
||
tags:
|
||
- AnimationRetargeting
|
||
rating: ⭐
|
||
---
|
||
# MixamoAnimationRetargeting
|
||
主要逻辑位于:FMixamoSkeletonRetargeter
|
||
- UE4MannequinToMixamo_BoneNamesMapping
|
||
- UE4MannequinToMixamo_ChainNamesMapping
|
||
- UE5MannequinToMixamo_BoneNamesMapping
|
||
- UE5MannequinToMixamo_ChainNamesMapping
|
||
|
||
重定向逻辑位于:FMixamoSkeletonRetargeter::Retarget()
|
||
|
||
## 生成TPose
|
||
```c++
|
||
static const TArray<FName> Mixamo_PreserveComponentSpacePose_BoneNames = {
|
||
"Head",
|
||
"LeftToeBase",
|
||
"RightToeBase"
|
||
|
||
#ifdef MAR_UPPERARMS_PRESERVECS_EXPERIMENTAL_ENABLE_
|
||
,"RightShoulder"
|
||
,"RightArm" ,"LeftShoulder" ,"LeftArm"#endif
|
||
};
|
||
|
||
static const TArray<TPair<FName, FName>> Mixamo_ParentChildBoneNamesToBypassOneChildConstraint = {
|
||
{"LeftUpLeg", "LeftLeg"},
|
||
{"LeftLeg", "LeftFoot"},
|
||
{"LeftFoot", "LeftToeBase"},
|
||
{"LeftToeBase", "LeftToe_End"},
|
||
{"RightUpLeg", "RightLeg"},
|
||
{"RightLeg", "RightFoot"},
|
||
{"RightFoot", "RightToeBase"},
|
||
{"RightToeBase", "RightToe_End"},
|
||
{"Hips", "Spine"}, // Heuristic to try to align better the part.
|
||
{"Spine", "Spine1"},
|
||
{"Spine1", "Spine2"},
|
||
{"Spine2", "Neck"}, // Heuristic to try to align better the part.
|
||
{"Neck", "Head"},
|
||
{"Head", "HeadTop_End"},
|
||
{"LeftShoulder", "LeftArm"},
|
||
{"LeftArm", "LeftForeArm"},
|
||
{"LeftForeArm", "LeftHand"},
|
||
{"LeftHand", "LeftHandMiddle1"}, // Heuristic to try to align better the part.
|
||
{"LeftHandIndex1", "LeftHandIndex2"},
|
||
{"LeftHandIndex2", "LeftHandIndex3"},
|
||
{"LeftHandIndex3", "LeftHandIndex4"},
|
||
{"LeftHandMiddle1", "LeftHandMiddle2"},
|
||
{"LeftHandMiddle2", "LeftHandMiddle3"},
|
||
{"LeftHandMiddle3", "LeftHandMiddle4"},
|
||
{"LeftHandPinky1", "LeftHandPinky2"},
|
||
{"LeftHandPinky2", "LeftHandPinky3"},
|
||
{"LeftHandPinky3", "LeftHandPinky4"},
|
||
{"LeftHandRing1", "LeftHandRing2"},
|
||
{"LeftHandRing2", "LeftHandRing3"},
|
||
{"LeftHandRing3", "LeftHandRing4"},
|
||
{"LeftHandThumb1", "LeftHandThumb2"},
|
||
{"LeftHandThumb2", "LeftHandThumb3"},
|
||
{"LeftHandThumb3", "LeftHandThumb4"},
|
||
{"RightShoulder", "RightArm"},
|
||
{"RightArm", "RightForeArm"},
|
||
{"RightForeArm", "RightHand"},
|
||
{"RightHand", "RightHandMiddle1"}, // Heuristic to try to align better the part.
|
||
{"RightHandIndex1", "RightHandIndex2"},
|
||
{"RightHandIndex2", "RightHandIndex3"},
|
||
{"RightHandIndex3", "RightHandIndex4"},
|
||
{"RightHandMiddle1", "RightHandMiddle2"},
|
||
{"RightHandMiddle2", "RightHandMiddle3"},
|
||
{"RightHandMiddle3", "RightHandMiddle4"},
|
||
{"RightHandPinky1", "RightHandPinky2"},
|
||
{"RightHandPinky2", "RightHandPinky3"},
|
||
{"RightHandPinky3", "RightHandPinky4"},
|
||
{"RightHandRing1", "RightHandRing2"},
|
||
{"RightHandRing2", "RightHandRing3"},
|
||
{"RightHandRing3", "RightHandRing4"},
|
||
{"RightHandThumb1", "RightHandThumb2"},
|
||
{"RightHandThumb2", "RightHandThumb3"},
|
||
{"RightHandThumb3", "RightHandThumb4"}
|
||
};
|
||
|
||
RetargetBasePose(
|
||
SkeletalMeshes,
|
||
ReferenceSkeleton,
|
||
Mixamo_PreserveComponentSpacePose_BoneNames,
|
||
UEMannequinToMixamo_BoneNamesMapping.GetInverseMapper(),
|
||
Mixamo_ParentChildBoneNamesToBypassOneChildConstraint,
|
||
/*bApplyPoseToRetargetBasePose=*/true,
|
||
UIKRetargeterController::GetController(IKRetargeter_UEMannequinToMixamo)
|
||
);
|
||
```
|
||
|
||
## 判断骨骼结构是否符合要求
|
||
```c++
|
||
bool FMixamoSkeletonRetargeter::IsMixamoSkeleton(const USkeleton * Skeleton) const
|
||
{
|
||
// We consider a Skeleton "coming from Mixamo" if it has at least X% of the expected bones.
|
||
const float MINIMUM_MATCHING_PERCENTAGE = .75f;
|
||
|
||
// Convert the array of expected bone names (TODO: cache it...).
|
||
TArray<FName> BoneNames;
|
||
UE4MannequinToMixamo_BoneNamesMapping.GetDestination(BoneNames);
|
||
// Look for and count the known Mixamo bones (see comments on IndexLastCheckedMixamoBone and UEMannequinToMixamo_BonesMapping).
|
||
constexpr int32 NumBones = (IndexLastCheckedMixamoBone + 1) / 2;
|
||
BoneNames.SetNum(NumBones);
|
||
FSkeletonMatcher SkeletonMatcher(BoneNames, MINIMUM_MATCHING_PERCENTAGE);
|
||
return SkeletonMatcher.IsMatching(Skeleton);
|
||
}
|
||
|
||
bool FSkeletonMatcher::IsMatching(const USkeleton* Skeleton) const
|
||
{
|
||
// No Skeleton, No matching...
|
||
if (Skeleton == nullptr)
|
||
{ return false;
|
||
}
|
||
const int32 NumExpectedBones = BoneNames.Num();
|
||
int32 nMatchingBones = 0;
|
||
const FReferenceSkeleton & SkeletonRefSkeleton = Skeleton->GetReferenceSkeleton();
|
||
for (int32 i = 0; i < NumExpectedBones; ++i)
|
||
{ const int32 BoneIndex = SkeletonRefSkeleton.FindBoneIndex(BoneNames[i]);
|
||
if (BoneIndex != INDEX_NONE)
|
||
{ ++nMatchingBones;
|
||
} } const float MatchedPercentage = float(nMatchingBones) / float(NumExpectedBones);
|
||
|
||
return MatchedPercentage >= MinimumMatchingPerc;
|
||
}
|
||
```
|
||
|
||
|
||
|
||
enum class ETargetSkeletonType
|
||
{
|
||
ST_UNKNOWN = 0,
|
||
ST_UE4_MANNEQUIN,
|
||
ST_UE5_MANNEQUIN,
|
||
|
||
ST_SIZE
|
||
};
|
||
|
||
static const char* const kUE4MannequinToMixamo_BoneNamesMapping[] = {
|
||
// UE Mannequin bone name MIXAMO bone name
|
||
"root", "root",
|
||
"pelvis", "Hips",
|
||
"spine_01", "Spine",
|
||
"spine_02", "Spine1",
|
||
"spine_03", "Spine2",
|
||
"neck_01", "Neck",
|
||
"head", "head",
|
||
"clavicle_l", "LeftShoulder",
|
||
"upperarm_l", "LeftArm",
|
||
"lowerarm_l", "LeftForeArm",
|
||
"hand_l", "LeftHand",
|
||
"clavicle_r", "RightShoulder",
|
||
"upperarm_r", "RightArm",
|
||
"lowerarm_r", "RightForeArm",
|
||
"hand_r", "RightHand",
|
||
"thigh_l", "LeftUpLeg",
|
||
"calf_l", "LeftLeg",
|
||
"foot_l", "LeftFoot",
|
||
"ball_l", "LeftToeBase",
|
||
"thigh_r", "RightUpLeg",
|
||
"calf_r", "RightLeg",
|
||
"foot_r", "RightFoot",
|
||
"ball_r", "RightToeBase",
|
||
// From here, ignored to determine if a skeleton is from Mixamo.
|
||
// From here, ignored to determine if a skeleton is from UE Mannequin. "index_01_l", "LeftHandIndex1",
|
||
"index_02_l", "LeftHandIndex2",
|
||
"index_03_l", "LeftHandIndex3",
|
||
"middle_01_l", "LeftHandMiddle1",
|
||
"middle_02_l", "LeftHandMiddle2",
|
||
"middle_03_l", "LeftHandMiddle3",
|
||
"pinky_01_l", "LeftHandPinky1",
|
||
"pinky_02_l", "LeftHandPinky2",
|
||
"pinky_03_l", "LeftHandPinky3",
|
||
"ring_01_l", "LeftHandRing1",
|
||
"ring_02_l", "LeftHandRing2",
|
||
"ring_03_l", "LeftHandRing3",
|
||
"thumb_01_l", "LeftHandThumb1",
|
||
"thumb_02_l", "LeftHandThumb2",
|
||
"thumb_03_l", "LeftHandThumb3",
|
||
"index_01_r", "RightHandIndex1",
|
||
"index_02_r", "RightHandIndex2",
|
||
"index_03_r", "RightHandIndex3",
|
||
"middle_01_r", "RightHandMiddle1",
|
||
"middle_02_r", "RightHandMiddle2",
|
||
"middle_03_r", "RightHandMiddle3",
|
||
"pinky_01_r", "RightHandPinky1",
|
||
"pinky_02_r", "RightHandPinky2",
|
||
"pinky_03_r", "RightHandPinky3",
|
||
"ring_01_r", "RightHandRing1",
|
||
"ring_02_r", "RightHandRing2",
|
||
"ring_03_r", "RightHandRing3",
|
||
"thumb_01_r", "RightHandThumb1",
|
||
"thumb_02_r", "RightHandThumb2",
|
||
"thumb_03_r", "RightHandThumb3",
|
||
// Un-mapped bones (at the moment). Here for reference.
|
||
//"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,}; |