
开头先交代清楚背景。我最近用一套自己搭的流程把一整栋老厂房从拍摄到渲染完整建成了可交互的3D场景整个过程跑完我只想说一句3D Gaussian Splatting3DGS这个方向确实让场景重建的门槛降了一大截但距离随便拍拍就能出大片还有相当距离。这套流程我给起了个名字叫SceneSplat主要解决的是从单物体到完整场景这一层问题单件物品、小摆件大家已经玩得很溜了可一旦把拍摄对象换成整间屋子、整栋建筑甚至一小片街区相机标定、内存占用、训练稳定性这些事全都会变成新的坎。SceneSplat就是围绕这个过程搭建的一套可复现方案适合正在接触3DGS、打算做场景级重建但又不想在细节上反复折腾的朋友参考。我默认你至少知道3DGS的基本概念把场景表示成海量3D高斯椭球每个椭球带位置、旋转、尺度、不透明度和颜色信息可微光栅化后直接用照片梯度优化。SceneSplat本质上没发明新算法它是对现有GS相关工具做了工程化组合用COLMAP做稀疏重建用分块训练解决大场景显存问题再用统一的坐标框架合并所有块最后导出成可交互预览的格式。下面我把整个链路的关键节点拆开说每一个地方都有实际测试的经验和教训。1. 为什么单物体流程跑到场景级就崩SceneSplat要解决的三个真实瓶颈我最早接触3DGS时也是从网上找现成的开源实现对着一个椅子、一个盆栽训练效果确实惊艳几分钟就能出白模和纹理都像样的结果。但当我第一次尝试把宿舍一整间屋子拍进去时问题马上就冒出来了而且不是某一个小问题是三个问题同时压过来。第一个瓶颈是相机位姿估计的稳定性。COLMAP在小物体上几乎无脑跑因为物体周围特征点多、视差大、相机轨迹自然发散。换成完整场景后输入照片变多、场景自遮挡严重纯靠SIFT特征做匹配会出现大量错误匹配对尤其是白墙、玻璃、反光地板这类弱纹理区域。我第一版跑出来的稀疏点云直接分成好几片训练出来的高斯都是悬浮的根本不成形状。第二个瓶颈是显存和训练时长。COLMAP输出的稀疏点云可能包含数十万甚至上百万个点3DGS在初始化时会在每个点附近撒高斯。单块RTX 4090 24GB显存到了场景中段训练就爆显存只能把图片分辨率压到很低结果细节全丢。而且迭代次数不变的情况下场景复杂度翻倍收敛时间也几乎翻倍实验节奏极其难受。第三个瓶颈是视觉一致性的破坏。大场景无法一次拍完通常要分段采集光照条件会变化同一面墙在不同照片里亮度不一样。3DGS优化的是像素级误差倾向于记住每张照片的样子而不是理解墙面本来的颜色结果就是同一个平面出现大块明暗斑驳。SceneSplat的设计目标就是针对这三个瓶颈分别给方案在数据端增加预处理和筛选策略在训练端引入分块与合并机制在成像端加入光照归一化。这个思路听起来不复杂但每步落地都有讲究下面按流程细讲。2. 拍摄与数据筛选决定重建质量的不是相机而是你的采集习惯很多人在3DGS上翻车第一个原因其实是拍摄质量。相机好坏反而没那么重要我用过手机主摄也用过入门微单只要满足几个条件重建结果差距不大。但拍摄习惯不对什么相机都救不回来。2.1 拍摄路径设计环绕、多层高度、重叠率基本原则是让相机围绕场景中心做洋葱式运动。对于一间屋子你要先在最外层绕一整圈然后收进去半米换个高度再绕一圈直到把每个角落都覆盖到。关键在于相邻两帧之间的重叠率至少要80%这不是我拍脑袋定的COLMAP做特征匹配时重叠太少会导致匹配对数断崖式下降相机位姿图就会分裂。我常用的做法是直接录视频而不是拍照片。视频帧率高回来后按间隔抽帧可以灵活控制密度。但视频有一个大坑运动模糊。手机录视频默认会开防抖和自动曝光帧与帧之间模糊程度差异很大这些模糊帧一旦进入数据集中就会成为训练时的噪声来源。我的建议是录视频时尽量保持移动缓慢后期抽帧时先人工扫一遍把明显模糊的帧剔除。2.2 COLMAP参数和稀疏重建的注意点COLMAP是SceneSplat里的第一阶段工具负责从多视角图片恢复相机参数和稀疏点云。命令行不复杂但有几个参数值得特别注意colmap feature_extractor --database_path db.db --image_path images --ImageReader.single_camera 1 --SiftExtraction.max_num_features 16384 colmap exhaustive_matcher --database_path db.db --SiftMatching.guided_matching 1 colmap mapper --database_path db.db --image_path images --output_path sparse --Mapper.ba_global_function_tolerance1e-6--ImageReader.single_camera 1这个参数建议场景采集如果用的是同一台相机同一镜头一定加上。它告诉COLMAP所有照片共享同一套内参能显著提升位姿估计稳定性。max_num_features我设到16384因为场景级照片特征点本来就多默认8192在弱纹理区域会不够用。稀疏重建完成后一定要检查两个东西。第一是注册率如果某几张照片始终无法注册进模型十有八九是拍摄位置太偏或者模糊太严重直接删掉不要硬留。第二是点云完整性如果你发现某个区域在点云里是空洞哪怕后面训练也不会凭空生成这部分的正确结构要么补拍要么干脆放弃这个区域。SceneSplat在这个阶段会生成一份质量报告统计每张图的匹配点数方便快速定位问题照片。2.3 曝光一致性与光照归一化处理场景级拍摄最常见的画质问题就是曝光不一致。室外拍摄时云遮住太阳室内拍摄时头顶灯光的频闪都会造成相邻帧亮度差异。3DGS对光度一致性极其敏感因为它在优化时直接比较像素值亮度都不同梯度方向就会乱。我的处理办法是在进入训练前做一次全局光照归一化选一张参考图算出其平均亮度然后对其他所有图做线性亮度映射把平均亮度对齐到参考图。这个操作不会损伤纹理信息但能明显减少训练时的闪烁和色彩斑驳。SceneSplat内置了一个Python脚本本质上就是把每张图的RGB通道乘个系数再加个偏移系数和偏移由两张图的亮度直方图匹配得到。3. 分块策略把大场景拆成能放进显存的小块再合回同一个坐标系如果场景不大单块训练没问题。但一旦COLMAP算出来的点云有几百万个点单块训练几乎必然显存溢出或收敛不稳。SceneSplat的方式是分块训练这步是整个流程里最核心也最需要仔细对待的部分。3.1 为什么必须分块而不是简单降采样图片一种绕开显存限制的常见做法是把图片分辨率降下来比如从4000px降到1600px。这确实能强行跑起来但代价是高频细节全部丢失重建结果远看还行稍微放大就糊成一片。另一种做法是减少迭代次数但欠拟合的后果是高斯数量不够几何结构残缺。分块训练的核心思路是把整个场景在空间上切成若干子区域每个子区域只使用覆盖它的那部分相机图片单独训练最后把所有子区域的高斯合并成一个完整场景。这样做有三个好处每块只需加载局部图片和局部点云显存占用可控每块的优化目标更集中几何收敛更快整场景可以并行在多块GPU上训练缩短总耗时。3.2 空间划分与重叠带设计SceneSplat的划分方法是基于COLMAP稀疏点云做的。具体来说把点云投影到地面平面选取点云法向主方向为Z轴然后对XY坐标做网格切分。网格大小取决于场景复杂度和单块目标高斯数我通常让每块初始高斯数控制在30万到50万之间。这里有个关键细节块与块之间必须有重叠带。如果两个块在边界处严格切开合并后就会出现一条明显的接缝因为两侧训练时对边界区域的高斯分布估计是不同的。SceneSplat的做法是让相邻块在边界处有约15%到20%的空间重叠重叠区域内的图片同时参与两个块的训练。合并时重叠区域的高斯按距离权重做一次简单混合效果远好于硬切。每块需要哪些相机图片判断标准是相机光心到块中心的距离加上视角覆盖判断。这个筛选过程看着简单实际一旦做成全自动很容易出现某块图片数量不够导致重建失败。我的做法是设置一个最低图片数阈值比如每块至少30张有效视角如果不足就自动扩大块边界直到满足要求。3.3 并行训练与统一坐标的关键分块训练有个天然的风险每块是独立优化相机参数和场景结构的虽然COLMAP已经把所有图的相机位姿统一在了同一个坐标系里但3DGS在优化过程中允许相机参数微调这个微调幅度可能导致各块的坐标基准产生微小偏移。SceneSplat的做法是在训练时把相机参数冻结到COLMAP解出的值不做进一步BA优化。这样每块都锁定在同一套坐标系里合并时就不需要做点云配准或坐标变换直接拼接即可。代价是该区域图片的标定若不够准确冻结参数会限制训练上限。权衡下来场景级重建里合并一致性比单块极致精度更重要所以我默认冻结。如果你拍的是一个几百张图的室内场景COLMAP的标定已经足够准冻结完全没问题。4. 训练参数与质量调优从能出图到细节扛打分块完成后剩下的就是训练本身。3DGS的开源实现已经很多核心训练逻辑大同小异但有几个参数在场景级任务里需要格外关注。4.1 密度控制参数的理解与调节3DGS训练过程中每一定间隔步数会检查一次高斯的梯度和透明度决定是否要克隆或分裂这就是密度控制。默认实现里densify_grad_threshold设为0.0002意思是在这一步中梯度超过该阈值的位置会被克隆或分裂。这一套在小物体上是合理的到了场景级由于视野范围大、各块内容复杂度差异悬殊固定阈值容易导致纹理丰富区域过度增长而平坦区域又长不出足够的高斯。SceneSplat在分块训练时会对每块统计初始点云的局部分布然后按区域调整密度阈值点云密集区域用更高的阈值防止爆炸稀疏区域用更低阈值促进增长。这个做法不需要改网络结构只是给密度控制器加了一个位置自适应的mask实测下来每块的高斯总数能稳定控制在设定范围内不会出现某个块训练到一半直接爆显存的情况。4.2 学习率与迭代策略对场景感知的影响场景级重建还有一个感知层面的问题人在观察一个场景时先能认出这是个房间有桌子有椅子再看细节纹理。神经渲染的训练收敛顺序其实也遵循类似规律低频率几何和颜色先收敛高频纹理后收敛。因此迭代策略应该配合这个时序。我的经验是前2000步用一个稍高的位置学习率让高斯大概分布到正确位置2000步之后把位置学习率降一个量级主要精力放在优化球谐系数和透明度上。具体的衰减策略不同实现略有差异但大原则一致。我踩过一次坑一开始就用很低的学习率从头训练结果几何结构半天定不下来白墙区域反复出现和消失的半透明高斯云。后续把前2000步的学习率提上来收敛速度快了很多。4.3 天空、背景和无限远元素的处理场景级拍摄几乎绕不开背景。室内有窗外景色室外有天空这些东西的特性是深度接近无限远用3D高斯表示时它们往往会被不恰当地安置在前景附近形成一团漂浮物。SceneSplat的解决方案有两个最简单的是把天空像素在训练时mask掉只对前景有效区域计算loss。另一种是把天空单独建模为一个远景球壳不参与前景的高斯优化。实测下来mask方式最简单且效果稳定配合COLMAP点云中本来就缺少远景点的特性只要在损失函数里对天空区域权重乘以0即可。关键在于天空区域的判定不能简单按颜色阈值因为白色天空和白色墙壁会混淆。我用的办法是把训练图中大部分帧的顶部区域比如上15%像素默认视为天空候选再结合语义分割模型辅助判断。5. 合并渲染与交互预览从离线重建到浏览器里随便逛分块训练完成的产物是一批.ply格式的高斯文件每块几十万到上百万个高斯。真正让它变得有用的是合并成单一场景并做实时渲染预览。5.1 高斯合并的工程细节合并本身在数学上就是拼接但由于每个块的高斯数量不同密度差异在块边界处可能肉眼可见。我在第3节提到重叠带内做距离权重混合这里补充具体做法对重叠区域内的每个高斯找到它属于哪些块然后按它到每块质心的距离倒数做归一化权重对颜色向量和不透明度做加权平均。位置、旋转、尺度这些几何参数取重叠前数值就行不需要混合。合并后的文件可以非常大单场景200万个高斯每个高斯存位置、旋转四元数、尺度三轴、不透明度、球谐系数若干文件体积很容易超过300MB。这对本地预览没问题放到Web端就太笨重了。所以SceneSplat在合并后还会跑一次剪枝去掉对渲染贡献极小的低不透明度高斯以及尺度小到对最终像素无影响的碎片高斯。剪枝后的场景通常能缩减一半体积画质损失肉眼几乎不可察觉。5.2 实时渲染预览与交互3DGS为什么能实时渲染是因为它走的是光栅化路线而非光线追踪。渲染器把每个高斯椭球当成一个billboard式图元先按深度排序再逐像素做alpha混合。这一步在GPU上就是大规模的绘制调用所以能在较高帧率下跑出照片级效果。本地预览我习惯用一个基于Three.js的Gaussian Splatting渲染插件它把训练好的.ply转成.splat格式在Web浏览器里就能加载。操作手感类似游戏引擎的飞行相机鼠标拖拽旋转视角滚轮缩放。对展示和交付来说直接发给对方一个网页链接比自己录屏发视频直观得多。5.3 场景级自动LOD性能与画质的平衡场景级模型比单物体大得多Web端如果要流畅漫游必须要做LODLevel of Detail。简单说就是根据相机距离动态决定渲染哪些层次的高斯。远景不需要那么多细节可以直接用剪枝后的低精度版本近景再切换完整版。SceneSplat的LOD分三层远距离用10万高斯的稀疏版本中距离用50万高斯的中等版本近距离用全量版本。这个切换需要一个关键参数每层高斯的占比。我调过一轮远中近三层大概按1比3比6分配比较合适切换半径根据场景物理尺寸自动计算一般让切换点在相机移动速度下看起来不突兀就行。做LOD还有一个额外好处显存不够的机器也能勉强跑起来虽然近景精度受限但至少不会崩溃。6. 我踩过的坑和最终效果复盘最后这部分说点实际的坑每个都是付了训练时长才换来的经验。第一个坑是COLMAP匹配阶段没有开启guided_matching导致大量误匹配进入稀疏重建最终点云里出现一层明显的云状漂浮物。这些漂浮物在3DGS训练初期会被当作真实结构去拟合最后变成半透明雾状层非常难去除。开了guided_matching之后利用极几何约束过滤掉了一大批错误匹配问题基本消失。第二个坑是曝光归一化做得太激进。有次我把所有图的亮度强行拉到同一水平结果弱光区域被拉伸后出现明显噪点训练出来墙面上全是细微的颗粒纹理。后来改成只对全局低频亮度做对齐保留局部对比度不变才平衡了闪烁和细节的关系。第三个坑是分块重叠带的比例。一开始为了省训练时间我把重叠带压到5%结果合并后边界处出现了一条明显的接缝后来按15%重跑了一遍边界块接缝才消失。选参数时不能只盯着节省算力重建质量才是第一优先。这三个坑的共同教训是3DGS看起来是端到端的神经网络训练实际上它很依赖输入数据的质量和前序环节的稳定性。前端一点小小的误差会在训练阶段被放大成明显的视觉瑕疵。最终跑通的SceneSplat流程用在老厂房这个案例上总共1200张照片分成18块并行训练单块在4090上约15分钟合并后约240万个高斯Web端预览稳定60帧。整个从拍摄到交付的时间大约一个晚上比传统NeRF方案的几天时间短了太多。这个结果谈不上完美但已经足够用于室内展厅、建筑记录这类实际场景。如果你手头也有类似的需求建议直接从分块并行和曝光归一化这两个点入手优化它们对最终效果的影响最直接操作上也不需要改算法核心。等把这两步吃透了再回头调密度阈值和LOD参数你会觉得整个流程顺手很多。