254 lines
9.5 KiB
Markdown
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 到指定层。如层级组织图所示,行为层和几何层彼此不依赖。这种独立性对于只需要使用 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)
|