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

254 lines
9.5 KiB
Markdown

## 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 层级](img/layers.svg "MetaHuman DNA 层级")
可以选择性地只加载 MetaHuman DNA 到指定层。如层级组织图所示,行为层和几何层彼此不依赖。这种独立性对于只需要使用 MetaHuman DNA 文件来驱动骨骼(使用行为层进行运行时评估)而不需要访问几何数据的用例至关重要。
#### 描述符
描述符层包含关于骨骼的基本元数据,例如:
- 角色名称
- 年龄
- 面部原型
- 以键/值对形式的任意字符串元数据
- 所需的兼容性参数(与高级系统相关,例如用于混合 MetaHuman DNA 文件)
#### 定义
定义层包含骨骼的静态数据,例如:
- 控制器、关节、变形、动画贴图和网格的名称
- 关节、变形、动画贴图和网格到各个 LOD 的映射
- 关节层级
- 绑定姿态(如 T 姿态)中的关节变换
该层包含基于所选 LOD 在后续层中进行过滤所需的信息。
#### 行为
行为层包含骨骼的动态数据,用于:
- 将 GUI 控制器映射到原始控制值
- 计算修正表情
- 计算关节变换
- 计算变形通道权重
- 计算动画贴图权重
#### 几何
几何层包含重建角色网格所需的所有数据,以及其蒙皮权重和变形目标增量。网格信息本身的结构类似于 OBJ 格式。
### API 概述
在使用 MetaHuman DNA 文件时,使用的两个主要接口是:
- [`BinaryStreamReader`](/dnacalib/DNACalib/include/dna/BinaryStreamReader.h)
- [`BinaryStreamWriter`](/dnacalib/DNACalib/include/dna/BinaryStreamWriter.h)
它们用于从二进制流读取数据或将数据写入二进制流。例如,使用 [`FileStream`](/dnacalib/DNACalib/include/trio/streams/FileStream.h) 处理文件时。
这里我们将展示一些展示库基本用法的代码片段。
DNA 库的一般 API 概述可以在[此处](/docs/dna_api.md)找到。
与 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_dna``` 和 ```save_dna``` 函数在大多数[`示例`](/examples/)中都有使用。
**注意**: 还有 [`JSONStreamReader`](/dnacalib/DNACalib/include/dna/JSONStreamReader.h) 和 [`JSONStreamWriter`](/dnacalib/DNACalib/include/dna/JSONStreamWriter.h),用于处理 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))
```
### 使用
有一些简短的[`示例`](/examples)涵盖了用户在使用 MetaHuman DNA 时可能遇到的一些情况。
其中包括:
- [`将二进制 DNA 文件内容写入 JSON 格式以供检查`](/examples/dna_binary_to_json_demo.py)
- [`从 DNA 中清除所有变形数据`](/examples/dnacalib_clear_blend_shapes.py)
- [`从 DNA 中删除某些 LOD`](/examples/dnacalib_lod_demo.py)