PhysForge:从一张图片生成可交互的 3D 物体

PhysForge:从一张图片生成可交互的 3D 物体

一句话:先由 VLM 写出“物理蓝图”,再由生成模型把部件形状和运动关节一起生成出来。

普通 3D 生成器主要解决“物体长什么样”。PhysForge 还要解决“物体怎样被操作”。例如输入一张柜子图片,输出不只是一个柜子 mesh,还应包含:柜体、柜门、把手;柜门连接到柜体;把手固定在柜门;柜门绕铰链旋转。

1. 先记住四层数据

理解 PhysForge 最容易混淆的地方,是把不同层次的信息混在一起。它实际上有四层:

整体物体
  └─ 部件结构:柜体、柜门、把手
       └─ 粗体素:每个部件大概占据哪些空间位置
            ├─ 几何特征:部件长什么样
            └─ KineVoxel:这个部件怎样运动

但最后一层有一个重要区别:

几何特征通常有多个,对应部件中的多个几何 voxel;KineVoxel 通常是部件级的,一个可动部件对应一份关节表示。

它不是“每一个几何 voxel 都有一套铰链参数”。

2. 全流程

flowchart TD I["输入图片"] --> V["TRELLIS 第一阶段:生成整体粗体素"] I --> E2["图像编码"] V --> E3["PartField + 位置感知 3D 卷积"] E2 --> P["VLM:生成物理蓝图"] E3 --> P P --> B["部件 bbox、属性、父子关系、关节类型"] V --> S["按部件 bbox 取候选粗体素"] B --> S S --> G["几何特征序列"] B --> K["部件级 KineVoxel"] G --> T["共享 Transformer 联合生成"] K --> T T --> GD["几何解码器"] T --> KD["运动学解码器"] GD --> M["部件 mesh 与纹理"] KD --> J["关节原点、轴方向、运动范围"] B --> A["按父子关系组装"] M --> A J --> A A --> O["可交互 3D 资产"]

VLM 和第二阶段的分工是:

VLM 规划阶段 第二阶段生成阶段
有哪些部件 部件具体长什么样
每个部件的粗略位置 铰链精确在哪里
谁是谁的父部件 关节轴朝哪个方向
fixed、revolute、prismatic 等类型 运动范围是多少
材料、质量、功能、状态等语义 最终 mesh、纹理和连续关节参数

3. 第一阶段:VLM 生成物理蓝图

3.1 输入

规划器使用 Qwen2.5-VL,接收三类信息:

  • 图片:提供外观、类别和可见结构;
  • 可选 2D mask:帮助控制部件划分的粒度;
  • 粗 3D 体素:提供三维空间线索,例如前后关系、厚度和位置。

图片和 mask 经过图像编码器。粗体素先经过 PartField 提取部件相关特征,再经过位置感知的 3D 卷积。论文提到输出特征宽度为 512;这表示特征向量的维度,不是体素数量。

3.2 输出

VLM 输出一条完整的、结构化的蓝图序列。对第 \(i\) 个部件,可以抽象成:

\[B_i=(b_i,\pi_i,\tau_i,\Phi_i). \]

  • \(b_i\):三维轴对齐包围盒;
  • \(\pi_i\):父部件;
  • \(\tau_i\):关节类型;
  • \(\Phi_i\):材料、质量、功能、状态和可供动作等属性。

教学示例:

整体:储物柜,场景 = 卧室

柜体:
  bbox = 主体区域
  parent = base
  joint = fixed
  function = 容纳物品

柜门:
  bbox = 正面薄板区域
  parent = 柜体
  joint = revolute
  function = 开闭储物空间
  state = [关闭, 打开]

把手:
  bbox = 门外侧小区域
  parent = 柜门
  joint = fixed
  affordance = [抓取, 拉动]

因此,VLM 直接给出的是:

柜门连接到柜体,类型是 revolute
把手连接到柜门,类型是 fixed

它不直接给出铰链的精确连续数值:

Joint origin = (x, y, z)
Joint axis   = (ax, ay, az)
Joint limits = (lower, upper)

