前言

  上周的时候,有网友问过我相关的导出场景模型的问题。
  当时他给我发了一篇微信公众号的文章 链接
  导出需要附带 矩阵信息 实在是太麻烦了,其实并不需要如此的复杂。
  碍于时间实在是太仓促,没有时间详细分析我这边具体的操作流程。
  遂抽空总结了一波,并且在总结的逛论坛的时候发现了更好导出的方案。

MergeStaticActor 方案

  最开始分析这个问题,我首先想到的是对 Actor 右键能否直接导出模型。
  然而却没有发现相关的方案,于是只能采用对 StaticMesh 等静态资源导出模型的方案了。
  但是采用这个方案,通过遍历场景的模型可以找到关联的静态模型,导出却没有了 场景的 位置,只能按照上面链接的方式,将矩阵重新实现一遍吗?
  经过我对 Actor 右键的研究,我发现可以通过 Merge Actors 合并生成一个带世界坐标位置的新 StaticMesh ,通过这个 Mesh 导出 FBX 就是带世界坐标位置的模型了。

alt

alt

  配置窗口勾选 Pivot Point at Zero 点击下方的 Merge Actors 会生成一个带世界坐标的 StaticMesh 静态模型资产。
  随后右键进行导出即可。

alt

  使用这个方案需要遍历场景中需要导出的资源,然后将临时的静态资源生成到临时的目录里,然后再逐个导出即可,最后再把临时目录删除干净。
  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
52
53
54
level_lib = unreal.EditorLevelLibrary
asset_lib = unreal.EditorAssetLibrary

selected_static_actors = [
a
for a in level_lib.get_selected_level_actors()
if isinstance(a, unreal.StaticMeshActor)
]
options = unreal.EditorScriptingMergeStaticMeshActorsOptions()
options.set_editor_property("destroy_source_actors", False)
options.set_editor_property("spawn_merged_actor", False)
setting = unreal.MeshMergingSettings()
# NOTE 保留 Actor 的坐标
setting.set_editor_property("pivot_point_at_zero", True)
options.set_editor_property("mesh_merging_settings", setting)

# NOTE 配置 FBX 导出选项
fbx_exporter = unreal.StaticMeshExporterFBX()
fbx_option = unreal.FbxExportOption()
fbx_option.export_morph_targets = False
fbx_option.export_preview_mesh = False
fbx_option.level_of_detail = False
fbx_option.collision = False
fbx_option.export_local_time = False
fbx_option.ascii = False
fbx_option.vertex_color = True

# NOTE 在工程里面创建一个临时导出路径
temp_directory = "/Game/Temp_FBX_Export"
# NOTE 设置 FBX 导出路径
export_path = r"C:\FBX_EXPORT"
for actor in selected_static_actors:
actor_name = actor.get_name()
asset_path = posixpath.join(temp_directory, actor_name)
options.set_editor_property("base_package_name", asset_path)
# NOTE 将选中的 Actor 合并为 StaticMesh 资源
level_lib.merge_static_mesh_actors([actor], options)

fbx_path = os.path.join(export_path, "%s.fbx" % actor_name)

mesh = unreal.load_asset(asset_path)
task = unreal.AssetExportTask()
task.set_editor_property("object", mesh)
task.set_editor_property("filename", fbx_path)
task.set_editor_property("exporter", fbx_exporter)
task.set_editor_property("automated", True)
task.set_editor_property("prompt", False)
task.set_editor_property("options", fbx_option)

unreal.Exporter.run_asset_export_task(task)

# NOTE 删除临时目录
asset_lib.delete_directory(temp_directory)

  上面的代码就可以实现选中场景的静态模型导出 FBX 的效果,每个 Actor 都是单独的 Mesh 进行命名导出。

导出贴图资源

  可以看到上面的方案导出如果是没有带上贴图的,如果需要贴图,需要从 Unreal 的材质里面查找。
  这里我可以通过依赖关系的方式找到模型关联的材质。
  然后也通过依赖的方式将关联的贴图全部导出来。
  这样可以实现宁可误杀,绝不放过的将所有贴图导出。

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
mesh_lib = unreal.EditorStaticMeshLibrary

