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 能跑通靠 三件事:
-
把形状变成函数 f(z, x) ≈ d:传统做法把形状存成体素网格——256³ 分辨率就要 64 MB,1024³ 直接爆。DeepSDF 把这个函数装进一个 8 层、每层 512 宽的 MLP,再把这一类所有形状的差异塞进一个 256 维的 z 向量。分辨率可以无限——你想看多细,就在多细的网格上查 f。
-
auto-decoder(没有编码器):常见做法是 encoder-decoder——给个点云,编码器算 z、解码器还原形状。DeepSDF 干脆砍掉编码器:训练时给每个形状随机初始化一个 z_i,然后同时优化网络权重 θ 和所有 z_i。推理时遇到新形状(比如一段不完整的点云)就冻住网络,只反向传梯度更新一个新 z,让网络输出的 SDF 拟合观测。这个想法后来在 NeRF 的场景潜码、扩散模型的条件生成里反复出现。
-
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 应该接近 0loss = Σ |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 的关键优势是类内压缩 + 任意分辨率——不是”比单个网格更小”,而是”对一整类形状,整体几何统计被网络压成一个共享先验”。
踩过的坑
-
只能表达水密表面:SDF 的定义要求”内”和”外”明确,所以形状必须是闭合的。布料、叶片、薄板这种”两面都是外”的结构 SDF 表达不了。后来 NeuS 用 occupancy + alpha 的混合表达绕过这个限制。
-
跨类别不泛化:在椅子上训出来的网络,让它表达飞机会拟合得很差——同一个 MLP 的容量分给了”椅子的几何统计”,没有给”飞机”留位置。要做多类得加 conditional input 或者重训。
-
渲染要 marching cubes 或 ray marching,慢:要可视化形状,要么在网格上查 f 然后跑 marching cubes 提网格(O(N³) 查询),要么对每条光线 ray-march(每条光线几十次网络前向)。比传统三角网格光栅化慢几个数量级。NeRF 继承了这个慢,3D Gaussian Splatting 才把渲染速度拉回实时。
-
auto-decoder 推理需要梯度下降:拿到新形状要花几秒到几分钟优化 z;不像 encoder 那样一次前向出结果。后续工作(如 3d-gaussian-splatting)部分回归了显式表达来避开这个问题。
-
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 的肩上。
学到什么
- 场可以是函数,函数可以是网络——任何在空间里连续定义的量(距离、占据、颜色、辐射),都能塞进一个 MLP 的权重里
- auto-decoder 比 encoder-decoder 简单:训练时一起优化数据潜码和网络参数;推理时用梯度下降找潜码。这个范式后来到处都是
- 离散表达 → 连续表达 是几何深度学习的一次范式跳跃,类似机器学习从”特征工程”到”端到端学习”
- 同时期撞车很常见:DeepSDF 和 Occupancy Networks 同月出现说明”想法成熟了”,不是某个人灵光一现
延伸阅读
- 论文 PDF:DeepSDF arXiv 1901.05103(含完整推导和 ShapeNet 实验)
- 同期对照:Mescheder 等 Occupancy Networks — 同月、同议题、不同输出
- 后续延伸:nerf-2020 —— 把”距离场”换成”辐射场”
- 替代路线:3d-gaussian-splatting —— 反神经场,但继承可微渲染范式
关联
- 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 — 深度学习主流框架