MetaFusion/docs/dna.md
2025-01-06 23:33:41 +08:00

9.5 KiB

DNA 库

DNA 库作为 DNACalib 的依赖项之一捆绑在本仓库中。 它提供了读取和写入 MetaHuman DNA 文件的核心功能。 它使用户能够查询和更改其中包含的信息。

DNACalib 提供了一组用于编辑 MetaHuman DNA 文件的实用命令。在底层,它使用 DNA 库。一些命令只是封装了对 DNA 库的几个调用,而其他命令则包含额外的逻辑。 虽然用户可以仅使用 DNA 库自己完成所有这些操作,但这些命令旨在让他们的工作更轻松。

MetaHuman DNA

MetaHuman DNA 是一种文件格式,旨在存储 3D 对象的骨骼和几何体的完整描述。 仅依靠 MetaHuman DNA 文件,就可以重建对象的完整网格并使其完全装配好骨骼,准备好进行动画制作。在实践中,MetaHuman DNA 文件仅用于存储人物角色的面部。

层级

MetaHuman DNA 文件中的数据分为几个逻辑层。这些层通过松散的层级结构连接在一起,其中 MetaHuman DNA 文件中的每个后续层都依赖于其上层中存储的数据。

MetaHuman DNA 层级

可以选择性地只加载 MetaHuman DNA 到指定层。如层级组织图所示,行为层和几何层彼此不依赖。这种独立性对于只需要使用 MetaHuman DNA 文件来驱动骨骼(使用行为层进行运行时评估)而不需要访问几何数据的用例至关重要。

描述符

描述符层包含关于骨骼的基本元数据,例如:

  • 角色名称
  • 年龄
  • 面部原型
  • 以键/值对形式的任意字符串元数据
  • 所需的兼容性参数(与高级系统相关,例如用于混合 MetaHuman DNA 文件)

定义

定义层包含骨骼的静态数据,例如:

  • 控制器、关节、变形、动画贴图和网格的名称
  • 关节、变形、动画贴图和网格到各个 LOD 的映射
  • 关节层级
  • 绑定姿态(如 T 姿态)中的关节变换

该层包含基于所选 LOD 在后续层中进行过滤所需的信息。

行为

行为层包含骨骼的动态数据,用于:

  • 将 GUI 控制器映射到原始控制值
  • 计算修正表情
  • 计算关节变换
  • 计算变形通道权重
  • 计算动画贴图权重

几何

几何层包含重建角色网格所需的所有数据,以及其蒙皮权重和变形目标增量。网格信息本身的结构类似于 OBJ 格式。

API 概述

在使用 MetaHuman DNA 文件时,使用的两个主要接口是:

它们用于从二进制流读取数据或将数据写入二进制流。例如,使用 FileStream 处理文件时。

这里我们将展示一些展示库基本用法的代码片段。 DNA 库的一般 API 概述可以在此处找到。

与 DNACalib 一样,DNA 库是用 C++ 编写的,但有一个 Python 包装器,因此可以从 C++ 和 Python 中使用它。

创建读取器/写入器

读取器

从文件读取二进制 DNA 的函数示例:

def load_dna(path):
    stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
    reader = BinaryStreamReader(stream, DataLayer_All)
    reader.read()
    if not Status.isOk():
        status = Status.get()
        raise RuntimeError(f"Error loading DNA: {status.message}")
    return reader

创建读取器时,除了流参数外,还可以指定要加载的数据层。在此示例中,将加载所有层,因为使用了 DataLayer_All,但您可以指定以下任何一项:

DataLayer_Descriptor
DataLayer_Definition - 包括描述符和定义
DataLayer_Behavior - 包括描述符、定义和行为
DataLayer_Geometry - 包括描述符、定义和几何
DataLayer_GeometryWithoutBlendShapes - 包括描述符、定义和不含变形的几何
DataLayer_AllWithoutBlendShapes - 包括除几何中的变形之外的所有内容
DataLayer_All

例如,如果您只想加载行为层(包括定义和描述符),请使用:

stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_Behavior)
reader.read()
if not Status.isOk():
    status = Status.get()
    raise RuntimeError(f"Error loading DNA: {status.message}")
写入器

从文件写入二进制 DNA 的函数示例:

def save_dna(reader, path):
    stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
    writer = BinaryStreamWriter(stream)
    # 使用所有数据层基于读取器创建写入器(如果没有传递参数给 setFrom(),DataLayer_All 是默认值)
    writer.setFrom(reader)
    # 例如,要仅使用几何层(包括定义和描述符)创建写入器,请使用:
    # writer.setFrom(reader, DataLayer_Geometry)

    writer.write()

    if not Status.isOk():
        status = Status.get()
        raise RuntimeError(f"Error saving DNA: {status.message}")

