7.3 KiB
Raw Blame History

title, date, excerpt, tags, rating
title date excerpt tags rating
AnimNode 2023-08-08 12:23:11

前言

参考:

动画逻辑节点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个骨骼索引。如果传入的是BoneContainerBoneIndex保存的是MeshIndex如果传入的是Skeleton的话BoneIndex保存的是SkeletonIndex。 Epic网站上也有一篇文章作为参考[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
      • 根据BoneIndexTArray<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。