前言

  MetaHuman 已经在数字人领域里面相当成熟的解决方案。
  并且 UE 官方开发了源码工程。
  目前 github 上有不少人演示自己套用 MetaHuman 动画的效果。
  于是我自己也尝试着想将它 UE 里面的控制器动画导出来。
  然而却发现行不通。

image.png

  它的控制器关键帧是在 sequencer 里面。
  最初是尝试将 sequencer 的资源全部导出成 FBX。
  然而控制器的关键帧并没有跟随导入到 FBX 当中。

  于是我想到可以用 unreal python 读取关键帧数据导出 json
   Maya 再读取数据设置关键帧到控制器上。

unreal python 导出关键帧

  有思路之后就好办。
  之前我也写过脚本来获取 sequencer 关键帧的。
  需要注意如果想要使用 unreal python 的 API 需要开启相应的 C++ 插件。

image.png

  否则 python 会获取不到相应的 API 报错。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# Import built-in modules
from collections import defaultdict
import json
import os

# Import local modules
import unreal

DIR = os.path.dirname(os.path.abspath(__file__))

def unreal_progress(tasks, label="进度", total=None):
total = total if total else len(tasks)
with unreal.ScopedSlowTask(total, label) as task:
task.make_dialog(True)
for i, item in enumerate(tasks):
if task.should_cancel():
break
task.enter_progress_frame(1, "%s %s/%s" % (label, i, total))
yield item


def main():
# NOTE: 读取 sequence
sequence = unreal.load_asset('/Game/Sequencer/MetaHumanSample_Sequence.MetaHumanSample_Sequence')
# NOTE: 收集 sequence 里面所有的 binding
binding_dict = defaultdict(list)
for binding in sequence.get_bindings():
binding_dict[binding.get_name()].append(binding)

# NOTE: 遍历命名为 Face 的 binding
for binding in unreal_progress(binding_dict.get("Face", []), "导出 Face 数据"):
# NOTE: 获取关键帧 channel 数据
keys_dict = {}
for track in binding.get_tracks():
for section in track.get_sections():
for channel in unreal_progress(section.get_channels(), "导出关键帧"):
if not channel.get_num_keys():
continue
keys = []
for key in channel.get_keys():
frame_time = key.get_time()
frame = frame_time.frame_number.value + frame_time.sub_frame
keys.append({"frame": frame, "value": key.get_value()})

keys_dict[channel.get_name()] = keys

# NOTE: 导出 json
name = binding.get_parent().get_name()
export_path = os.path.join(DIR, "{0}.json".format(name))
with open(export_path, "w") as wf:
json.dump(keys_dict, wf, indent=4)

  上面的脚本会定位 MetaHuman 的 sequence 资源,然后导出关键帧的信息为 json

  导出会在脚本目录输出两个 json 文件。
  Maya 可以解析这个这两个 json 将关键帧设置到 控制器上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# Import built-in modules
import json
import os
import traceback

# Import third-party modules
import pymel.core as pm

DIR = os.path.dirname(os.path.abspath(__file__))


def progress(seq, status="", title=""):
pm.progressWindow(status=status, title=title, progress=0.0, isInterruptable=True)
total = len(seq)
for i, item in enumerate(seq):
try:
if pm.progressWindow(query=True, isCancelled=True):
break
pm.progressWindow(e=True, progress=float(i) / total * 100)
yield item # with body executes here
except:
traceback.print_exc()
pm.progressWindow(ep=1)
pm.progressWindow(ep=1)


def main():

# NOTE: 读取数据
with open(os.path.join(DIR, "BP_metahuman_001.json"), "r") as rf:
data = json.load(rf)

attr_map = {"location": "t", "rotation": "r"}
status = "Import Keyframe to metahuman controller"

# NOTE: undo 支持
pm.undoInfo(ock=1)
for channel, frame_list in progress(data.items(), status=status):
# NOTE: 解析 channel_name
has_attr = channel.count(".")

if not has_attr:
# NOTE: 处理 `CTRL_C_eye_parallelLook_4311` 格式
ctrl_name = channel.rsplit("_", 1)[0]
attr = "ty"
else:
parts = iter(channel.split("."))
ctrl_name = next(parts, "")
param = next(parts, "")
axis = next(parts, "")
if not axis:
# NOTE: 处理 `CTRL_C_teethD.Y_4330` 格式
attr = "t"
axis = param
else:
# NOTE: 处理 `CTRL_L_eyeAim.Rotation.Y_4387` 格式
attr = attr_map.get(param.lower())
attr += axis.split("_")[0].lower()

# NOTE: 解析出控制器属性设置关键帧
attribute = pm.PyNode(".".join([ctrl_name, attr]))
for frame_data in frame_list:
frame = frame_data.get("frame")
value = frame_data.get("value")
attribute.setKey(t=frame, v=value)

pm.undoInfo(cck=1)

  加载 unreal 导出的数据。

总结

  其实整个流程不复杂,有思路就很好处理。