这些数值由第二阶段生成。

3.3 是多轮对话吗?

论文描述的是一次规划任务中,VLM 自回归地生成完整蓝图序列:

整体类别
→ Part-1 的包围盒和属性
→ Part-2 的包围盒和属性
→ Part-2 的父部件和关节类型
→ ...
→ 结束标记

所以可以理解为:一次输入、一次完整规划输出;内部一个 token 接一个 token 地生成。 论文没有描述把整体信息、部件信息和关系信息拆成三次独立调用,也没有公开完整的 prompt 和字段顺序。

3.4 包围盒如何表达?

一个三维包围盒需要 6 个坐标:

\[ b_i=(x_{\min},y_{\min},z_{\min},x_{\max},y_{\max},z_{\max}). \]

PhysForge 增加了 64 个离散坐标 token:<box0> 到 <box63>,以及 <boxs>、<boxe> 两个边界 token。因此一个包围盒可以用 6 个坐标 token 紧凑表示。

教学示例:如果坐标已经归一化到 \([0,1]\),可以用

\[q(u)=\operatorname{round}(63u) \]

把坐标映射到 64 档。\(u\approx0.5\) 时,大约对应 <box32>。这是帮助理解的简化示例,论文没有公开所有坐标归一化细节。

3.5 为什么物理属性能帮助分部件?

只看外观时,柜门和把手可能颜色相近。若模型还要同时回答:

把手可以抓取
柜门负责开闭
把手固定在柜门上

这些功能和关系会帮助它把两个部件分开。因此,物理属性不仅是蓝图的输出,也为部件划分提供了额外语义约束。

4. 第二阶段的基础:OmniPart 如何生成部件 mesh?

PhysForge 的几何生成部分建立在 OmniPart 的第二阶段上。先理解 OmniPart 的核心流程。

4.1 整体粗体素先变成部件候选体素

TRELLIS 第一阶段先根据图片生成整个物体的稀疏 active voxel。它们描述物体整体大致占据的空间,不是最终 mesh。

VLM 给出每个部件的 bbox 后,系统从整体 active voxel 中取出位于 bbox 内的候选 voxel:

整体 active voxel
        ↓ 用柜门 bbox 裁剪
柜门候选 voxel
        ↓ 预测有效性,丢掉误包含的 voxel
柜门有效 voxel

形式上可以写成:

\[V_i^{\mathrm{candidate}} =\{v\in V^{\mathrm{whole}}\mid v\text{ 位于部件 }i\text{ 的 bbox 内}\}, \]

\[V_i^{\mathrm{valid}} =\{v\in V_i^{\mathrm{candidate}}\mid s_i(v)>\beta\}. \]

这里的 \(s_i(v)\) 是候选 voxel 属于部件 \(i\) 的有效性分数。

教学示例:柜门 bbox 圈进 10 个 voxel,其中 2 个属于柜体边缘。模型可以给这 2 个 voxel 较低的有效性分数,最后过滤掉它们。这个机制可以去掉误包含的 voxel,但不能补回 bbox 外漏掉的 voxel。

这不是“每个部件从零开始独立搜索 active voxel”,而是“整体 active voxel → bbox 取候选 → 有效性过滤”。

4.2 每个几何 voxel 有什么?

在 TRELLIS 的结构化 latent 中,一个 active voxel 通常由两部分组成:

空间位置 p_i + 局部特征 f_i

局部特征包含该位置附近的几何和外观信息。一个部件通常包含多个这样的几何 voxel,因此它对应的是一串几何 latent,而不是一个单独的几何向量。

OmniPart 同时保留:

整体物体 latent
+ 柜体 latent
+ 柜门 latent
+ 把手 latent

不同部件加入不同的 part position embedding,告诉 Transformer 哪些 token 属于同一个部件。整体 latent 提供全局上下文,防止部件互相漂浮或比例不一致。

4.3 OmniPart 如何得到 mesh?

生成过程可以概括为:

