513 lines
17 KiB
Markdown
Raw Normal View History

2024-10-23 16:38:20 +08:00
# 相关类
- TsArkitDataReceiver(ArkitDataReceiver)
- TsChingmuMocapReceiverActor(ChingmuMocapReceiverActor)
- TsMotionReceiverActor(MotionReceiverActor) => BP_MotionReceiver定义了MotionNetConfig.ini。
- TsMotionSenderActor(MotionSenderActor)
# TsChingmuMocapReceiverActor
2024-10-30 16:38:24 +08:00
***地图里只会有一个生成的TsChingmuMocapReceiverActor来管理动捕数据接收***
2024-10-23 16:38:20 +08:00
1. Init()在Server才会Spawn TsChingmuMocapReceiverActor。
2. ConnectChingMu()**ChingmuComp.StartConnectServer()**
3. Multicast_AligmMotionTime()寻找场景中的BP_MotionReceiver并且调用Receiver.AlignTimeStamp()。
## ChingmuMocapReceiverActor
2024-10-30 16:38:24 +08:00
核心逻辑:
- ***FChingmuThread::Run()***
- ***AChingmuMocapReceiverActor::Tick()***
- AChingmuMocapReceiverActor::DoSample()
2024-10-28 19:23:10 +08:00
```c++
void AChingmuMocapReceiverActor::BeginPlay()
{
Super::BeginPlay();
MaxHumanCount = 10;
MaxRigidBodyCount = 10;
CacheLimit = 240;
SampledHumanData = NewObject<UMocapFrameData>();
ThreadInterval = 0.002;
BackIndexCount = int64(UMotionUtils::BackSampleTime / (1000.0 / CHINGMU_SERVER_FPS));//BackSampleTime = 100ms CHINGMU_SERVER_FPS =120ms
ChingmuComp = Cast<UChingMUComponent>(GetComponentByClass(UChingMUComponent::StaticClass()));
if (ChingmuComp == nullptr)
{
UE_LOG(LogTemp, Error, TEXT("Chingmu Component is missing!!"));
}
Thread = new FChingmuThread("Chingmu Data Thread", this);
Sender = GetMotionSender();
}
```
2024-10-30 19:11:55 +08:00
FChingmuThread::Run()中处理完[[#ST_MocapFrameData]]之后将几个演员动补数据存入FrameQueue之后。在Tick()出队之后数据存入AllHumanFrames/AllRigidBodyFrames。
- AllHumanFrames
- ID
- std::vector<ST_MocapFrameData*> Frames
- ID
- TimeStamp
- FrameIndex
- BonesWorldPos
- BonesLocalRot
2024-10-30 16:38:24 +08:00
```c++
void AChingmuMocapReceiverActor::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
if(!Sender)
{
Sender = GetMotionSender();
}
2024-10-30 19:11:55 +08:00
const auto CurTime = ULiveDirectorStatics::GetUnixTime();//获取当前系统时间
2024-10-30 16:38:24 +08:00
if(UseThread)
{
// 线程方式
// 在数据队列中获取青瞳数据
2024-10-30 19:11:55 +08:00
while (!FrameQueue.IsEmpty())//处理完所有
2024-10-30 16:38:24 +08:00
{
ST_MocapFrameData* Frame;
2024-10-30 19:11:55 +08:00
if (FrameQueue.Dequeue(Frame))//出队
2024-10-30 16:38:24 +08:00
{
2024-10-30 19:11:55 +08:00
PutMocapDataIntoFrameList(Frame);//将帧数数据塞入对应HuamnID/RigidBodyID的AllHumanFrames/AllRigidBodyFrames中。
2024-10-30 16:38:24 +08:00
}
}
}
DoSample(AllHumanFrames);
DoSample(AllRigidBodyFrames);
// 每隔1s计算一次平均包间隔
if (CurTime - LastCheckIntervalTime > 1000)
{
if (AllHumanFrames.Num() > 0)
{
AllHumanFrames[0]->CalculatePackageAverageInterval(this->PackageAverageInterval);
LastCheckIntervalTime = CurTime;
}
}
}
```
2024-10-30 19:11:55 +08:00
### 采样相关逻辑
- ***SampleByTimeStamp***()
2024-10-30 16:38:24 +08:00
```c++
void AChingmuMocapReceiverActor::DoSample(TArray<MocapFrames*>& Frames)
{
for (auto i = 0; i < Frames.Num(); i++)
{
2024-10-30 19:11:55 +08:00
Frames[i]->CheckSize(CacheLimit);//判断当前帧数据是否超过指定长度240帧2~4秒数据移除超出长度的数据。
if (SampleByTimeStamp(Frames[i]->Frames))//对数据进行插值当前插值数据存在SampledHumanData。
2024-10-30 16:38:24 +08:00
{
2024-10-30 19:11:55 +08:00
SendFrameToCharacter();//执行对应的TsChingmuMocapReceiverActor.ts中的逻辑主要是触发一个事件讲数据传递给TsMotionRetargetComponent.ts 或者 TsSceneLiveLinkPropActor.ts动捕道具
2024-10-30 16:38:24 +08:00
}
}
}
2024-10-30 19:11:55 +08:00
class MocapFrames
{
public:
int ID;
std::vector<ST_MocapFrameData*> Frames = {};
public:
MocapFrames(): ID(0)
{
}
bool CheckSize(const int Limit)
{
if (Frames.size() > Limit)
{
const int DeletedCount = Frames.size() / 2;
for (auto i = 0; i < DeletedCount; i++)
{
auto Data = Frames[i];
if (Data)
{
delete Data;
}
Data = nullptr;
}
Frames.erase(Frames.cbegin(), Frames.cbegin() + DeletedCount);
return true;
}
return false;
}
};
2024-10-30 16:38:24 +08:00
```
2024-10-30 19:11:55 +08:00
对数据进行插值,当前插值数据存在**SampledHumanData**。
```c++
bool AChingmuMocapReceiverActor::SampleByTimeStamp(std::vector<ST_MocapFrameData*>& DataList)
{
const int64 SampleTime = ULiveDirectorStatics::GetUnixTime() - UMotionUtils::BackSampleTime;//UMotionUtils::BackSampleTime = 100ms,采样100ms的数据。
int Previous = -1;
int Next = -1;
for (int Index = DataList.size() - 1; Index > 0; Index--)//从Last => First遍历所有数据确定插值的2个数据Index。
{
const ST_MocapFrameData* Data = DataList[Index];
if (Data == nullptr)
{
continue;
}
if (Data->TimeStamp - SampleTime > 0)
{
Next = Index;
}
else
{
Previous = Index;
break;
}
}
if (bShowSampleLog)
{
UE_LOG(LogTemp, Warning, TEXT("prev: %d, next: %d, total: %llu"), Previous, Next, DataList.size());
}
if (Previous != -1 && Next != -1)
{
const auto p = DataList[Previous];
const auto n = DataList[Next];
const float Factor = (n->TimeStamp - p->TimeStamp) > 0
? (1.0 * (SampleTime - p->TimeStamp) / (n->TimeStamp - p->TimeStamp))
: 1.0;
// Bone world pos cannot lerp like this
// It will cause bone length changes all the time
SampledHumanData->ID = p->ID;
SampledHumanData->TimeStamp = SampleTime;
SampledHumanData->FrameIndex = p->FrameIndex;
for (auto Index = 0; Index < 23; Index++)//对23个骨骼进行差值
{
SampledHumanData->BonesWorldPos[Index] = UKismetMathLibrary::VLerp(
p->BonesWorldPos[Index], n->BonesWorldPos[Index], Factor);
SampledHumanData->BonesLocalRot[Index] = UKismetMathLibrary::RLerp(p->BonesLocalRot[Index].Rotator(),
n->BonesLocalRot[Index].Rotator(),
Factor, true).Quaternion();
}
return true;
}
if (Previous != -1)//容错处理全都是Previous数据太旧直接清空。
{
SampledHumanData->CopyFrom(DataList[Previous]);
if(SampleTime - DataList[Previous]->TimeStamp > UMotionUtils::MotionTimeout)
{
// data is too old, clear the data list.
DataList.clear();
}
return true;
}
if (Next != -1)//没有Previous直接复制Next的数据。
{
SampledHumanData->CopyFrom(DataList[Next]);
return true;
}
return false;
}
```
2024-10-28 19:23:10 +08:00
### FChingmuThread
用途为:
2024-10-30 16:38:24 +08:00
- 获取当前系统时间。
- 使用异步Task的方式通过调用**UChingMUComponent::FullBodyMotionCapBaseBonesLocalSpaceRotation()** 来更新每个演员的动捕数据。动捕数据存储在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***中。
- 管理HumanToLastReceiveTime以此管理每个动捕演员的动画数据时长。
- OwnerActor->OnGetHumanData_NotInGameThread()
- 根据当前时间与当前Frames从UChingMUComponent中将数据复制到[[#ST_MocapFrameData]]中。
- 将[[#ST_MocapFrameData]]转换成JSON后使用AMotionSenderActor::OnGetRawMocapData_NotInGameThread()发送。
- 将当前帧数据加入FrameQueue队列。
2024-10-30 19:11:55 +08:00
- 线程睡眠0.001s。以此保证AChingmuMocapReceiverActor::Tick()中可以把数据都处理完。
2024-10-28 19:23:10 +08:00
```c++
uint32 FChingmuThread::Run()
{
FTransform Tmp;
while (bRun)
{
if (OwnerActor && OwnerActor->UseThread && OwnerActor->ChingmuComp && OwnerActor->ChingmuComp->IsConnected())
{
CurTime = ULiveDirectorStatics::GetUnixTime();
// Human
for (auto HumanIndex = 0; HumanIndex < OwnerActor->MaxHumanCount; HumanIndex++)
{
const auto bRes = OwnerActor->ChingmuComp->FullBodyMotionCapBaseBonesLocalSpaceRotation(
OwnerActor->ChingmuFullAddress, HumanIndex, TmpTimeCode);
if (bRes)
{
if (!HumanToLastReceiveTime.Contains(HumanIndex))//空数据处理。
{
HumanToLastReceiveTime.Add(HumanIndex, 0);
}
2024-10-30 16:38:24 +08:00
if (HumanToLastReceiveTime[HumanIndex] != TmpTimeCode.Frames)//判断是否收到新的Frame数据
2024-10-28 19:23:10 +08:00
{
HumanToLastReceiveTime[HumanIndex] = TmpTimeCode.Frames;
OwnerActor->OnGetHumanData_NotInGameThread(HumanIndex, CurTime, TmpTimeCode.Frames);
}
else
{
// get same frame, skip
break;
}
}
}
// Rigidbody
for (auto RigidBodyIndex = OwnerActor->RigidBodyStartIndex; RigidBodyIndex < OwnerActor->RigidBodyStartIndex
+ OwnerActor->MaxRigidBodyCount; RigidBodyIndex++)
{
OwnerActor->ChingmuComp->GetTrackerPoseTC(OwnerActor->ChingmuFullAddress, RigidBodyIndex, Tmp,
TmpTimeCode);
if (!RigidBodyToLastReceiveTransform.Contains(RigidBodyIndex))
{
RigidBodyToLastReceiveTransform.Add(RigidBodyIndex, FTransform::Identity);
}
// 道具的TmpTimeCode.Frames永远为0所以无法用帧数判断
// 改为transform判断
if (!RigidBodyToLastReceiveTransform[RigidBodyIndex].Equals(Tmp))
{
RigidBodyToLastReceiveTransform[RigidBodyIndex] = Tmp;
OwnerActor->OnGetRigidBodyData_NotInGameThread(RigidBodyIndex, Tmp, CurTime, TmpTimeCode.Frames);
}
}
}
if (bRun)
{
FPlatformProcess::Sleep(OwnerActor ? OwnerActor->ThreadInterval : 0.004);
}
else
{
break;
}
}
UE_LOG(LogTemp, Warning, TEXT("%s finish work."), *ThreadName)
return 0;
}
```
2024-10-30 16:38:24 +08:00
## ST_MocapFrameData
2024-10-30 19:11:55 +08:00
- ST_MocapFrameData为动捕数据的原始帧数据。
2024-10-28 19:23:10 +08:00
```c++
#define MOCAP_BONE_COUNT 23
enum E_MotionType
{
Human,
RigidBody
};
enum E_SourceType
{
Mocap,
CMR,
Replay
};
struct ST_MocapFrameData
{
int ID;
int64 TimeStamp;
int FrameIndex;
E_MotionType MotionType;
E_SourceType SourceType;
FVector BonesWorldPos[MOCAP_BONE_COUNT];
FQuat BonesLocalRot[MOCAP_BONE_COUNT];
};
class LIVEDIRECTOR_API UMocapFrameData : public UObject
{
GENERATED_BODY()
public:
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int ID;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<FVector> BonesWorldPos;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
TArray<FQuat> BonesLocalRot;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int64 TimeStamp;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int FrameIndex;
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int MotionType; // 0 human; 1 rigidbody
UPROPERTY(BlueprintReadWrite, EditAnywhere)
int SourceType; // 0 mocap, 1 cmr
public:
void CopyFrom(const ST_MocapFrameData* Other)
{
ID = Other->ID;
TimeStamp = Other->TimeStamp;
FrameIndex = Other->FrameIndex;
MotionType = Other->MotionType;
SourceType = Other->SourceType;
for (auto Index = 0; Index < 23; Index++)
{
BonesWorldPos[Index] = Other->BonesWorldPos[Index];
BonesLocalRot[Index] = Other->BonesLocalRot[Index];
}
}
};
class MocapFrames
{
public:
int ID;
std::vector<ST_MocapFrameData*> Frames = {};
void CalculatePackageAverageInterval(float& Res)
{
if(Frames.size() > 0)
{
auto First = Frames[0];
auto Last = Frames[Frames.size() - 1];
if(Last->FrameIndex > First->FrameIndex)
{
Res = 1.0 * (Last->TimeStamp - First->TimeStamp) / (Last->FrameIndex - First->FrameIndex);
}
}
}
};
```
2024-10-30 12:48:27 +08:00
# MotionCapture(青瞳插件)
实现1个组件与3个动画节点
- [[#ChingMUComponent]]
- [[#AnimNode_ChingMUPose]]:接受骨骼动捕数据。
- [[#AnimNode_ChingMURetargetPose]]:接受重定向后的骨骼动捕数据。
- AnimNode_ChingMURetargetPoseForBuild:
## ***ChingMUComponent***
2024-10-30 14:14:00 +08:00
1. Init
1. BeginPlay()取得ini文件中的配置信息取得当前角色的SkeletonMesh => CharacterSkinMesh;取得BoneName=>BoneIndex Map、TPose状态下骨骼的旋转值、TposeParentBonesRotation。
2. Connect
1. StartConnectServer()motionCapturePlugin->ConnectCommand = "ConnectServer"。具体逻辑会在FMotionCapture::Tick()处理。
2. DisConnectServer()motionCapturePlugin->ConnectCommand = "DisConnect"。
2024-10-30 16:38:24 +08:00
3. [[#CalculateBoneCSRotation()]]
4. [[#FullBodyMotionCapBaseBonesLocalSpaceRotation]]
2024-10-30 14:14:00 +08:00
### CalculateBoneCSRotation
2024-10-30 16:38:24 +08:00
> Get Human Fullbody Tracker data ,including of 23joints localRotation and root joint world Position
1. m_motioncap->CMHuman()调用DLL的CMHumanExtern()获取一个Double数组前3个是RootLocation后面全是Rotation。
2. 计算最终的四元数旋转值。
3. 返回的形参 FQuat* BonesComponentSpaceRotation数组指针。
2024-10-30 14:14:00 +08:00
2024-10-30 16:38:24 +08:00
### FullBodyMotionCapBaseBonesLocalSpaceRotation
相比CalculateBoneCSRotation增加了时间码以及GlobalLocation的动捕数据获取。
1. m_motioncap->CMHuman()调用DLL的CMHumanExtern()获取一个Double数组前3个是RootLocation后面全是Rotation。
2. motionCapturePlugin->CMHumanGlobalRTTC()调用DLL的CMHumanGlobalRTTC()1-24 New Features。计算**VrpnTimeCode**以及**GlobalLocationList**。
2024-10-30 12:48:27 +08:00
2024-10-30 16:38:24 +08:00
数据存在**ChingMUComponent**中的***LocalRotationList***、***GlobalLocationList***。
2024-10-30 12:48:27 +08:00
## FAnimNode_ChingMUPose
1. Initialize_AnyThread():取得**ChingMUComponent**。
2. Update_AnyThread():调用**ChingMUComponent->CalculateBoneCSRotation()**
3. Evaluate_AnyThread()对23根骨骼进行遍历取得RefPose后将从Update_AnyThread()获得动捕数据(**Rotation**覆盖到上面ComponentSpace**根骨骼需要额外添加Location数据**。最后将数据从ComponentSpace => LocalSpace。
## AnimNode_ChingMURetargetPose
1. Initialize_AnyThread()创建曲线逻辑TCHour、TCMinute、TCSecond、TCFrame
2. Update_AnyThread()
3. Evaluate_AnyThread():相关逻辑都实现在这里。
### AnimNode_ChingMURetargetPose::Evaluate_AnyThread()
2024-10-23 16:38:20 +08:00
# TsMotionReceiverActor
只在BeginPlay()中调用了this.MarkAsClientSeamlessTravel(); 具体逻辑在`AMotionReceiverActor`
## MotionReceiverActor
2024-10-28 19:23:10 +08:00
2024-11-19 10:33:49 +08:00
![[动捕逻辑思维导图.canvas]]
# Config与BoneName相关逻辑
1. Config/FullBodyConfig.json储存了对应的骨骼名称、Morph以及RootMotion骨骼名称。
1. 通过 UMotionUtils::GetModelBones()、UMotionUtils::GetMoveableBones()、UMotionUtils::GetMorphTargets()获取名称数组。
2. GetModelBones()
1. 主要在FAnimNode_FullBody::Initialize_AnyThread()被调用。
2. 填充`TArray<FBoneReference> BoneRefList;`顺带初始化SampledFullBodyData。
2024-11-19 17:11:20 +08:00
3. InitBoneRefIndex()初始化BoneRefList中每个FBoneReference的BoneIndex通过骨骼名称找到如果没有找到会提示对应的Log。
2024-11-19 13:12:03 +08:00
4. FAnimNode_FullBody::Evaluate_AnyThread(),作用在[[#ApplyDataToPose()]]。
2024-11-19 11:21:24 +08:00
3. GetMorphTargets()
2024-11-19 13:12:03 +08:00
1. 主要在FAnimNode_FullBody::Initialize_AnyThread()被调用。
## ApplyDataToPose()
### BoneTransform
2024-11-19 17:11:20 +08:00
1. 遍历BoneRefList从UMotionUtils::GetModelBones()获得)
2. 对BoneIndex有效的骨骼进行一些操作。
1. 取得当前动画蓝图输出Pose的**骨骼Index**以及**采样后动捕数据的旋转值**。
2. 如果骨骼名是Hips就将当前Index设置给HipsIndex。
3. 将旋转值应用到OutputPose中。
4. 判断当前骨骼名是否为MoveableBones中的名称将这些骨骼的Location设置到OutputPose中。
2024-11-19 13:12:03 +08:00
### MorphValues
2024-11-19 17:11:20 +08:00
将对应MorphTarget数据应用到对应的CurveChannel上。
2024-11-19 13:12:03 +08:00
### RootMotion
2024-11-19 17:11:20 +08:00
根据bUseHipsTranslation变量执行不同的逻辑
#### MapTranslationToHips
调用函数形参如下:
```c++
MapTranslationToHips(Output, EvaluatedFullBodyData, 0, HipsIndex);
```
1. 获取Joints骨骼的Locaiton作为RootMotion数据
2. 判断Joints骨骼是不是根骨骼如果**是**则调整RootMotion数据轴向。
3. 将Joints骨骼的Location归零。
4. 如果Hips骨骼有效则将RootMotion数据加到其Location上。
#### ExtractRootMotionInfo
1. 获取Joints骨骼的Locaiton作为RootMotion数据。
2. 判断Joints骨骼是不是根骨骼如果**不是**则调整RootMotion数据轴向。**轴向与MapTranslationToHips()不同**
3. 将Joints骨骼的Location归零。
4. 将RootMotion设置给AnimInstance的RootMotionLocation。
2024-11-28 16:23:41 +08:00
5. 如果Hips骨骼有效进行一堆计算最终将Rotation设置AnimInstance的RootMotionRotation。
# 采样接收数据
2024-11-28 17:29:49 +08:00
- [x] 确定ChingMu重定向后Transform数组长度。
- 数据长度150个但中间有很多事空值。
- [ ]
TArray<MocapFrames*>
2024-11-29 15:27:21 +08:00
使用cmr二郎拖。
2024-11-29 19:16:40 +08:00
# UE4移植Mocap问题确定
ASoul的Control有一步摆初始Pose的操作让下列骨骼的初始化Pose发生了改变。
Spine UE4角度<33.144 0.356 0.649> => UE5角度<33.418,-1.266,0.351>
Spine~Spine3
Chest
ChestMid
Neck1
Head
LeftArm
LeftForeArm
LeftHand
RightArm
RightForeArm
2024-12-03 10:12:02 +08:00
RightHand
# UE4 ChingMu重定向
- 移植FullBody的以下语句。
```c++
bGetMotionData = Recv->SampleFullBodyData_AnimationThread(ValidIdentity,
ULiveDirectorStatics::GetUnixTime() -
UMotionUtils::BackSampleTime * 2,
SampledFullBodyData);
```
2024-12-03 11:38:55 +08:00
##
2024-12-03 12:42:39 +08:00
- AChingmuMocapReceiverActor创建线程=>
- FChingmuThread负责接收数据并且塞入AChingmuMocapReceiverActor的FrameQueue=>
- [x] 添加时间轴判断,避免加入重复的帧。
- AChingmuMocapReceiverActorTick=>
- 从FrameQueue提取动捕帧数据并且塞入`TArray<MocapRetargetFrames*> AllHumanFrames`PutMocapDataIntoFrameList())。
- DoSample()
2024-12-03 15:54:12 +08:00
1. SampleByTimeStamp():对所有帧进行采样。
2. SendFrameToCharacter()逻辑在Puerts中发送给MotionProcess动捕数据。
- CalculatePackageAverageInterval():相关逻辑感觉没用。
- FAnimNode_FullBody =>
- Update_AnyThread()bGetMotionData = Recv->SampleFullBodyData_AnimationThread()取得对应HumanID的动捕数据。
- Evaluate_AnyThread()取得SampledFullBodyData => ApplyDataToPose()。