Loading... # 关于使用 Sequence Recorder 无法录制面部 Morph Target 问题的解决方法 这里 Sequence Recorder 包含动画蓝图中默认的录制窗口: ![20220609165131](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220609165131.png) 总体上来说有两种方法,一种是基于蓝图和代理骨骼,一种是通过C++代码实现。 ## 1. 蓝图加代理骨骼的实现 主要是通过这个节点实现: ![20220607165201](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220607165201.png) 选中节点,然后选中充当代理的骨骼(注意,最好是动画用不到的骨骼,不然大概率会出现奇怪的效果,如果没有这种可以自己在导入引擎后的 Skeleton 文件中创建虚拟骨骼): ![20220607165401](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220607165401.png) 上图中选中了 `TONGLE` 的 `Translation X` 作为数据来源,即骨骼的坐标 `X` 值作为数据来源。 再设置下要设置数据的目标: ![20220607165756](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220607165756.png) 这里设置的目标类型是名为 `jawOpen` 的 Morph Target。此外,目标也可以设置为另一个骨骼,以及 Material Parameter,这里不作展开。 最后类似这样连接,就可以通过 TONGLE 骨骼的坐标 X 来控制 jawOpen 了: ![20220607170348](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220607170348.png) 最左侧的 Transform (Modyfy) Bone 只是演示用,实际上工程中都是动画源。 ### 1.1. 优缺点 优点: 1. 蓝图实现方便,能够快速验证; 2. 适用于只需要更新少量 Morph Target 的场合; 缺点: 1. Morph Target 一多起来(例如 ARKit 那种52个 Morph Target 的情况)就会很难管理,代码会很杂乱(这算是蓝图通病); 2. 不适合协作修改(蓝图 Merge 起来还是不太方便); 3. 可能需要创建大量的代理骨骼(一个骨骼最多只能传数据到9个 Morph Target); ## 2. 代码实现 我比较推荐通过代码实现,总体步骤是创建插件,创建新的继承`AnimNode_Base`动画节点(或者其他继承`AnimNode_Base`的子类例如`AnimNode_SkeletalControlBase`),然后在动画节点的`Evaluate_AnyThread`(如果是其他`AnimNode_Base`的子类那么可能是`EvaluateXXX_AnyThread`,例如`AnimNode_SkeletalControlBase`的`EvaluateComponentSpace_AnyThread`)。下面是详细步骤。 ### 2.1. 创建插件 这一步没什么好说的,直接通过引擎 Plugin 界面创建就行: ![20220607163818](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220607163818.png) 插件名字等内容可以随意填,插件类型建议选Blank: ![20220609164936](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220609164936.png) ### 2.2. 创建动画节点 这里以`AnimNode_Base`作为基类为例子,实际上可以用别的动画节点类,可以根据业务需求选择。 我们重点看`AnimNode`部分,除了`AnimNode`还需要创建`AnimGraphNode`,这里不详细说,因为创建默认的就可以了。 假设我们创建的类为: ```C++ USTRUCT(BlueprintInternalUseOnly) struct DATALINK_API FAnimNode_DLMoCap : public FAnimNode_Base { // 这个一定要有 GENERATED_USTRUCT_BODY() // ...这里先省略 } ``` 重点是重载`FAnimNode_Base`的这个函数: ```C++ virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override; virtual void Update_AnyThread(const FAnimationUpdateContext & Context) override; virtual void Evaluate_AnyThread(FPoseContext& Output) override; ``` 其他函数基本上不怎么需要修改,而`Initialize_AnyThread`和`Update_AnyThread`主要用来初始化、加载数据,所以实际上重点只是`Evaluate_AnyThread`。在`Evaluate_AnyThread`函数中,最重要的部分则是: ```C++ void FAnimNode_DLMoCap::Evaluate_AnyThread(FPoseContext& Output) { // 忽略这一行,主要是用来接受上游动画节点的动画数据,和我们要做的事情不太相关 SourcePose.Evaluate(Output); // 获取骨骼 const USkeleton* Skeleton = Output.AnimInstanceProxy->GetSkeleton(); // 从骨骼上获取UID,实际上相当于是根据名字获取动画曲线 Curve 的 UID // 这里的 jawOpen 最好是一个 FName 类型的变量,这样方便传入 Morph Target 名字,这里只是演示用所以直接输入了名字 const SmartName::UID_Type NameUID = Skeleton->GetUIDByName(USkeleton::AnimCurveMappingName, "jawOpen"); if (NameUID != SmartName::MaxUID) { // 这里的 1.0f 最好是一个 float 变量,这一行相当于设置曲线当前值为 1.0f Output.Curve.Set(NameUID, 1.0f); } } ``` 我对这里操作的理解是,创建一个对应给定 Morph Target 的 Curve(获取 UID 的时候是通过名字获取的,所以如果输入的 Morph Target 名字是模型没有的 Morph Target,那么应该就会获取失败,最终返回结果会是 `SmartName::MaxUID`),然后写入当前帧对应的值。 最后录制好之后我们可以直接打开动画文件,看到 Morph Target 的曲线: ![20220609164527](https://images-1300215216.cos.ap-guangzhou.myqcloud.com/Blog/20220609164527.png) 此时表情数据也可以正常导出为 FBX 文件给美术进一步修改、使用。 最后修改:2022 年 06 月 20 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 随缘