跳转到内容

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

是什么

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

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

要看这个形状长什么样,就在空间里采点查 d,把所有 d=0 的点连起来——那就是表面。整个 ShapeNet 椅子类的几千把椅子被压成同一个 MLP 的权重 + 每把椅子一个 256 维向量,加起来不到 10 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——把输入坐标直接拼接到第 4 层(论文中部)。思路和 ResNet 一致,让网络更容易学高频细节。

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

实践案例

案例 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。整个椅子库压成 ~7 MB + 6 万 KB ≈ 13 MB。

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

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

冻结网络 θ
随机初始化 z
对每个观测点 x_obs(来自部分扫描),它的 SDF 应该接近 0
loss = Σ |f_θ(z, x_obs)| + 正则项 ||z||²
对 z 反向传梯度,迭代几百步

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

案例 3:潜空间插值

z_chair = encode(office_chair)
z_throne = encode(king_throne)
for t in [0, 0.25, 0.5, 0.75, 1.0]:
z_t = (1 - t) * z_chair + t * z_throne
render(z_t)

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

案例 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。

适用 vs 不适用场景

适用

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

不适用

  • 实时渲染(用 3d-gaussian-splatting 或传统三角网格)
  • 非水密结构(薄板、布料、流体)
  • 跨大类别的统一表达(用 transformer-based 的更新一代方法)
  • 需要显式拓扑信息的下游任务(CAD、网格编辑)

历史小故事(可跳过)

  • 1996:John Hart 写下 sphere tracing——只要你有一个能给 SDF 的函数,就能渲染任意形状。但那时候 SDF 都是手写解析公式(球、立方体、莫比乌斯环),表达能力极弱。
  • 2017:PointNet 让神经网络第一次直接吃 3D 点云,几何深度学习开端。
  • 2019 年 1 月:DeepSDF 在 arXiv 出现。同月几乎同一周,Mescheder 等的 Occupancy Networks 也出现——两组人独立想到了”用 MLP 表达形状”,只是一个回归 SDF、一个回归 occupancy probability。两篇都中了 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 训练标配

反向链接

  • 3d-gaussian-splatting —— 3D Gaussian Splatting — 用一堆 3D 模糊光斑重建场景
  • nerf-2020 —— NeRF — 用一个 MLP 把整个场景”背”下来
  • pytorch —— PyTorch — 深度学习主流框架