vault backup: 2023-11-07 17:47:51
This commit is contained in:
parent
53ee3a10cc
commit
66fe7b2504
16
.obsidian/plugins/various-complements/data.json
vendored
16
.obsidian/plugins/various-complements/data.json
vendored
@ -78,6 +78,22 @@
|
|||||||
"lastUpdated": 1699258527676
|
"lastUpdated": 1699258527676
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"使用最终finalyOffset": {
|
||||||
|
"使用最终finalyOffset": {
|
||||||
|
"currentFile": {
|
||||||
|
"count": 1,
|
||||||
|
"lastUpdated": 1699348533203
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Target": {
|
||||||
|
"Target": {
|
||||||
|
"currentFile": {
|
||||||
|
"count": 1,
|
||||||
|
"lastUpdated": 1699348599323
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
title: Untitled
|
title: UE5 MotionMatching 教程
|
||||||
date: 2023-07-07 10:56:07
|
date: 2023-07-07 10:56:07
|
||||||
excerpt: 摘要
|
excerpt: 摘要
|
||||||
tags:
|
tags:
|
@ -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,};
|
//"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
|
# 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<FBoneChain>& srcRtgChains = SourceIKRig->GetRetargetChains();
|
||||||
|
const TArray<FBoneChain>& 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<UIKRigProcessor>();
|
||||||
|
SourceIKRigProcessor->Initialize(SourceIKRig,SourceIKRig->GetPreviewMesh());
|
||||||
|
UIKRigProcessor* TargetIKRigProcessor=NewObject<UIKRigProcessor>();
|
||||||
|
TargetIKRigProcessor->Initialize(TargetIKRig,TargetIKRig->GetPreviewMesh());
|
||||||
|
const FIKRigSkeleton& srcRigSkeleton = SourceIKRigProcessor->GetSkeleton();
|
||||||
|
const FIKRigSkeleton& tgtRigSkeleton = TargetIKRigProcessor->GetSkeleton();
|
||||||
|
|
||||||
|
for(TObjectPtr<URetargetChainSettings> 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<srcIkRigRtgChainDetail.ikRigRetargetMap.Num()-1 && tgtId<tgtIkRigRtgChainDetail.ikRigRetargetMap.Num()-1;) {
|
||||||
|
auto [srcIkRigId, srcRtgId, srcLenToNext, srcTotalLenUntilCurrent] = srcIkRigRtgChainDetail.ikRigRetargetMap[srcId];
|
||||||
|
auto [tgtIkRigId, tgtRtgId, tgtLenToNext, tgtTotalLenUntilCurrent] = tgtIkRigRtgChainDetail.ikRigRetargetMap[tgtId];
|
||||||
|
|
||||||
|
float srcTotalLenToNextPercent = (srcTotalLenUntilCurrent + srcLenToNext) / srcIkRigRtgChainDetail.totalLength;
|
||||||
|
float tgtTotalLenToNextPercent = (tgtTotalLenUntilCurrent + tgtLenToNext) / tgtIkRigRtgChainDetail.totalLength;
|
||||||
|
|
||||||
|
if(tgtTotalLenToNextPercent > 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<FName>{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;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Loading…
x
Reference in New Issue
Block a user