【3DV 进阶-8】Hunyuan3D2.1 数据预处理详解- 水密化和采样 watertight and sample
- 【3DV 进阶-1】Hunyuan3D2.1 训练代码详细理解上-模型调用流程
- 【3DV 进阶-2】Hunyuan3D2.1 训练代码详细理解下-数据读取流程
- 【3DV 进阶-3】Hunyuan3D2.1 训练代码详细理解之-Flow matching 训练 loss 详解
- 【3DV 进阶-4】VecSet 论文+代码对照理解
- 【3DV 进阶-5】3D生成中 Inductive Bias (归纳偏置)的技术路线图
- 【3DV 进阶-6】为什么3D点云是无序集合?而2D图片是有序的呢?
- 【3DV 进阶-7】Hunyuan3D2.1-ShapeVAE 整体流程
本文分析的代码在:https://github.com/Tencent-Hunyuan/Hunyuan3D-2.1/blob/main/hy3dshape/tools/watertight/watertight_and_sample.py
整体流程可以概括为:
- 归一化输入 → 2. 水密化重建 → 3. 表面点采样(随机 + 锐边)→ 4. 近场 / 体积 SDF 采样 → 5.
保存多模态监督数据。
1. 入口与参数
- 主函数读取
--input_obj、--output_prefix参数,然后执行igl.read_triangle_mesh读入三角网格数据V、F。 V = normalize_to_unit_box(V):把模型缩放到[0,1]^3,避免后续采样尺度不一致。
V, F = igl.read_triangle_mesh(input_obj)
V = normalize_to_unit_box(V)mc_verts, mc_faces = Watertight(V, F)
surface_data, sdf_data = SampleMesh(mc_verts, mc_faces)np.savez(f'{name}_surface.npz', **surface_data)
np.savez(f'{name}_sdf.npz', **sdf_data)
igl.write_obj(f'{name}_watertight.obj', mc_verts, mc_faces)
2. Water-tight 重建
Watertight(V, F) 负责把原始网格重建成密闭模型:
- 计算包围盒,填充一定 padding。
- 在盒子内生成
grid_res^3个网格点,调用igl.signed_distance得到每个点的 SDF。 - 把
ε - |SDF|作为体素值,igl.marching_cubes提取等值面,得到水密顶点mc_verts和面mc_faces。
def Watertight(V, F, epsilon = 2.0/256, grid_res = 256):...sdf = igl.signed_distance(grid_points, V, F, sign_type=igl.SIGNED_DISTANCE_TYPE_PSEUDONORMAL)[0]mc_verts, mc_faces = igl.marching_cubes(epsilon - np.abs(sdf), grid_points, grid_res, grid_res, grid_res, 0.0)return mc_verts, mc_faces
3. 表面采样
SampleMesh 对水密网格进行多分布采样:
- 构建
trimesh.Trimesh,设置总面数并固定采样点数sample_num = 499712 // 4。 random_surface:调用random_sample_pointcloud,均匀地从表面采样点和法线。sharp_surface:调用sharp_sample_pointcloud,先按法线夹角检测“锐边顶点”,再沿这些边按长度加权采样,保留细节。
random_surface, random_normal = random_sample_pointcloud(mesh, num=sample_num)
random_sharp_surface, sharp_normal = sharp_sample_pointcloud(mesh, num=sample_num)surface = np.concatenate((random_surface, random_normal), axis=1).astype(np.float16)
sharp_surface = np.concatenate((random_sharp_surface, sharp_normal), axis=1).astype(np.float16)
surface_data = {"random_surface": surface, "sharp_surface": sharp_surface}
4. 近场与体积 SDF
sample_sdf 结合 random_surface 和 sharp_surface 生成 SDF 监督数据:
vol_points:在立方体内随机采样体积点。random_near_points:把随机表面点加两种尺度的截断正态扰动;sharp_near_points类似,但按不同尺度分组,覆盖从极近到相对远的多层近场。- 对三组点分别计算
signed_distance,并取负号当作标签(与 Hunyuan 的实现习惯保持一致)。 - 数据写入字典,包括点坐标和
SDF值。
vol_points = (np.random.rand(n_volume_points, 3) - 0.5) * 2 * 1.05
...
random_near_points = random_surface + offset1 / offset2
sharp_near_points = sharp_surface[...] + np.random.normal(...)
vol_sdf = igl.signed_distance(vol_points, mesh.vertices, mesh.faces)[0]
...
data = {"vol_points": vol_points.astype(np.float16), "vol_label": (-vol_sdf).astype(np.float16), ...}
5. 数据导出
脚本最后把 surface_data、sdf_data 分别 np.savez 成 *_surface.npz、*_sdf.npz,同时输出水密网格 *_watertight.obj,供后续训练或验证使用。
