跳转到内容

DeepSDF — 用一个 MLP 把整类 3D 形状的距离场背下来

待复核

DeepSDF 是一个用神经网络把”3D 形状”编码成连续函数的方法。日常类比:与其用一沓积木拼一把椅子,不如训一个会回答”任意一点到椅子表面有多远”的小盒子;表面就藏在所有”距离 = 0”的位置里。

输入:一个空间点 (x, y, z) + 一个 256 维的”形状身份证” z。 输出:一个标量 d——点到形状表面的有符号距离(在外面是正、在里面是负、刚好在表面是零)。

要看这个形状长什么样,就在空间里采点查 d,把所有 d=0 的点连起来——那就是表面。整个 ShapeNet 椅子类的几千把椅子被压成同一个 MLP(约 7 MB)+ 每把椅子一个约 1 KB 的 256 维向量;六千把合计大约十几 MB,靠的是网络共享、潜码人均很便宜。

不理解 DeepSDF,下面这些事就接不上:

  • 为什么 2019 年之后 3D 重建论文里突然到处是”neural”和”implicit”——这条路就是从这里岔出来的
  • 为什么 nerf-2020 的 MLP 长得跟 DeepSDF 几乎一样(只是输出从距离变成颜色+密度)
  • 为什么 3d-gaussian-splatting 这种”反神经场”的工作要专门拿 NeRF / DeepSDF 当对照组——它们解决的是同一个问题,技术路线分叉
  • 为什么”形状不是一堆三角形而是一个函数”会成为几何深度学习的中心想法

DeepSDF 能跑通靠 三件事

  1. 把形状变成函数 f(z, x) ≈ d:传统做法把形状存成体素网格——256³ 分辨率就要 64 MB,1024³ 直接爆。DeepSDF 把这个函数装进一个 8 层、每层 512 宽的 MLP,再把这一类所有形状的差异塞进一个 256 维的 z 向量。分辨率可以无限——你想看多细,就在多细的网格上查 f。

  2. auto-decoder(没有编码器):常见做法是 encoder-decoder——给个点云,编码器算 z、解码器还原形状。DeepSDF 干脆砍掉编码器:训练时给每个形状随机初始化一个 z_i,然后同时优化网络权重 θ 和所有 z_i。推理时遇到新形状(比如一段不完整的点云)就冻住网络,只反向传梯度更新一个新 z,让网络输出的 SDF 拟合观测。这个想法后来在 NeRF 的场景潜码、扩散模型的条件生成里反复出现。

  3. truncated L1 损失:训练数据是 (x, SDF_true(x)) 对。loss 不是普通 L1,而是 |clamp(f(z,x), δ) − clamp(SDF_true, δ)|——|d| > δ 的远处一律截断成 δ。远处的距离场不重要(反正离表面老远);只有 |d| < δ 的近表面区需要精确。这一步让网络把容量集中在关键区。

加一个细节:MLP 中间有 skip connection——把 latent 码 z 与坐标 (x,y,z) 再次拼接到第 4 层输出(论文中部)。思路和 ResNet 一致,让网络更容易学高频细节。

为什么是 SDF 而不是 occupancy(点在内还是外的二分类)?SDF 给出连续的、有梯度的、有距离信息的场——这意味着每一点的梯度方向直接告诉你”表面在哪个方向”,对优化和后续 ray-marching 都更友好。Occupancy 路线(同期的 Mescheder 工作)参数更省但渲染时缺少法向信息,需要数值估计。两条路在不同任务上各有优势。

案例 1:从 ShapeNet 训一个椅子类的 DeepSDF

Section titled “案例 1:从 ShapeNet 训一个椅子类的 DeepSDF”
shapenet/chairs/ ← ~6000 把椅子的网格
chair_000.obj
chair_001.obj
...

预处理:每把椅子在表面附近采 50 万个点,每个点算一次真值 SDF(用网格的快速距离查询)。 训练:一次喂一批 (i, x, sdf_true),i 是椅子编号;网络查 z_i 和 x,输出 f_θ(z_i, x),loss 反传同时更新 θ 和 z_i。 结果:θ 大约 7 MB,每把椅子一个 256 维 z 约 1 KB;六千把潜码合计约 6 MB,整库约 13 MB——和开篇口径一致。

案例 2:形状补全(DeepSDF 最亮的应用)

Section titled “案例 2:形状补全(DeepSDF 最亮的应用)”

你扫描一把椅子,但椅子背被遮挡,只拿到正面 + 座位 + 一条腿的部分点云。怎么补全椅背?

冻结网络 θ
随机初始化 z
# 观测点来自部分扫描,近似落在表面上 → 目标 SDF≈0
# (训练时用真值 SDF 监督;补全时没有完整真值,只约束观测点)
loss = Σ |f_θ(z, x_obs)| + 正则项 ||z||²
对 z 反向传梯度,迭代几百步

收敛后,z 落在”椅子潜空间”里最像观测的那把椅子附近——网络用类先验(训练时见过的椅子分布)把椅背脑补出来。这是经典体素方法做不到的:它们没有”椅子是什么”的先验。

没有编码器,插值用的是训练时一起优化出来的 z 表(或案例 2 那种对观测优化得到的 z):