def ls_dependencies(path):
# NOTE 获取资产的依赖资产
data = asset_lib.find_asset_data(path)
options = unreal.AssetRegistryDependencyOptions()
dependencies = asset_registry.get_dependencies(data.package_name, options)
return dependencies

total = mesh_lib.get_number_materials(mesh)
for i in range(total):
material = mesh.get_material(i)
textures = ls_dependencies(material.get_path_name())
for texture_path in textures:
data = asset_lib.find_asset_data(texture_path)
# NOTE 过滤非贴图资产 | 不用 `isinstance` 的方式可以不用加载资产
if not issubclass(getattr(unreal,str(data.asset_class)), unreal.Texture):
continue

texture_name = str(data.asset_name)
tga_path = os.path.join(texture_folder, "%s.tga" % texture_name)
tga_exporter = unreal.TextureExporterTGA()
mesh = unreal.load_asset(asset_path)
task = unreal.AssetExportTask()
task.set_editor_property("object", data.get_asset())
task.set_editor_property("filename", tga_path)
task.set_editor_property("exporter", tga_exporter)
task.set_editor_property("automated", True)
task.set_editor_property("prompt", False)
task.set_editor_property("options", fbx_option)

unreal.Exporter.run_asset_export_task(task)

  通过 ls_dependencies 的方式将静态资产关联的所有资产全部罗列出来。
  然后通过 AssetData 获取相关的数据。
  最后可以获取到关联在材质上的贴图导出。
  如果想要在 DCC 重建贴图需要将相关的 Diffuse Normal 贴图信息导出去,比如用 Json 进行存储,然后 DCC 端写一个读取配置工具。根据记录的贴图数据将关联的贴图贴到 DCC 材质的相关贴图通道上。

直接导出场景模型方案

  上面的方案有个缺点,只能导出静态资产,如果是 SkeletalMesh 或者是 Foliage 相关的 Mesh 都无法导出。
  因为 EditorScriptingMergeStaticMeshActorsOptions 这个类只对 静态模型 起作用。
  然而 Unreal 的 Level 是可以直接导出场景所有的资产的。
  那么有没有解决问题的办法呢?
  非常凑巧,最近逛 Unreal 的论坛还真就找到了更加简单的方法。

https://forums.unrealengine.com/t/python-convert-actors-to-static-mesh/152073

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import unreal
level_lib = unreal.EditorLevelLibrary
output_file = r'C:\FBX_EXPORT\ue4_output.fbx'

selected_actors = level_lib.get_selected_level_actors()
if len(selected_actors) == 0:
print("No actor selected, nothing to export")
quit()

task = unreal.AssetExportTask()
# NOTE 关键在于设置当前 world 为导出对象
task.object = level_lib.get_editor_world()
task.filename = output_file
# NOTE 加上 selected 只导出选中的物体
task.selected = True
task.replace_identical = False
task.prompt = False
task.automated = True
task.options = unreal.FbxExportOption()
task.options.vertex_color = False
task.options.collision = False
task.options.level_of_detail = False
unreal.Exporter.run_asset_export_task(task)

  使用这个方案可以导出 Foliage landscape 等特殊的 Actor

  只是使用这个方案查询材质需要在 关联的 component 下进行操作。

1
2
3
4
5
6
7
8
for actor in unreal.EditorLevelLibrary.get_selected_level_actors():
comp = actor.root_component
for comp in comp.get_children_components(True):
# NOTE 查询继承于静态模型的 component
if isinstance(comp,unreal.StaticMeshComponent):
mesh = comp.static_mesh
print(mesh)
# NOTE 再通过 mesh 查找材质和贴图

  通过上面的代码可以获取到关联继承的 StaticMeshComponent
  即便 InstancedFoliageActor 没有材质模型贴图的属性显示。

alt

  还是可以通过获取 actor 的 component 找到 FoliageInstancedStaticMeshComponent ,从而找到关联的 static_mesh

总结

  至于在 DCC 里面重建贴图的方式没有细说。
  其实可以通过通配符匹配 _N _D 后缀的贴图,然后将这些通用的贴图在 DCC 里面自动连接上。
  主要通过 json 的配置描述每个 fbx 关联的贴图即可。