从"表示"开始把地基打牢
这一部讲六件事:3D 生成到底在生成什么、3D 有哪些"表示"、怎么从一个连续场抠出一张网格、 当今主线(原生 3D latent 扩散)的流水线长什么样、驱动它的两台引擎(SDS 与 rectified flow)有何不同, 以及把全书串起来的 4 个痛点。后面每一部都建立在这六节之上。
§1.13D 生成在生成什么?
你已经熟悉 NeRF 和 SDF。请先把一句话刻进脑子里: NeRF、SDF、网格、点云——这些都是「表示 (representation)」,每一个都是一个已经拟合好的物体。 你训练一个 NeRF,是把一个场景的几百万个权重优化到位;它回答的是"这个形状怎么存?"
3D 生成问的是完全不同的问题:学一个关于形状的分布,从里面采样出新的形状。 关键的认知转弯是:你几乎从不直接生成 NeRF/SDF 的那几百万个权重;而是先训一个 autoencoder,把每个形状压成一个小小的 latent(可能是一组向量 vecset、一个 triplane、或一个稀疏体素格),它的 decoder 能把 latent 还原成一个 occupancy/SDF 场;然后在这些 latent 上学分布(用扩散 / flow 模型)。 NeRF/SDF 退化成了 decoder 的目标,生成发生在更高一层——latent 空间里。
§1.2表示 zoo · 你能把 3D 存成什么
生成的第一步是选一种表示。下表是这个领域的"动物园"。看两件事:哪些是显式 (explicit)(直接存坐标)、 哪些是隐式 (implicit)(存一个要去查询的函数);以及哪些通常用作生成 latent、哪些用作输出格式。
| 表示 | 显/隐 | 存什么 | 内存量级 | 对生成的利弊 |
|---|---|---|---|---|
| Voxel / occupancy 格 | 显式 | 规则 3D 网格上的占据/密度 | + 规则网格好上 3D CNN;− 立方内存,分辨率卡在 ~ | |
| Point cloud | 显式 | 无序 集 | + 便宜灵活;− 没有面 / 拓扑,得再 meshing | |
| Polygon mesh | 显式 | 顶点 + 面(连通性) | + 美术 / 引擎要的最终格式;− 不规则、变长,极难直接生成 | |
| 隐式 SDF | 隐式 | 有符号距离 ,面在 | 一个 MLP | + 光滑、水密、任意分辨率;− 必须取面 |
| 隐式 occupancy 场 | 隐式 | ,面在 | 一个 MLP | 同 SDF;是分类器口味 |
| NeRF / 辐射场 | 隐式 | 密度 + 视角相关颜色 | MLP / hash 格 | + 从图像出真实感;− 是外观不是几何,无干净面 |
| 3D Gaussian Splatting | 显式 | 一堆 3D 高斯(均值、协方差、不透明度、SH 色) | + 实时渲染、锐利;− 不是面,meshing 是额外活 | |
| Triplane | 混合 | 3 张轴对齐 2D 特征平面 + 小 MLP | + 紧凑、对 2D CNN 友好的 latent;− 轴对齐偏置、容量有限 | |
| Vecset | 隐式 latent | 无序的 个 latent 向量() | + 置换不变、无网格偏置,transformer 的理想 latent;− 需要学好的 decoder | |
| DMTet | 混合 | 可变形四面体格 + 逐顶点 SDF → marching tets | + 可微网格、能学拓扑;− 分辨率受 tet 格限制 | |
| FlexiCubes | 混合 | cube 格上的标量场 + 额外逐 cell 权重 → dual MC | + 比 DMTet 更锐、质量更高的可微网格;− 仍受格分辨率限制 |
记住这条分工:生成 latent 通常是 triplane / vecset / 稀疏体素结构化 latent; 输出几乎总是一张 polygon mesh(从隐式场取出来的),偶尔附带 3DGS/NeRF 做外观。 SDF/occupancy 是 latent 解码出来的那个中间场。下面这个 demo 让你亲手感受同一个形状的五种存法和它们的"参数账单"。
先点 隐式 SDF:整张平面被一个连续函数染色,零等值线就是表面——一个 MLP 就能存任意分辨率。 再切 Voxel 格并拉分辨率,感受 内存(这里是 2D 的 ,3D 里更狠)。 点云没有面、网格不规则、高斯只管渲染不管表面。这就是为什么"latent 用隐式场、输出用网格"成了标准分工。
§1.3从场到面 · 取面与"可微取面"
隐式场(SDF/occupancy)要变成能用的网格,得做等值面抽取 (iso-surface extraction)。经典算法是 Marching Cubes(Lorensen & Cline, 1987):把场采样到规则格上,看每个立方体 8 个角的符号; 种符号组合按对称性归约成 15 个 case 查表,每个 case 给一组固定的局部三角化;顶点位置在立方体的边上由场值线性插值到等值 决定。 快、稳,但对拓扑不可微(离散查表挡住了梯度),还会带格对齐的伪影。
现代生成里我们常常想让渲染出的图像上的 loss 一路反传到底层的场。于是出现了可微取面: 离散的拓扑决定每步固定,但顶点位置是场值的连续函数,所以 存在。
DMTet(Shen et al., NeurIPS 2021)在一个(可变形的)四面体格的每个顶点 上存一个 SDF 值 。 Marching Tetrahedra 按每个 tet 四个 SDF 值的符号三角化;边 上的过零点为
它对 和 都可微。格顶点本身也是可学的偏移,所以模型能变形格子去贴细节。 FlexiCubes(Shen et al., SIGGRAPH 2023)在 Dual Marching Cubes 上再加一组可学的逐 cell 权重 (把对偶顶点挪到 cell 内任意位置)和切分权重 ,给优化器足够的自由度去抓锐利的边和角、产出水密流形网格——这是 DMTet/MC 做不到的。 你在 GET3D、Fantasia3D、InstantMesh、MeshLRM 里反复会见到它们。
Marching Cubes 的 2D 表亲。拖动两个青色圆心改变 SDF;拉分辨率看实线(抽取的折线)逼近虚线(真实等值线)。 每个格 cell 看 4 角符号、在变号的边上线性插值出一个顶点——分辨率越高越平滑,但 3D 里就是 的内存代价。 这正是高分辨率原生生成(Direct3D-S2 把它推到 )必须用稀疏体素的原因。
§1.4当今主线 · 原生 3D latent 扩散流水线
把 2023–2025 的主流配方画成一条流水线(3DShape2VecSet → CLAY → TripoSG / Step1X-3D / Hunyuan3D)。它和老的 SDS 完全不同:
| 阶段 | 做什么 |
|---|---|
| (a) 训一个 3D VAE | Encoder 把采样的表面点压成 latent(vecset ,或稀疏体素结构化 latent);decoder 把 latent + 查询点解码成 occupancy/SDF。监督 = 重建 + KL。 |
| (b) 在 latent 空间训 DiT | 在那个 latent 空间里训一个 扩散 / flow Transformer,条件是图像 embedding(DINOv2/CLIP)或文本(CLIP/T5),靠 cross-attention 注入。这才是真正的生成模型。 |
| (c) 推理 | 从 DiT 采一个 latent → decoder 在稠密查询格上解出场 → Marching Cubes → mesh → (单独一步)贴材质。 |
整个领域里最该背下来的一条公式是 3DShape2VecSet(Zhang et al., SIGGRAPH 2023)的 encode/decode 结构: 用 farthest-point sampling 选出的查询种子 去 cross-attention 输入点特征,得到 latent 集 ; 任意查询点 的 occupancy 是 对 的一次 cross-attention 读出:
其中 是采样的表面点、 是位置编码、。 vecset 是无序的、置换不变的、没有网格偏置——天生就是 transformer 的 latent。 (精确的 normalization / 头部细节请对照原文,这里给的是正确的结构骨架。)
为什么"几何先行、材质后补"成了几乎所有系统的两段式标配?三点: (1) 几何的分布和 RGB 差一个量级,几何用 3D-native 的 occupancy/SDF 目标学最好,而材质复用预训练的 2D / 多视图图像扩散最省; (2) 先有一张干净 mesh,就有了精确表面去 condition 材质(渲 depth/normal/position → 多视图 PBR → 烤进 UV),保证几何-材质对齐; (3) 模块化——VAE/DiT 和 paint 模型能各自独立替换升级。CLAY、Hunyuan3D、TripoSG、Step1X-3D 全这么干。
把推理写成 20 行教学伪代码(rectified-flow 版,下一节解释为什么是直线流):
# 原生 3D latent 扩散:从噪声到一张 mesh(教学版,省略 batch / 数值细节)
import torch
from skimage.measure import marching_cubes
@torch.no_grad()
def generate_mesh(dit, vae_decoder, image_embed, N=2048, C=64,
steps=25, grid_res=256):
# 1. 从 VAE latent 空间的纯高斯噪声起步(vecset 形状 N x C)
z = torch.randn(1, N, C, device="cuda")
# 2. rectified-flow ODE:把速度场从 t=0(噪声) 积分到 t=1(数据)
# 直线流 => 朴素 Euler、很少的步数就够
ts = torch.linspace(0, 1, steps + 1, device="cuda")
for i in range(steps):
t = ts[i].expand(1)
v = dit(z, t, cond=image_embed) # 预测的速度 v_theta(z_t, t | image)
z = z + (ts[i+1] - ts[i]) * v # 朝数据流形走一个 Euler step
# 3. 在单位立方体里铺一个稠密查询格
lin = torch.linspace(-1, 1, grid_res, device="cuda")
gx, gy, gz = torch.meshgrid(lin, lin, lin, indexing="ij")
queries = torch.stack([gx, gy, gz], -1).reshape(1, -1, 3)
# 4. decoder 把 latent 解码成每个查询点的 occupancy/SDF
# (每个查询点对 latent 集 z 做一次 cross-attention)
field = vae_decoder(queries, z).reshape(grid_res, grid_res, grid_res)
# 5. 抠等值面(occupancy=0.5 或 SDF=0)-> 三角网格;贴材质是下游单独一步
verts, faces, normals, _ = marching_cubes(field.cpu().numpy(), level=0.5)
return verts, facesDemo 7(在 Part VI)把第 1、4、5 步可视化:采表面点 → 压成 个 latent → 查询解码 → marching squares 重建。 现在你已经能看懂那张图了。
§1.5两台生成引擎 · SDS vs Rectified Flow
全站的生成动力,归根结底来自同一类模型:扩散模型 (diffusion model)。两台"引擎"都建在它上面—— 引擎 A 把一个冻结的扩散模型借来当评委(SDS),引擎 B 则是扩散模型的一个更快变体(rectified flow)。 所以先花一节,假设你没接触过扩散,从零把它讲清楚。
先修 · 扩散模型 30 秒讲清
扩散模型只干一件事:学会把噪声一点点"擦"回数据。分两半看。
① 前向(加噪,不用学):拿一份干净数据 (一张图、或一个 latent),逐步掺高斯噪声,掺到最后变成一团纯噪声。任意一步都有闭式:
时间 从 (干净)走到 (纯噪声),保留系数 从 单调降到 。这一步没有参数,就是照公式掺噪声。
② 反向(去噪,要学):训一个网络 ——看着一张加了噪的 ,把当初掺进去的那团噪声 猜出来。目标就是一个均方误差:
怎么生成:从一团纯噪声 出发,反复"猜噪声、减掉一点点",, 最后落到数据分布上——就采出一个新样本。想生成"对应某张图 / 某段文字"的东西,就把条件 (图像 / 文本 embedding)一起喂进去 ; 想让它更听话,用 classifier-free guidance (CFG) 把条件方向放大:,这里的 就是常说的"CFG 强度"。
引擎 A · Score Distillation Sampling(SDS)
DreamFusion(Poole et al., 2022)的招数:优化一个 3D 表示(参数 ,比如一个 NeRF),让它的渲染图 被一个冻结的文生图扩散模型 (就是上一节那个噪声预测器 ,权重锁死不动)打分:
是加噪的渲染图, 是文本 embedding。白话:给渲染图加点噪声,问冻结的 2D 模型 "往哪改更像这段文字?",再把 3D 参数往那个方向推;全程不需要任何 3D 训练数据。 它过饱和 / Janus 的原因也写在公式里:要拿到可用信号得用很大的 guidance(mean-seeking → 过平滑过饱和); 而每个视角被一个没有 3D 一致性约束的 2D 模型独立打分,于是每个视角都想长成"正脸"——多面 Janus 由此而来。
引擎 B · DDPM vs Rectified Flow
刚讲的就是标准扩散 DDPM(Ho et al., 2020):逆转那条多步加噪链、每步预测噪声 ,采样要走几十~几百步,而且轨迹是弯曲的。 Rectified flow / flow matching(Liu et al., 2022;Lipman et al., 2022)换个目标:不学"逐步去噪",而是直接学一个速度场 , 把噪声 沿直线匀速送到数据 :
目标速度就是那条直线的常数方向 。直线路径 → 近乎线性的 ODE → 采样步数少得多、训练更稳。 这就是 2025 那一波(TripoSG、Step1X-3D、TRELLIS)都改用 rectified-flow 的原因:3D 的一次前向(解码 + marching cubes)很贵, 砍步数比在 2D 里更值钱。
八个噪声样本(灰点)要走到数据环(青点)。把步数 K 调到很小(如 4),切换路径类型: 直线流哪怕 2 步也几乎精确落点;弯曲扩散步数一少就overshoot、落不到环上。 每一步在 3D 里都意味着一次大 DiT 前向,所以"直线 → 少步"是 2025 主线模型的共同选择。
§1.64 个痛点 · 把全书串起来的轴
在读任何一篇论文之前,先把这四个痛点记住。后面每一张 paper 卡都会映射回其中至少一个。 不同流派的根本区别,就是它们优先攻哪个痛点。
痛点 (a):数据稀缺
2D 图像模型吃的是几十亿张图(LAION-5B);而最大的开源 3D 库 Objaverse 才 ~80 万、Objaverse-XL ~1000 万但很脏。 差几个数量级。→ 这逼出了"借 2D 先验"(SDS、多视图扩散),也让"原生 3D 预训练"格外珍贵。
痛点 (b):没有"画布"——表示与拓扑
2D 图永远是规则的 像素阵,CNN/ViT 直接吃。可网格是不规则的(顶点/面数可变、连通性任意), 没有固定张量形状——这逼出了上面的 latent 迂回,还要额外解决取面(DMTet/FlexiCubes/MC)和拓扑(亏格、洞、分件)。
痛点 (c):多视图一致性 / Janus
一个物体必须从每个角度看都对、且自洽。2D 先验方法最典型的翻车就是 Janus(多面)。→ 这逼出了一致多视图扩散、和直接学 3D 分布的原生方法。
痛点 (d):又快、又好、又能用
逐资产优化(SDS,分钟~小时)vs 前馈(亚秒)vs 采样质量,是一条贯穿始终的张力。 而"能用"还要求干净拓扑 + 可重光照 PBR 材质 + 可绑定动画。→ 这逼出了 LRM、rectified flow、蒸馏、mesh-AR、PBR、可动资产生成。
评估两句话备用:几何看 Chamfer Distance(两点集最近邻距离均值,越小越好) 与 F-score(阈值内精确率/召回率的调和平均,更抗离群点); 文本/图像对齐看 CLIP-score / CLIP-R-Precision 与三模态的 ULIP。术语都收进了附录名词表。