z_chair = z_table["office_chair"] # 训练得到的潜码,不是 encode()
z_throne = z_table["king_throne"]
for t in [0, 0.25, 0.5, 0.75, 1.0]:
z_t = (1 - t) * z_chair + t * z_throne
render(f_θ(z_t, ·)) # 冻住 θ,只换 z

中间过渡的形状是真实 3D 的、平滑的、几何合理的——表面随 t 连续变形。这种”形状语义算术”在体素/网格表示里几乎做不出来。

案例 4:DeepSDF vs 体素表示的内存对比

Section titled “案例 4:DeepSDF vs 体素表示的内存对比”
表示存储分辨率备注
体素网格 256³64 MB固定 256³单形状,不能更细
体素网格 1024³4 GB固定 1024³几乎无法训练
三角网格几 MB拓扑固定难以处理形状变化
DeepSDF (单形状)~7 MB MLP + 1 KB z任意一个 MLP 服务整类
DeepSDF (整类椅子 6000 把)~7 MB + 6 MB任意网络共享,潜码人均 1 KB

可以看出 DeepSDF 的关键优势是类内压缩 + 任意分辨率——不是”比单个网格更小”,而是”对一整类形状,整体几何统计被网络压成一个共享先验”。

  1. 只能表达水密表面:SDF 的定义要求”内”和”外”明确,所以形状必须是闭合的。布料、叶片、薄板这种”两面都是外”的结构 SDF 表达不了。后来 NeuS 用 occupancy + alpha 的混合表达绕过这个限制。

  2. 跨类别不泛化:在椅子上训出来的网络,让它表达飞机会拟合得很差——同一个 MLP 的容量分给了”椅子的几何统计”,没有给”飞机”留位置。要做多类得加 conditional input 或者重训。

  3. 渲染要 marching cubes 或 ray marching,慢:要可视化形状,要么在网格上查 f 然后跑 marching cubes 提网格(O(N³) 查询),要么对每条光线 ray-march(每条光线几十次网络前向)。比传统三角网格光栅化慢几个数量级。NeRF 继承了这个慢,3D Gaussian Splatting 才把渲染速度拉回实时。

  4. auto-decoder 推理需要梯度下降:拿到新形状要花几秒到几分钟优化 z;不像 encoder 那样一次前向出结果。后续工作(如 3d-gaussian-splatting)部分回归了显式表达来避开这个问题。

  5. z 维度是个超参:256 维是论文里的设定,调小(64)会丢细节、调大(1024)会过拟合。“形状空间该多大”没有先验,要按数据规模试。这个问题在所有 latent code 方法里都存在,扩散模型时代部分被替代为 token sequence。

适用

  • 同类形状的紧凑表达(一个 MLP + 几千个潜码 = 整个类)
  • 形状补全 / 部分观测下的重建
  • 形状空间插值与生成(潜空间几何运算)
  • 后续神经场研究的起点(neural fields、SDF-based 渲染)

不适用

  • 实时渲染(用 3d-gaussian-splatting 或传统三角网格)
  • 非水密结构(薄板、布料、流体)
  • 跨大类别的统一表达(用 transformer-based 的更新一代方法)
  • 需要显式拓扑信息的下游任务(CAD、网格编辑)
  • 1996:John Hart 写下 sphere tracing——只要你有一个能给 SDF 的函数,就能渲染任意形状。但那时候 SDF 都是手写解析公式(球、立方体、莫比乌斯环),表达能力极弱。
  • 2017:PointNet 让神经网络第一次直接吃 3D 点云,几何深度学习开端。
  • 2018-12 / 2019-01:Mescheder 等 Occupancy Networks(arXiv 1812.03828)与 DeepSDF(arXiv 1901.05103)约一个月内先后上线——两组人独立想到”用 MLP 表达形状”,一个回归 occupancy、一个回归 SDF。两篇都中了 CVPR 2019。
  • 2020:NeRF 把同样的 MLP 思想推到 view synthesis——把”距离场”换成”辐射场”。
  • 2023:3D Gaussian Splatting 把这一脉的”用 MLP 当容器”反过来,回到显式表达,但训练范式(可微渲染 + 梯度下降)继承自 NeRF。

四年里这条线从”形状的 MLP 表达”演化到”实时新视角合成”,每一步都站在 DeepSDF 的肩上。

  1. 场可以是函数,函数可以是网络——任何在空间里连续定义的量(距离、占据、颜色、辐射),都能塞进一个 MLP 的权重里
  2. auto-decoder 比 encoder-decoder 简单:训练时一起优化数据潜码和网络参数;推理时用梯度下降找潜码。这个范式后来到处都是
  3. 离散表达 → 连续表达 是几何深度学习的一次范式跳跃,类似机器学习从”特征工程”到”端到端学习”
  4. 同时期撞车很常见:DeepSDF 和 Occupancy Networks 约一个月内先后出现,说明”想法成熟了”,不是某个人灵光一现
  • nerf-2020 —— 直系晚辈:MLP 的输出从 SDF 换成 (RGB, σ),加上体渲染
  • 3d-gaussian-splatting —— 从 NeRF 路线再分叉,回到显式表达但保留训练范式
  • pytorch —— DeepSDF 官方实现的训练框架
  • colmap-2016 —— 真实场景拿姿态的前置工具,后来 NeRF 训练标配
  • pointnet —— PointNet — 直接吃点云的 3D 神经网络