系列文章目录
- 【3D AICG 系列-1】Trellis v1 和 Trellis v2 的区别和改进
- 【3D AICG 系列-2】Trellis 2 的O-voxel (上) Shape: Flexible Dual Grid
- 【3D AICG 系列-3】Trellis 2 的O-voxel (下) Material: Volumetric Surface Attributes
- 【3D AICG 系列-4】Trellis 2 的Shape SLAT Flow Matching DiT 训练流程
- 【3D AICG 系列-5】Trellis 2 的 Pipeline 推理流程的各个中间结果和形状
- 【3D AICG 系列-6】OmniPart 训练流程梳理
- 【3D AICG 系列-7】PartUV 代码流程深度解析
- 【3D AICG 系列-8】PartUV 流程图详解
- 【3D AICG 系列-9】Trellis2 推理流程图超详细介绍
- 【3D-AICG 系列-10】Trellis v2 只在 512/1024上训却能生成 1536
- 【3D-AICG 系列-11】Trellis 2 的 Shape VAE 训练流程梳理
- 【3D-AICG 系列-12】Trellis 2 的 Shape VAE 的设计细节 Sparse Residual Autoencoding Layer
- 【3D-AICG 系列-13】Trellis 2 的 SC-VAE 的 Training Loss 细节
- 【3D-AICG 系列-14】Trellis 2 的 Texturing Pipeline 保留单层薄壳,而 Textured GLB 会变成双层
文章目录
- 系列文章目录
- [Mesh -> O-voxel](#Mesh -> O-voxel)
-
前文有介绍 Trellis 2 的 Flexible Dual Grid 整体流程,本文则聚焦于将这部分的 C++ 代码细节和论文对应起来。
-
代码来自
/repo/TRELLIS.2/o-voxel/src/convert/flexible_dual_grid.cpp被
/repo/TRELLIS.2/data_toolkit/dual_grid.py中的o_voxel.convert.mesh_to_flexible_dual_grid函数调用


Mesh -> O-voxel
跳出细节,看大局。整个 flexible_dual_grid.cpp 文件只干一件事:
总目标
输入一个三角网格(mesh),输出一组稀疏体素,每个体素内有一个"最佳代表点"(dual vertex)。
关键输入(只有 4 个需要关心的)
| 输入 | 含义 |
|---|---|
vertices (N,3) |
网格顶点坐标 |
faces (M,3) |
三角面的顶点索引 |
voxel_size (3,) |
每个体素在 x/y/z 方向的尺寸 |
grid_range (2,3) |
体素索引的范围 [min, max) |
关键输出(只有 3 个)
| 输出 | 含义 |
|---|---|
voxel_indices (K,3) int |
K 个活跃体素的整数坐标 |
dual_vertices (K,3) float |
每个体素内的最佳代表点坐标 |
intersected (K,3) bool |
每个体素在 x/y/z 三个方向上是否被表面穿过 |
- 活跃体素 = Mesh 表面与体素网格有交的体素
- voxel_indices (i, j, k):表示"第几格",只能是整数。
- dual_vertices (x, y, z):表示"这一格里的点在世界/局部坐标下的具体位置",可以是格子内的任意一点。并且 QEF 求出来的解是连续坐标,例如 (2.3, 1.7, 0.8),表示在局部坐标系下 x=2.3、y=1.7、z=0.8 的点,所以必须用 float 才能存这些小数。
关键算法:只有 2 步
第 1 步:收集约束("哪些体素被穿过、穿过时的几何信息是什么")
由 3 个函数完成,每个函数只做一件事------往 QEF (quadratic error function) 矩阵里加一项约束:
| 函数 | 做什么 | 一句话 |
|---|---|---|
intersect_qef |
扫描线找"三角面穿过了哪些体素边",记录交点和法平面 | 发现活跃体素 + 加"交点平面"约束 |
face_qef |
遍历三角面的包围盒,找与三角面重叠的体素 | 加"面片平面"约束(让代表点靠近面) |
boundry_qef |
找只被一个三角形引用的边(开边界),DDA 遍历经过的体素 | 加"边界线"约束(让代表点靠近边界) |
三个函数的共同点:都在往同一个 qefs[i](4×4 矩阵)里 累加,本质是在说"这个体素的代表点应该尽量满足这些几何约束"。
第 2 步:求解("在每个体素内找最佳代表点")
就是 562--765 行的循环:
对每个活跃体素:
Q = 累加好的 QEF 矩阵(+ 可选正则项)
先无约束解 Ax=b
如果解在体素内 → 用它
如果解跑到体素外 → 依次试"固定1个面/2个面/3个面(角点)",取误差最小的
→ 得到 dual_vertex
一句话 :在体素的小盒子里,找一个点,使它离所有穿过该体素的三角面尽量近。
一张图串起来
mesh (vertices + faces)
│
▼
┌─────────────────┐
│ intersect_qef │ 扫描线 → 发现活跃体素 + 累加 QEF
└────────┬────────┘
▼
┌─────────────────┐
│ face_qef │ 面片重叠检测 → 累加 QEF(可选)
└────────┬────────┘
▼
┌─────────────────┐
│ boundry_qef │ 边界 DDA → 累加 QEF(可选)
└────────┬────────┘
▼
┌─────────────────┐
│ QEF 求解循环 │ 每体素解 min ||Ax-b||² → dual_vertex
└────────┬────────┘
▼
(voxel_indices, dual_vertices, intersected)
所以你只需要记住 :前面 3 个函数是"收集约束、往 QEF 矩阵里加东西",最后一个循环是"解方程、得代表点"。其它所有代码(扫描线怎么扫、投影怎么测、DDA 怎么走、约束求解的分支枚举)都是这两步的实现细节。