除了在创建读取器时指定层外,还可以在创建写入器时指定要使用的层(作为 setFrom() 方法的参数)。

load_dnasave_dna 函数在大多数示例中都有使用。

注意: 还有 JSONStreamReaderJSONStreamWriter,用于处理 JSON 格式而不是二进制格式的 MetaHuman DNA 文件。但是应该注意,JSON 变体仅用于调试等目的。与二进制读取器和写入器不同,它们的 JSON 对应项不能执行过滤,并且通常会生成更大的文件。 存储 DNA 文件的推荐格式是二进制格式。

已知问题: 目前读取 JSON MetaHuman DNA 文件会失败。此问题将在未来版本中解决。

示例

以下是使用该库的一些示例片段。

示例 1: 读取指定网格的中性顶点位置
dna = load_dna(input_path)

if dna.getMeshCount() == 0:
    print("No meshes found in DNA.")
    return

mesh_index = 0
xs = dna.getVertexPositionXs(mesh_index)
ys = dna.getVertexPositionYs(mesh_index)
zs = dna.getVertexPositionZs(mesh_index)
示例 2: 读取中性关节坐标和关节方向值
dna = load_dna(input_path)

# 读取关节坐标
neutral_joint_translation_xs = dna.getNeutralJointTranslationXs()
neutral_joint_translation_ys = dna.getNeutralJointTranslationYs()
neutral_joint_translation_zs = dna.getNeutralJointTranslationZs()

# 读取关节方向
neutral_joint_orient_xs = dna.getNeutralJointRotationXs()
neutral_joint_orient_ys = dna.getNeutralJointRotationYs()
neutral_joint_orient_zs = dna.getNeutralJointRotationZs()
示例 3: 读取表情的变形目标增量并更改它们
def read_blend_shape_target_deltas(reader, mesh_index, blend_shape_target_index):
    """
    读取变形目标增量和相应的顶点索引。
    """

    vertex_indices = reader.getBlendShapeTargetVertexIndices(
        mesh_index, blend_shape_target_index
    )
    blend_shape_target_delta_count = reader.getBlendShapeTargetDeltaCount(
        mesh_index, blend_shape_target_index
    )
    deltas = []
    for delta_index in range(blend_shape_target_delta_count):
        x, y, z = reader.getBlendShapeTargetDelta(
            mesh_index, blend_shape_target_index, delta_index
        )
        deltas.append([x, y, z])
    return vertex_indices, deltas

# 读取然后更改表情 "jaw_open" 的变形目标增量,网格为 "head_lod0_mesh"
input_dna = load_dna(input_path)

mesh_name = "head_lod0_mesh"
mesh_count = input_dna.getMeshCount()
head_mesh_index = 0
for mesh_index in range(mesh_count):
    if input_dna.getMeshName(mesh_index) == mesh_name:
        head_mesh_index = mesh_index
        break

bs_target_count = input_dna.getBlendShapeTargetCount(head_mesh_index)
expr_name = "jaw_open"

# 获取指定表情的变形目标索引
for i in range(bs_target_count):
    bs_channel_index = input_dna.getBlendShapeChannelIndex(head_mesh_index, i)
    bs_name = input_dna.getBlendShapeChannelName(bs_channel_index)
    if bs_name == expr_name:
        bs_target_index = i
        break

vertex_indices, deltas = read_blend_shape_target_deltas(input_dna, head_mesh_index, bs_target_index)

# 修改增量(在本例中,只是给每个增量加 1.0)
for i in range(len(deltas)):
    deltas[i][0] += 1.0
    deltas[i][1] += 1.0
    deltas[i][2] += 1.0

# 从输入 DNA 创建写入器 DNA
output_stream = dna.FileStream(outputPath, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary)

# 在此示例中,为了调试目的,以 JSON 格式写入 DNA,以快速查看变形增量是否已更改
output_dna = dna.JSONStreamWriter(output_stream)
output_dna.setFrom(input_dna)

# 为表情写入新的变形增量值
output_dna.setBlendShapeTargetDeltas(mesh_index, bs_target_index, deltas)

# 如果您以删除或添加某些增量的方式修改了增量,
# 那么您还必须设置与新增量对应的新顶点索引:
# output_dna.setBlendShapeTargetVertexIndices(mesh_index, bs_target_index, new_vertex_indices)

# 写入具有修改值的 DNA
output_dna.write()

if not dna.Status.isOk():
    status = dna.Status.get()
    raise RuntimeError("Error saving DNA: {}".format(status.message))

使用

有一些简短的示例涵盖了用户在使用 MetaHuman DNA 时可能遇到的一些情况。 其中包括: