MeshFlow

MeshFlow:高效艺术风格 3D 网格生成

论文:MeshFlow: Efficient Artistic Mesh Generation via MeshVAE and Flow-based Diffusion Transformer
代码:facebookresearch/meshflow
arXiv:2606.04621

1. 一句话理解

MeshFlow 把 3D 网格生成改成下面这件事:

flowchart LR A[真实训练网格] --> B[MeshVAE 编码器] B --> C[连续 latent:训练目标] C -.训练.-> D[Flow DiT] P[输入点云 / 可选参考图像] --> D N[推理时的随机噪声] --> D D --> E[生成 mesh latent] E --> F[MeshVAE 解码器] F --> G[恢复边和三角面] G --> H[艺术风格 3D 网格]

传统方法逐个生成顶点和三角面;MeshFlow 先把网格压缩成连续 latent,再并行生成整个网格。


2. 为什么网格生成困难?

一个三角网格包含两类信息:

信息 例子 特点
几何 顶点坐标 \((x,y,z)\) 连续
拓扑 哪些顶点相连、哪些点组成三角面 离散

传统自回归方法通常这样生成:

sequenceDiagram participant M as 模型 M->>M: 生成顶点 1 M->>M: 生成顶点 2 M->>M: 生成顶点 3 M->>M: 生成三角面 1 M->>M: 生成顶点 4 M->>M: 生成三角面 2 M->>M: ...

主要问题:

  • 网格越大,串行步骤越多;
  • 坐标常被离散成有限等级,产生量化误差;
  • 序列可能提前结束,生成不完整网格。

3. 核心设计:把离散拓扑变成连续向量

MeshFlow 最终希望用三组信息表示每个顶点:

\[\text{vertex}_i=(v_i,n_i,e_i) \]

其中:

  • \(v_i\):顶点坐标;
  • \(n_i\):顶点法线;
  • \(e_i\):边连接特征,也叫 edge embedding。

这里的 \((v_i,n_i,e_i)\) 是模型的输出表示 / 解码后表示。它不等于编码器的原始输入。

3.1 用 embedding 判断是否有边

对两个顶点 \(i,j\),从 \(e_i\) 和 \(e_j\) 计算边连接分数。开源实现使用时空距离形式的分数:

\[s_{ij}=\|e_i^{(s)}-e_j^{(s)}\|_2^2-\|e_i^{(t)}-e_j^{(t)}\|_2^2 \]

开源代码在 \(s_{ij}>\tau\) 时判断两点相连。这个分数不是普通欧氏距离,不能简单理解成“越近越相连”。

flowchart LR A[顶点 i 的 embedding] --> C{连接分数是否大于阈值?} B[顶点 j 的 embedding] --> C C -->|是| D[恢复边 i-j] C -->|否| E[不建立边]

因此,离散邻接矩阵被连续向量计算出的连接分数替代。论文正文的示意判定式写作 \(d\leq\tau\),但损失式和开源代码使用的是相反方向;这里按照实际实现说明。

3.2 用三条边恢复三角面

如果三个顶点两两相连,就恢复一个三角面:

graph LR V1((v1)) --- V2((v2)) V2 --- V3((v3)) V3 --- V1

3.3 用法线恢复面朝向

三条边只能确定三角形的三个点,不能确定顺时针还是逆时针。

MeshFlow 使用顶点法线的平均值作为面方向:

\[n_f=\operatorname{normalize}(n_1+n_2+n_3) \]

再与坐标计算出的面法线比较;方向相反时交换两个顶点的顺序。


4. MeshVAE:把网格压缩成 latent

4.1 编码器输入

要特别区分三个层次:

层次 每个顶点包含的内容
编码器输入 位置、法线、由真实邻接关系找到的邻居坐标
latent 压缩后的连续隐藏向量
解码器输出 重建位置、法线、edge embedding、有效顶点 mask

按照论文的设计,编码器输入由以下三部分组成:

\[x_i=\operatorname{Concat}\left(\operatorname{PE}(v_i),\operatorname{PE}(n_i),\{v_j:j\in\mathcal{N}_i\}\right) \]

其中:

  • \(\operatorname{PE}\):Fourier positional encoding;
  • \(\mathcal{N}_i\):顶点 \(i\) 的邻居;
  • 邻居坐标提供局部几何和拓扑信息。

这里的关键点是:edge embedding 不在论文编码器输入公式中,而是在解码器输出中出现。 编码器读取真实网格的离散邻接关系,先找到邻居坐标;解码器无法直接输出离散邻接矩阵,因此改为预测连续的 edge embedding,再由它恢复拓扑。

还要区分论文公式和开源代码的默认配置:

来源 实际输入
论文方法 \(\operatorname{PE}(v_i)\)、\(\operatorname{PE}(n_i)\)、邻居顶点坐标
开源代码 embedder(padded_verts) 加上 point_feats_type 中配置的特征

代码默认的 point_feats_type 是 ["verts_normal"],所以默认一定有:

\[x_i=\operatorname{Concat}\bigl(\operatorname{PE}(v_i),n_i\bigr) \]

邻居坐标只有在配置中加入 neighbor_points 时才会进入编码器。法线只有在配置写成 embed@verts_normal 时才会经过同一个 Fourier embedder;写成 verts_normal 时使用原始三维法线。实际运行应以 checkpoint 的 config.yaml 为准。

4.2 压缩过程

flowchart LR A[N 个顶点 token] --> B[Fourier 编码] B --> C[TokenMerge] C --> D[Cross-Attention<br/>读取完整网格] D --> E[Self-Attention<br/>建模全局结构] E --> F[n 个 latent token] F -.通常 n≈N/4.-> F

TokenMerge 类似 PixelShuffle,把多个顶点 token 的信息合并到更少的 token 中。

4.3 解码过程

flowchart LR A[n 个 latent token] --> B[TokenSplit] B --> C[Cross-Attention] C --> D[N 个输出 token] D --> E[顶点坐标 v] D --> F[法线 n] D --> G[边 embedding e] D --> H[有效顶点 mask]

解码器输出:

\[(\hat v_i,\hat n_i,\hat e_i,\hat m_i) \]


5. MeshVAE 的训练目标

总损失为:

\[\mathcal{L}=\mathcal{L}_{mask}+\mathcal{L}_{v}+\mathcal{L}_{n}+\mathcal{L}_{adj}+\lambda_{kl}\mathcal{L}_{kl} \]

损失 作用
\(\mathcal{L}_{mask}\) 判断当前位置是不是有效顶点
\(\mathcal{L}_{v}\) 让预测坐标接近真实坐标
\(\mathcal{L}_{n}\) 让预测法线接近真实法线
\(\mathcal{L}_{adj}\) 区分相连和不相连的顶点
\(\mathcal{L}_{kl}\) 让 latent 接近标准高斯分布

拓扑对比损失的直观目标:

flowchart LR A[真实有边的顶点对] --> B[连接分数高于阈值] C[真实无边的顶点对] --> D[连接分数低于阈值]

这样,解码器不需要直接预测离散邻接矩阵,也能恢复网格拓扑。


6. Flow DiT:从噪声生成整个网格

6.1 训练阶段

设真实网格 latent 为 \(z_0\),随机噪声为 \(\epsilon\):

\[\epsilon\sim\mathcal{N}(0,I) \]

构造中间状态:

\[z_t=(1-t)z_0+t\epsilon \]

于是:

  • \(t=0\):真实 latent;
  • \(t=1\):纯噪声。

DiT 学习预测这条直线的速度:

\[v^*(z_t,t)=\epsilon-z_0 \]

训练目标:

\[\mathcal{L}_{CFM}=\left\|v_\theta(z_t,t,c)-(\epsilon-z_0)\right\|_2^2 \]

其中 \(c\) 是点云或图像条件。

6.2 推理阶段

flowchart LR A[随机高斯噪声] --> B[DiT 预测速度] B --> C[更新 latent] C --> D[DiT 预测速度] D --> E[更新 latent] E --> F[得到干净 mesh latent] F --> G[MeshVAE Decoder]

推理时从噪声出发,沿着 DiT 预测的方向逐步回到真实网格 latent。

6.3 为什么速度快?

自回归生成 MeshFlow
一个 token 接一个 token 生成 所有 latent token 同时处理
生成长度随网格变大 去噪步数基本固定
容易提前终止 统一输出完整 token 集合
通常需要离散坐标 直接生成连续坐标

7. 如何加入输入形状?

点云条件

点云先被粗略体素化,再通过 3D RoPE 注入 DiT:

flowchart LR A[输入点云] --> B[体素化] B --> C[3D 空间位置编码] C --> D[注入 noisy latent] D --> E[生成符合输入形状的网格]

模型还把目标顶点数作为条件,控制输出网格的分辨率。

参考图像条件

开源实现支持可选的参考图像:

flowchart LR A[参考图像] --> B[DINOv3 图像编码器] B --> C[视觉 token] C --> D[Cross-Attention 注入 DiT]

点云主要控制三维形状,图像主要提供外观和风格参考。


8. 网格提取算法

MeshVAE 解码后还不是最终三角网格,论文继续执行:

flowchart TD A[mask logits] --> B[保留有效顶点] B --> C[计算 edge embedding 两两距离] C --> D[按阈值恢复边] D --> E[寻找三角闭环] E --> F[恢复三角面] F --> G[修补小边界环] G --> H[根据法线调整面方向] H --> I[输出 Mesh]

修补步骤主要处理扩散预测不准确造成的小孔洞:

边界边 → 找到闭合多边形环 → 对小环重新三角化

9. 训练和推理的完整流程

训练

flowchart TD A[真实网格] --> B[提取顶点、法线、邻接关系] B --> C[训练 MeshVAE] C --> D[得到真实 latent z₀] D --> E[加入高斯噪声得到 zₜ] E --> F[训练 Flow DiT 预测速度] G[点云 / 图像条件] --> F

推理

flowchart TD A[点云 / 图像] --> B[提取条件特征] C[随机噪声] --> D[Flow DiT 去噪] B --> D D --> E[生成 mesh latent] E --> F[MeshVAE 解码] F --> G[恢复边和三角面] G --> H[输出 .glb / .ply 网格]

10. 实验结果

在 Toys4K 测试集上,数值越低越好:

方法 Chamfer Distance Hausdorff Distance
FastMesh-V4K 4.05 10.22
MeshFlow 2.33 4.23

单个物体的推理时间约为:

方法 时间
BPT 约 8 分钟
TreeMeshGPT 约 4 分钟
FastMesh-V1K 约 21 秒
MeshFlow 约 1.2 秒

MeshVAE 的特点:

  • 连续表示,避免坐标量化;
  • latent token 数量约为原始顶点的四分之一;
  • 四倍下采样时,边结构 F1 仍约为 \(0.89\)。

11. 优点和局限

优点

  • 生成速度快;
  • 顶点坐标保持连续;
  • 网格表示紧凑;
  • 支持点云和参考图像条件;
  • 不需要逐个生成三角面。

局限

  • edge embedding 的分数阈值判断可能产生错误连接;
  • 扩散预测不准时可能出现孔洞;
  • Chamfer Distance 不能完整评价拓扑质量;
  • 完整训练需要大规模数据和 GPU 资源。

12. 最终总结

MeshFlow 的核心可以压缩成三个动作:

flowchart LR A[拓扑连续化] --> B[MeshVAE 压缩] B --> C[Flow DiT 并行生成]

对应的技术含义是:

  1. 用顶点 embedding 计算连接分数来表示边;
  2. 用 MeshVAE 把网格压缩到紧凑 latent;
  3. 用 Flow Matching 从噪声并行生成整个 latent;
  4. 解码后再通过几何规则恢复三角面。

最关键的思想是:

先把离散网格变成连续、紧凑的表示,再使用扩散模型生成。

posted @ 2026-09-29 10:41  S-X-Q  阅读(10)  评论(0)    收藏  举报