BlueRoseNote/03-UnrealEngine/Animation/UE5商城动画重定向插件笔记.md

8.2 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
UE5商城动画重定向插件笔记 2023-09-05 12:02:29
AnimationRetargeting

MixamoAnimationRetargeting

主要逻辑位于FMixamoSkeletonRetargeter

  • UE4MannequinToMixamo_BoneNamesMapping
  • UE4MannequinToMixamo_ChainNamesMapping
  • UE5MannequinToMixamo_BoneNamesMapping
  • UE5MannequinToMixamo_ChainNamesMapping

重定向逻辑位于FMixamoSkeletonRetargeter::Retarget()

生成TPose

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

判断骨骼结构是否符合要求

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,};

EasyPose