128 lines
7.3 KiB
Markdown
128 lines
7.3 KiB
Markdown
---
|
||
title: AnimNode
|
||
date: 2023-08-08 12:23:11
|
||
excerpt:
|
||
tags:
|
||
rating: ⭐
|
||
---
|
||
|
||
# 前言
|
||
参考:
|
||
- https://zhuanlan.zhihu.com/p/611398524
|
||
|
||
## 动画逻辑节点(AnimNode)
|
||
AnimNode是动画节点的纯逻辑类,用于运行时执行。实际上是一个Struct。
|
||
|
||
### 更新(Update)
|
||
节点的Update用于根据Weight计算动画的各种权重。因为Weight会在下一阶段清空。如果按照Epic的编写习惯,我们应该在Update里面拿到所有外部数据并且预计算,保证Evaluate可以直接使用。
|
||
|
||
### 评估(Evaluate)
|
||
根据上一个节点的Pose,计算出输出到下个节点的Pose。这是动画节点最重要的部分。正常来说我们应该把骨骼计算部分都放在这里。
|
||
注意Update和Evaluate都有可能运行在子线程上,除了读写AnimInstanceProxy外,操作其他东西都不是线程安全的,尽可能不要碰外部的UObject。
|
||
|
||
### 根节点(Root)
|
||
也叫Output Pose节点。根节点是最重要的节点。对于用户来说,他是所有动画逻辑的输出节点。但是对于蓝图来说,他是整个蓝图节点的开始。AnimInstance将从这里开始建立整个动画节点的树状联系。
|
||

