前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >关于使用 Sequence Record 无法录制面部 Morph Target 问题的解决方法

关于使用 Sequence Record 无法录制面部 Morph Target 问题的解决方法

作者头像
太阳影的社区
发布2022-06-12 13:28:21
3860
发布2022-06-12 13:28:21
举报

关于使用 Sequence Record 无法录制面部 Morph Target 问题的解决方法

这里 Sequence Recorder 包含动画蓝图中默认的录制窗口:

20220609165131
20220609165131

总体上来说有两种方法,一种是基于蓝图和代理骨骼,一种是通过C++代码实现。

1. 蓝图加代理骨骼的实现

主要是通过这个节点实现:

20220607165201
20220607165201

选中节点,然后选中充当代理的骨骼(注意,最好是动画用不到的骨骼,不然大概率会出现奇怪的效果,如果没有这种可以自己在导入引擎后的 Skeleton 文件中创建虚拟骨骼):

20220607165401
20220607165401

上图中选中了 TONGLETranslation X 作为数据来源,即骨骼的坐标 X 值作为数据来源。

再设置下要设置数据的目标:

20220607165756
20220607165756

这里设置的目标类型是名为 jawOpen 的 Morph Target。此外,目标也可以设置为另一个骨骼,以及 Material Parameter,这里不作展开。

最后类似这样连接,就可以通过 TONGLE 骨骼的坐标 X 来控制 jawOpen 了:

20220607170348
20220607170348

最左侧的 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_SkeletalControlBaseEvaluateComponentSpace_AnyThread)。下面是详细步骤。

2.1. 创建插件

这一步没什么好说的,直接通过引擎 Plugin 界面创建就行:

20220607163818
20220607163818

插件名字等内容可以随意填,插件类型建议选Blank:

20220609164936
20220609164936

2.2. 创建动画节点

这里以AnimNode_Base作为基类为例子,实际上可以用别的动画节点类,可以根据业务需求选择。

我们重点看AnimNode部分,除了AnimNode还需要创建AnimGraphNode,这里不详细说,因为创建默认的就可以了。

假设我们创建的类为:

代码语言:javascript
复制
USTRUCT(BlueprintInternalUseOnly)
struct DATALINK_API FAnimNode_DLMoCap : public FAnimNode_Base {
    // 这个一定要有
    GENERATED_USTRUCT_BODY()
    // ...这里先省略
}

重点是重载FAnimNode_Base的这个函数:

代码语言:javascript
复制
virtual void Initialize_AnyThread(const FAnimationInitializeContext& Context) override;
virtual void Update_AnyThread(const FAnimationUpdateContext & Context) override;
virtual void Evaluate_AnyThread(FPoseContext& Output) override;

其他函数基本上不怎么需要修改,而Initialize_AnyThreadUpdate_AnyThread主要用来初始化、加载数据,所以实际上重点只是Evaluate_AnyThread。在Evaluate_AnyThread函数中,最重要的部分则是:

代码语言:javascript
复制
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
20220609164527

此时表情数据也可以正常导出为 FBX 文件给美术进一步修改、使用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 关于使用 Sequence Record 无法录制面部 Morph Target 问题的解决方法
    • 1. 蓝图加代理骨骼的实现
      • 1.1. 优缺点
    • 2. 代码实现
      • 2.1. 创建插件
      • 2.2. 创建动画节点
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档