1. 从"豆包"这个代号说起:3DGS到底在解决什么问题
第一次看到"3DGS论文解读 - 豆包"这个标题,很多人会愣一下——"豆包"是什么?是某个内部代号,还是某个工具链的昵称?其实在图形学圈子里,给论文或项目起个接地气的代号是常态,方便在组会、群聊里快速指代。抛开代号不谈,这个标题真正指向的核心是3D Gaussian Splatting(三维高斯泼溅),一个在2023年SIGGRAPH上引爆实时渲染领域的重建与渲染方案。
我接触3DGS的契机很实际:手头有一个需要把多视角照片快速变成可实时漫游三维场景的需求,传统摄影测量流程走一遍要几十分钟甚至几小时,而NeRF类方案虽然质量高,但渲染一帧要等好几秒,根本没法交互。3DGS出现之后,训练时间压到十几分钟,渲染直接跑到上百帧,这个反差让我意识到它不是一个"论文里的漂亮数字",而是真的能改变工作流的工具。
所以这篇解读不是把论文摘要翻译一遍,而是站在一个实际要用它做事的人的角度,把3DGS的核心机制、为什么它能这么快、训练和渲染里那些论文不会明说的坑,以及它和NeRF、传统网格重建的取舍关系讲清楚。适合已经听说过3DGS但没搞懂原理的人,也适合准备上手跑通第一个场景、却被环境配置和显存问题卡住的实践者。读完之后,你应该能判断自己的项目到底该不该用3DGS,以及用的时候要盯住哪几个关键参数。
2. 三维高斯泼溅的底层逻辑:为什么不是点云也不是NeRF
2.1 场景表示的第三条路:显式高斯基元
要理解3DGS,先得理解它和前辈们的根本分歧。传统三维重建最后大多落到网格(Mesh)或点云(Point Cloud)上,网格适合渲染但拓扑复杂区域容易破面,点云简单但渲染时会出现空洞、噪点,而且点云本身没有"体积感",光照和视角变化下表现很差。NeRF走的是另一条路:用一个多层感知机(MLP)把空间坐标和视角方向映射成颜色和密度,本质是隐式表示,渲染靠沿光线积分。质量惊艳,但每次查询都要过一遍网络,速度上不去。
3DGS的选择很聪明:它既不用网格也不用纯隐式网络,而是用一大堆三维高斯椭球来显式地"堆"出场景。每个高斯有自己的位置、旋转、缩放、不透明度和球谐系数(用来表达随视角变化的颜色)。你可以把它想象成用无数个半透明的、有方向性的"棉花糖"去填充空间,每个棉花糖的大小、朝向、透明度都可学。渲染的时候,把这些高斯按深度排序后投影到屏幕上做alpha混合,就得到了图像。
这个设计的关键在于:表示是显式的,但参数是可微的。显式意味着渲染不需要跑神经网络,速度天然快;可微意味着可以用梯度下降从照片里把这几百万个高斯"拟合"出来。这就是它同时拿下质量和速度的根本原因。
2.2 从一组照片到几百万个高斯:训练流程拆解
训练3DGS的输入是一组标定好的多视角照片,外加每张照片的相机内外参。整个流程可以拆成四步,我按实际跑代码时的顺序讲。
第一步是稀疏点云初始化。通常用COLMAP这类运动恢复结构(SfM)工具先跑一遍,得到相机位姿和一批稀疏三维点。这些点就是高斯的初始位置。这里有个容易被忽略的点:初始化点云的质量直接决定后续收敛速度和最终效果。如果SfM跑出来的点又少又飘,后面高斯会花大量时间"搬家",甚至卡在局部最优。
第二步是为每个点初始化高斯属性。位置直接沿用点云坐标,缩放初始值一般设成到最近邻点距离的量级,旋转初始为单位四元数,不透明度初始设一个偏低的固定值,颜色用球谐的零阶项(相当于一个基础RGB)初始化。这些初始值论文里给了推荐范围,但实际调的时候要根据场景尺度微调。
第三步是可微光栅化与损失回传。把当前所有高斯投影到某个训练视角,按深度排序做alpha混合得到渲染图,和真实照片算L1损失加一个D-SSIM结构相似度损失,然后反向传播更新所有高斯参数。这一步是3DGS工程上最核心的部分,它自己写了一套基于tile的光栅化器,把屏幕切成16×16的小块,每块只处理投影到该块的高斯,极大提升了并行效率。
第四步是自适应密度控制。这是3DGS区别于普通"可微点云"的灵魂操作。训练过程中会周期性检查每个高斯:如果它的梯度很大(说明这个区域重建得不好、需要更多细节),就把它克隆或分裂成更小的高斯;如果某个高斯的不透明度长期低于阈值(说明它是多余的),就把它剪枝掉。正是这个机制让高斯数量能自适应地从初始的几万个增长到几百万个,把细节区域填满、把空旷区域清干净。
2.3 球谐系数:让颜色随视角"活"起来
很多人第一次看3DGS的代码会困惑:为什么每个高斯的颜色不是一个RGB,而是一串球谐系数?这其实是它处理视角相关外观的关键。现实中的物体不是漫反射的,金属、塑料、布料在不同角度看颜色和亮度都不一样。如果只给一个固定RGB,渲染出来的场景会像塑料玩具一样死板。
球谐函数是一组定义在球面上的基函数,用它们的线性组合可以表达任意方向上的颜色分布。3DGS里通常用3阶球谐,对应16个系数(每通道),也就是每个高斯存48个颜色相关数值。渲染时根据当前视角方向代入球谐基,算出这个方向上的颜色。阶数越高,能表达的高频视角变化越丰富,但存储和计算也越贵。实际项目里如果场景主要是漫反射材质,降到1阶甚至0阶能省不少显存,效果损失有限——这是论文不会强调、但工程上很实用的取舍。
3. 训练环节的实操细节:参数、显存与收敛判断
3.1 关键参数的实际含义与调参方向
论文里给了一堆超参数,但真正影响成败的就那么几个。我把它们和实际调参经验整理成表,方便对照。
| 参数 | 作用 | 调大后的效果 | 实际建议 |
|---|---|---|---|
| 位置学习率 | 控制高斯移动速度 | 收敛快但易震荡 | 场景大时适当调大,细节场景保持默认 |
| 缩放学习率 | 控制高斯大小变化 | 高斯膨胀快,可能糊 | 默认偏保守,别急着调大 |
| 不透明度学习率 | 控制高斯显隐 | 收敛快但易过拟合噪声 | 噪声多的数据要调小 |
| 致密化间隔 | 多久做一次克隆分裂 | 间隔小则高斯增长快 | 显存紧张时调大间隔 |
| 致密化阈值 | 梯度多大才分裂 | 阈值低则高斯爆炸 | 默认值对多数场景够用 |
| 不透明度剪枝阈值 | 多低才删除 | 阈值高则删得狠 | 别设太高,否则细节被误删 |
我踩过最典型的一个坑是:为了让重建更精细,把致密化阈值调低,结果高斯数量从两百万暴涨到八百万,显存直接爆掉,训练中断。后来才明白,高斯数量不是越多越好,超过一定密度后边际收益急剧下降,反而拖慢渲染。现在我的习惯是先按默认参数跑一遍看基线,再针对具体问题微调单一参数,一次只动一个。
3.2 显存瓶颈的真实来源与缓解手段
3DGS训练吃显存是出了名的。一个中等规模场景,训练峰值显存轻松上到十几GB。显存主要花在三块:高斯参数本身(几百万个高斯,每个几十个浮点数)、光栅化时的中间缓冲、以及反向传播保存的激活值。
缓解手段有几个,按性价比排序。最有效的是控制致密化上限,在代码里设一个最大高斯数量,到顶就停止克隆分裂,这能直接把显存封顶。其次是降低球谐阶数,从3阶降到1阶能省掉大量颜色系数存储。再就是减小训练图像分辨率,前期用低分辨率快速收敛,后期再切高分辨率精修。最后才是考虑换更大显存的卡——但很多时候调参比堆硬件更管用。
提示:如果你的场景是室内小物体,几万到几十万个高斯就够了;如果是室外大场景,才需要往百万级走。别一上来就追求高数量,先看质量瓶颈到底在哪。
3.3 怎么判断训练是不是"收敛好了"
3DGS没有像传统深度学习那样清晰的收敛曲线,判断质量更多靠视觉检查加指标辅助。我通常看三个信号:训练损失(L1加D-SSIM)是否进入平台期、渲染图和原图的差异是否集中在特定区域、以及高斯数量是否稳定下来。
如果损失还在缓慢下降但视觉上已经看不出改善,多半是过拟合了训练视角,换几个没参与训练的视角一看就露馅。如果某些区域始终模糊,可能是初始化点云在那里太稀疏,或者致密化没覆盖到。这时候与其干等,不如检查SfM点云在那片区域的密度,必要时手动补点或调整致密化策略。实测下来,训练轮数不是越多越好,多数场景一两万轮就够,再训下去只是让高斯在原地微调,收益极小。
4. 渲染管线与实时性:快在哪里,代价是什么
4.1 基于tile的并行光栅化
3DGS渲染快,核心在于它把"泼溅"这件事做成了高度并行的光栅化。流程是这样的:先把所有高斯按视锥剔除掉屏幕外的,然后投影到图像平面,接着把屏幕切成16×16的tile,统计每个tile被哪些高斯覆盖,按深度排序,最后每个tile独立做alpha混合。因为tile之间互不干扰,可以大规模并行,GPU利用率极高。
这套管线和传统三角形光栅化思路类似,但处理的是高斯而不是三角形。高斯投影到屏幕后是一个有方向的椭圆,需要计算它的协方差矩阵在屏幕空间的投影,这一步涉及雅可比矩阵变换,是数学上最绕的部分。不过对使用者来说,这些都被封装好了,你只需要知道:渲染速度和高斯数量、屏幕分辨率强相关,和场景复杂度关系不大。
4.2 实时渲染的真实帧率与影响因素
论文里宣称的"上百帧"是在特定条件下测的。实际项目里,帧率受几个因素影响:高斯总数、渲染分辨率、以及是否开启了排序优化。我实测过一个约两百万高斯的室内场景,在1080p下能跑到80到120帧,4K下掉到30帧左右。如果高斯数量上到五百万,1080p也就四五十帧。
这里有个反直觉的点:渲染速度对高斯数量的敏感度是非线性的。因为排序和混合的开销随覆盖同一像素的高斯数量增长,如果场景里高斯堆叠严重(比如透明物体、毛发),某些像素可能被几十个高斯覆盖,那一片区域就会成为瓶颈。解决办法是在训练后做一次高斯压缩,把贡献极小的高斯合并或删除,往往能砍掉三成数量而几乎不损失画质。
4.3 和NeRF渲染的体验差异
用过NeRF的人再上手3DGS,最大的感受是"终于能拖动了"。NeRF渲染一帧要沿光线采样上百个点、每个点过一次MLP,即便有各种加速技巧,交互式漫游依然吃力。3DGS把渲染变成了纯光栅化操作,没有神经网络推理,延迟低了一个数量级。
但代价也存在。3DGS的显式表示意味着存储占用大,一个场景动辄几百MB到几GB,而NeRF的MLP权重可能只有几十MB。另外3DGS对训练视角的覆盖比较敏感,视角稀疏的区域容易出现"飞絮"状伪影,而NeRF的隐式平滑性在一定程度上能掩盖这种问题。所以选型时要看场景:要实时交互、能接受大存储,选3DGS;要极致压缩、对速度不敏感,NeRF系仍有优势。
5. 那些论文不会告诉你的坑:从环境到数据的实战教训
5.1 环境配置里最容易翻车的地方
3DGS的官方实现依赖CUDA扩展,编译环节是新手第一道坎。最常见的问题是CUDA版本、PyTorch版本、编译器版本三者不匹配,报错信息还特别晦涩,往往是一堆模板错误。我的经验是:先确定显卡驱动支持的CUDA上限,再选一个和它匹配的PyTorch预编译版本,最后确保系统里的gcc版本在支持范围内。三者对齐后,编译基本一次过。
另一个坑是子模块没拉全。3DGS仓库带了几个第三方子模块,如果克隆时没加递归参数,编译会缺头文件。这个错误提示通常指向某个找不到的.h文件,看到这类报错先检查子模块。
5.2 数据采集的隐性要求
论文假设输入是"标定好的多视角照片",但没说清楚采集时的注意事项。我踩过的坑包括:照片曝光不一致导致重建出现色块、运动模糊导致SfM位姿估计漂移、以及拍摄时覆盖角度不够导致背面全是伪影。
实操建议是:拍摄时锁定曝光和白平衡,尽量用高快门避免模糊,围绕物体走两到三圈、每圈不同高度,保证各个角度都有覆盖。如果物体表面有强反光或透明材质,SfM会很难受,这时候要么喷显影剂,要么接受这部分重建质量下降。数据质量的上限决定了重建质量的上限,再好的算法也救不了烂数据。
5.3 伪影与"飞絮"的排查思路
训练完发现场景里飘着一堆半透明的絮状物,是3DGS最常见的视觉问题。排查链路我总结成三步:先看这些絮状物出现在哪些视角,如果只在训练没覆盖的角度出现,那是视角稀疏问题,补拍即可;如果训练视角里也有,检查那片区域的高斯不透明度分布,多半是致密化过度产生了大量低不透明度高斯;最后看初始化点云,如果SfM在那片区域产生了离群点,高斯会围绕离群点生长。
对应的修复手段:对视角稀疏问题补数据;对致密化过度,调高剪枝阈值或限制最大高斯数;对离群点,在SfM阶段做一次统计滤波,把远离主体的点删掉再喂给3DGS。这套流程我走过好几遍,基本能解决八成以上的伪影问题。
6. 3DGS适合谁用:场景选型与能力边界
6.1 它擅长和不擅长的场景
3DGS在静态、刚体、有充分多视角覆盖的场景里表现最好,比如文物数字化、房间漫游、产品展示。它对光照的处理是"烘焙"进球谐系数里的,所以如果场景光照固定,效果很好;但如果需要重新打光、改变光照条件,就得额外做重光照处理,这不是原生3DGS能直接搞定的。
它不擅长的场景也很明确:动态物体(人、流体)、镜面反射强烈的物体、以及视角覆盖严重不足的场景。动态场景需要引入时间维度,属于4D扩展的范畴,复杂度和数据要求都上一个台阶。镜面反射因为违反漫反射假设,球谐表达会失真。这些边界在选型阶段就要想清楚,别等训练完才发现方向错了。
6.2 和传统摄影测量、网格重建的取舍
如果最终产物是要导入游戏引擎或CAD软件做编辑,那网格仍然是更好的选择,因为网格有明确的拓扑、方便做碰撞和物理。3DGS产出的是高斯集合,虽然可以转成网格(有相关工具),但转换过程会损失质量,且拓扑不一定干净。
如果目标是快速可视化、实时漫游、或者做新视角合成,3DGS的优势就体现出来了。它的训练和渲染速度、以及照片级的真实感,在可视化场景里几乎是无敌的。我的判断标准很简单:需要"编辑几何"就用网格,需要"看和漫游"就用3DGS。
6.3 后续可以延伸的方向
跑通基础流程之后,有几个方向值得深入。一是压缩与加速,把高斯数量压下来、把渲染再提速,这对移动端部署很关键。二是大场景分块,单场景高斯数量有上限,超大场景需要分块训练再拼接。三是结合语义,给高斯附加语义标签,做可交互的场景理解。四是动态扩展,把时间维度加进去处理动态场景。
这些方向目前都有活跃的研究和开源实现,但工程成熟度参差不齐。我的建议是先把静态场景跑扎实,理解清楚每个环节的取舍,再去碰这些进阶方向,否则很容易在复杂管线里迷失。
最后分享一个我自己的习惯:每次跑新场景,先用低分辨率、少轮数快速跑一个"预览版",确认数据没问题、流程能通,再上全参数正式训练。这个习惯帮我省下了大量因为数据问题白跑的时间——毕竟3DGS训练虽然快,但也不是几秒钟的事,先验证再投入,永远比闷头跑完再返工划算。