|
||
|
||
### 动画节点的属性
|
||
AnimNode通过他们的属性从外部获取信息。尽可能通过属性拿到想要的数据,而不是在运行时一层层往上Cast然后获取。
|
||
AnimNode里面的EditAnywhere的UProperty都会在动画蓝图里暴露出来。有几个特殊的Meta
|
||
- AlwaysAsPin
|
||
- NeverAsPin
|
||
- PinShownByDefault
|
||
- PinHiddenByDefault
|
||
|
||
他们可以控制属性要不要作为pin暴露出去。
|
||
变成Pin后可以直接从变量连接过去(图中的Translation),也可以直接绑定属性(图中的Alpha)。
|
||
如果连接变量的话,要注意节点是否仍然保持着FastGraph(闪电图标)。
|
||
|
||
## 骨骼索引(BoneIndex)
|
||
BoneIndex有三种,我们一一解释。
|
||
|
||
### SkeletonBoneIndex
|
||
首先Skeleton的骨骼包含着所有Mesh的所有骨骼。每当新增骨骼,只要名字不重复,就可以插入到Skeleton里面。Skeleton主要保存着他们的父子关系,所以不同网格体之间的同名骨骼的父子关系一定要正确。
|
||
在Skeleton里面,所有的骨骼都有一个唯一的Name和唯一的ParentIndex,然后保存在BoneTree里面。骨骼在这里面的Index,就属于SkeletonBoneIndex了。
|
||
SkeletonBoneIndex主要用于查找父骨骼,当然很多地方已经直接帮你缓存下来了,实际应用中很少需要自己去Skeleton里面查找。
|
||
|
||
### MeshBoneIndex
|
||
MeshBoneIndex是当前的骨骼网格体用到的骨骼的Index。
|
||
用于显示骨架网格体,所以输出的话是输出MeshBoneIndex。
|
||
|
||
### CompactPoseBoneIndex
|
||
当前Lod用到的骨骼的Index,在RequiredBone里面保存。
|
||
通过`XXBone.Initialize(RequiredBones)` 进行初始化。
|
||
我们在动画节点里面一般通过FBoneReference来操作骨骼。BoneRefrence会通过BoneName获得3个骨骼索引。如果传入的是BoneContainer,那`BoneIndex`保存的是MeshIndex,如果传入的是Skeleton的话,那`BoneIndex`保存的是SkeletonIndex。
|
||
Epic网站上也有一篇文章作为参考[[AI偶像陪伴项目笔记]](https://zhuanlan.zhihu.com/p/611398524#ref_1)。
|
||
|
||
# FAnimNode
|
||
- Initialize_AnyThread:用于初始化数据,初始化ComponentPose、AlphaBoolBlend、AlphaScaleBiasClamp。
|
||
- CacheBones_AnyThread:用于缓存Pose。
|
||
- GatherDebugData:获取Debug数据。
|
||
- Evaluate:计算动画结果。
|
||
- EvaluateSkeletalControl_AnyThread:在其他线程计算骨骼结果。
|
||
|
||
# VMC4UE
|
||
1. 支持多端口(多台捕捉机器)一起工作。
|
||
|
||
***存在问题***:
|
||
1. 只做了简单的BoneTransform与Morph适配。没有针对VMC协议的其他内容进行适配。
|
||
## AnimNode_ModifyVMC4UEBones
|
||
继承自FAnimNode_SkeletalControlBase。
|
||
- Initialize_AnyThread:调用InitializeBoneReferences()
|
||
- InitializeBoneReferences:初始化了之后需要用到BoneReferences、InitialBones。
|
||
- CacheBones_AnyThread:**重写函数**,ComponentPose.CacheBones()。
|
||
- Evaluate:无实现
|
||
- **EvaluateSkeletalControl_AnyThread**:核心逻辑。
|
||
- 判断VRMMapping是否经过初始化,如果没有则调用BuildMapping(),主要是将节点设置的VRMMapping.BoneMapping数据传递给`TMap<FName, FName> BoneMappingSkeletonToVMC`。
|
||
- 调用UVMC4UEBlueprintFunctionLibrary::GetStreamingSkeletalMeshTransform()
|
||
- 开启StreamingSkeletalMeshTransform的线程读写锁。
|
||
- 计算根骨骼Transform。
|
||
- 获取FComponentSpacePoseContext.Pose与BoneContainer的引用,开始遍历所有骨骼(使用BoneIndex)。
|
||
- 根据BoneIndex,从`TArray<FBoneReference> BoneReferences`获取BoneName。
|
||
- 根据BoneName,判断BoneMappingSkeletonToVMC是否包含该骨骼。如包含该骨骼则提取对应的骨骼数据;不包则从InitialBones中取对应骨骼的值(RefPose)。
|
||
- 获取ParentBoneIndex,并将父骨骼的Transform乘上;如果父骨骼是根骨骼则乘以之前计算的RootTransform。
|
||
- 因为骨骼Index是从Root开始算的,这样相当将之前整个骨骼链的数据都乘上了。
|
||
- 将计算完成的新值替换OutBoneTransforms中的。
|
||
- IsValidToEvaluate
|
||
- BuildMapping:
|
||
|
||
成员变量作用:
|
||
- `TArray<FBoneReference> BoneReferences`:存储BoneIndex、BoneName、CachedCompactPoseIndex。
|
||
- `TMap<FName, FName> BoneMappingSkeletonToVMC`:VRM角色骨骼映射表。VRM4U可以生成(不确定)。
|
||
- `UVMC4UEVRMMapping* PrevVRMMapping`:上一个VRMMaping,用来检测是否需要重新驱动VRMMaping。
|
||
- `UVMC4UEVRMMapping* VRMMapping`:用于指定VRMMaping资源。
|
||
- `TArray<FTransform> InitialBones`:记录初始RefPose的骨骼形变,找不到骨骼的情况会使用。
|
||
|
||
### VRMMaping的作用
|
||
存储VRM角色骨骼映射表与BlendShape信息。
|
||
VMC软件只用于驱动VRM角色或者与VRM骨骼结构相同的角色,角色的骨骼数量、排序大概率不同,会使得Index不同。所以需要搞一个BoneName映射表,用来寻找驱动的骨骼。
|
||
|
||
## GetStreamingSkeletalMeshTransform
|
||
Create部分的逻辑主要是创建NewStreamingSkeletalMeshTransform并且绑定一个UUEOSCReceiver对象。由对接受到数据进行处理。
|
||
端口与NewStreamingSkeletalMeshTransform的Map存储在OSCManager中。
|
||
### OnReceivedVMC
|
||
根据VMC Address(其实是数据类型)进行对应判断:
|
||
- /VMC/Ext/Root/Pos
|
||
- /VMC/Ext/Bone/Pos
|
||
- /VMC/Ext/Blend/Val
|
||
- /VMC/Ext/Blend/Apply
|
||
|
||
按照VMC的协议格式将数据进行反序列化并且填充到UVMC4UEStreamingSkeletalMeshTransform中。
|
||
|
||
## FAnimNode_ModifyVMC4UEMorph
|
||
继承自FAnimNode_Base。大致逻辑与AnimNode_ModifyVMC4UEBones相似。
|
||
主要逻辑位于Evaluate_AnyThread():
|
||
1. 判断是否需要重新构建VRMMaping,如有需要调用BuildMapping()进行构建。
|
||
2. 调用UVMC4UEBlueprintFunctionLibrary::GetStreamingSkeletalMeshTransform()。
|
||
3. 从AnimInstanceProxy取得Skeleton。
|
||
4. 相关有效性判断。
|
||
5. Reset所有MorphStates的值为0。
|
||
6. 开启StreamingSkeletalMeshTransform的线程读写锁。
|
||
7. StreamingSkeletalMeshTransform->CurrentBlendShapes,获取所有BlendShape进行遍历。
|
||
1. 寻找名字匹配的BlendShape.Clips,不匹配则直接进入下一次循环。
|
||
2. 寻找名字匹配的BlendShape.Meshs,不匹配则直接进入下一次循环。
|
||
3. 修改对应MorphStates[MorphName]的数值。
|
||
8. 输出所有MorphState.Value。
|
||
|
||
|