flowchart LR B["部件 bbox"] --> C["从整体 active voxel 取候选"] C --> L["每个部件的多个几何 latent"] L --> E["加入 part position embedding"] E --> T["共享 Transformer 联合去噪"] T --> F["过滤无效 voxel"] F --> D["几何解码器"] D --> M["部件 mesh 与纹理"]

它不是“先生成完整 mesh,再切成部件”,而是先把不同部件放进不同的 latent 区域,再同时生成这些部件。共享 Transformer 让局部部件和整体形状保持一致。

5. PhysForge 的新增:部件级 KineVoxel

OmniPart 解决的是“部件长什么样”。PhysForge 还要解决“部件怎样运动”。

5.1 一个可动部件的关节参数

对第 \(i\) 个可动部件,关节参数写成:

\[P_i=(O_i,A_i,L_i)\in\mathbb R^8, \]

其中:

  • \(O_i\in\mathbb R^3\):关节原点,例如铰链位置;
  • \(A_i\in\mathbb R^3\):关节轴方向;
  • \(L_i\in\mathbb R^2\):运动下限和上限。

例如柜门:

joint type = revolute
O = 铰链位置
A = 竖直方向
L = [0°, 90°]

5.2 KineVoxel 不是普通的空间 voxel

论文把 \(P_i\) 编码成一个特殊的 KineVoxel:

\[z_{k,i}=E_{\mathrm{kine}} \left(\operatorname{concat}(S_OO_i,S_AA_i,S_LL_i)\right). \]

这里的 \(z_{k,i}\) 是一个部件级运动学 latent/token。它的名字里虽然有 voxel,但它不表示空间中新增了一块实体体素。

更直观地说:

柜门的多个几何 latent:描述柜门长什么样
柜门的一个 KineVoxel:描述柜门怎样运动

如果一个物体有多个可动部件,通常会有多个部件级运动学表示;固定部件不需要独立的运动参数。

5.3 关节类型如何进入第二阶段?

VLM 预测的 revolute、prismatic 等类型会变成类型 embedding:

\[\tilde z_{k,i}=z_{k,i}+E_{\mathrm{type}}(\tau_i). \]

这很重要,因为同样的“轴方向”在不同关节类型下含义不同:

  • revolute:绕轴旋转;
  • prismatic:沿轴平移;
  • fixed:不产生相对运动。

5.4 几何特征和 KineVoxel 如何交互?

在几何 latent 下采样后,系统将几何 token 序列和 KineVoxel 序列拼接,再送入共享的中间 Transformer:

flowchart TD V["部件有效粗体素"] --> GE["几何编码器"] GE --> G["部件的多个几何 latent"] P["部件关节参数 O、A、L"] --> KE["运动学编码器"] T["VLM 的关节类型"] --> TE["类型 embedding"] KE --> ADD["加上类型 embedding"] TE --> ADD ADD --> K["部件级 KineVoxel"] G --> CAT["与 KineVoxel 拼接"] K --> CAT CAT --> TR["共享中间 Transformer"] TR --> GD["几何输出路径"] TR --> KD["运动学输出路径"] GD --> M["部件 mesh 与纹理"] KD --> J["关节原点、轴、范围"]

因此,二者的关系是:

多个几何 latent + 一个部件级 KineVoxel
              ↓
      共享 Transformer 交换信息
              ↓
     几何解码器 + 运动学解码器

这让柜门的几何形状可以帮助定位铰链,而 revolute 类型又能约束运动参数应按旋转关节解释。

训练时,运动学编码器可以接收真实关节参数作为监督。推理时,没有真实铰链位置;运动学 latent 和几何 latent 都从噪声状态开始,随生成过程逐步更新。

6. 第二阶段如何训练和推理?

6.1 训练:学习从噪声走向目标

第二阶段使用 Conditional Flow Matching。直观地说,训练时模型看到一个处于中间状态的几何和运动学 latent,学习它们应该朝哪个方向更新。

教学示例:一维情况下,噪声为 \(-1\),目标为 \(3\),可以用

\[X_t=(1-t)X_0+tX_1 \]

连接二者。模型看到当前状态、时间和蓝图条件后,预测更新方向。实际任务中的 \(X\) 同时包含几何 latent 和运动学 latent。

几何与运动学分别计算速度误差:

\[\mathcal L= \mathcal L_{\mathrm{geo}} +\lambda_{\mathrm{kine}}\mathcal L_{\mathrm{kine}}, \qquad \lambda_{\mathrm{kine}}=10. \]

运动学项权重更大,表示训练时特别重视关节参数的精度。

6.2 推理:从随机 latent 生成资产

输入:图片,可选 2D mask
粗体素 ← TRELLIS 第一阶段
蓝图 ← VLM(图像, 粗体素, mask)
根据 bbox 得到各部件候选粗体素
初始化几何 latent 和部件级运动学 latent
重复多个时间步:
    用蓝图、几何 latent 和 KineVoxel 预测更新方向
    同时更新几何与运动学 latent
部件 mesh ← 几何解码器(最终几何 latent)
关节参数 ← 运动学解码器(最终运动学 latent)
按蓝图中的父子关系组装资产

最终输出包括:

部件 mesh 与纹理
部件父子关系
材料、质量、功能等属性
关节类型
关节原点、轴方向、运动范围

7. 完整例子:柜门如何打开?

下面是教学示例,数值不代表论文实验结果。假设 VLM 输出:

柜体 = base
柜门 = 柜体的 revolute 子部件
把手 = 柜门的 fixed 子部件

接着,从整体粗体素中按柜门 bbox 取出柜门候选 voxel。柜门的多个几何 latent 描述门板、边缘和厚度;另外生成一个部件级 KineVoxel,表示:

\[O=(0,0,0),\qquad A=(0,0,1),\qquad L=(0,\pi/2). \]

这表示柜门绕竖直轴从关闭状态旋转到 90 度。门上任意一点 \(x\) 的位置可以写成:

\[ x'=O+R(A,\theta)(x-O). \]

把手是柜门的固定子部件,所以它继承柜门的运动:

\[T_{\mathrm{world,handle}} =T_{\mathrm{world,door}}T_{\mathrm{door,handle}}. \]

如果输入的是抽屉,VLM 可能预测 prismatic。此时第二阶段生成的轴表示滑动方向,运动可以写成:

\[ x'=x+sA. \]

因此,关节类型是两阶段之间的重要接口:VLM 决定“旋转还是平移”,KineVoxel 生成“具体轴、位置和范围”。

8. 数据和边界

PhysForge 使用 PhysDB,为约 15 万个物体提供四层标注:

  1. 整体:类别、尺度、使用场景;
  2. 静态:部件、材料、质量;
  3. 功能:用途和状态机;
  4. 交互:可供动作、父子关系和关节类型。

PhysDB 主要帮助学习物理属性和高层结构。精确关节原点、轴方向和运动范围还使用 PartNet-Mobility 与 Infinite-Mobility 的运动学数据进行训练。

“physics-grounded”表示生成结果具有物理语义、部件层次和运动学结构,并不等于每个结果都已经通过真实仿真验证。论文没有把完整的碰撞、摩擦、惯性或控制器调参都交给 VLM 预测;这些仍可能需要部署阶段处理。

单张图片也无法消除遮挡、背面和内部结构的歧义。因此它更像是根据视觉和常识生成一个合理的可交互资产,而不是保证得到现实物体的精确数字孪生。

9. 五句话记住方法

  1. TRELLIS 先从图片得到整个物体的粗 active voxel。
  2. VLM 输出部件 bbox、属性、父子关系和关节类型。
  3. 每个部件从整体 active voxel 中取候选区域,再形成多个几何 latent。
  4. 每个可动部件额外有一个部件级 KineVoxel,表示关节原点、轴和运动范围。
  5. 几何 latent 与 KineVoxel 经过共享 Transformer 联合生成,最后分别解码为 mesh 和运动学参数。
posted @ 2026-09-21 19:55  S-X-Q  阅读(12)  评论(0)    收藏  举报