【技术解析】3D高斯泼溅:从稀疏点云到实时辐射场渲染的演进之路
1. 从稀疏点到3D高斯场景表示的革命大家好我是老张在图形学和三维重建领域摸爬滚打了十几年。今天想和大家聊聊一个让我眼前一亮的“新”技术——3D高斯泼溅。说它新其实它2023年才被正式提出但它的思想却像一把钥匙巧妙地打开了实时高质量三维重建和渲染的大门。咱们今天不聊那些复杂的数学公式就说说它到底是怎么一回事以及为什么它能做到又快又好。你可能听说过NeRF就是那个能用几张照片生成一个逼真三维场景的神奇技术。NeRF确实厉害画质没得说但它有个致命缺点慢。训练慢渲染也慢想实时交互基本没戏。而3D高斯泼溅恰恰就是冲着解决这个“慢”字来的。它的目标很明确在保持NeRF级别画质的同时实现实时的渲染速度。这听起来有点像天方夜谭但它真的做到了。那么它凭什么能做到呢核心在于它用一种非常聪明的方式来表示三维场景。传统的NeRF用一个巨大的神经网络去“记住”空间中每一点的颜色和密度查询起来自然费劲。而3D高斯泼溅则反其道而行之它用一堆“小泡泡”来填充场景。这些小泡泡就是3D高斯。你可以把它想象成无数个飘在空中的、半透明的、形状各异的彩色气球。每个气球有自己的位置、大小、胖瘦协方差、透明度α和颜色用球谐系数表示。渲染一张新视角的图片就是把这些气球按照一定的规则“拍扁”到你的屏幕上。这个起点非常关键。它从哪里得到这些初始的“气球”呢答案是运动恢复结构。简单说就是给你一堆同一个场景不同角度拍的照片SfM算法能像福尔摩斯一样通过分析照片中特征点的移动反推出相机的拍摄位置并生成一个稀疏的三维点云。这个点云就像建筑的钢筋骨架虽然点不多但勾勒出了场景的大致轮廓。3D高斯泼溅的第一步就是把这些稀疏的点每一个都变成一个初始的3D高斯“气球”。这样一来我们不是从零开始而是站在了一个相当不错的起点上。这种表示方法的妙处在于它的显式和可微。显式意味着每个“气球”的属性位置、形状、颜色都是白纸黑字摆在那里的参数我们可以直接去调整它们。可微意味着整个从“气球”到最终渲染图片的过程每一步的数学关系都是清晰的我们可以计算“如果最终图片和真实照片有差距该怎样微调每个气球的参数来缩小这个差距”。这为后续高效的优化奠定了基础。1.1 为什么是“高斯”它比“点”强在哪你可能会问直接用SfM生成的那些点云来渲染不行吗就像很多传统的点云渲染器那样。这里就体现出“高斯”的智慧了。一个纯粹的点在屏幕上就是一个像素如果点不够密渲染出来的画面就会满是空洞像筛子一样。而一个3D高斯分布在三维空间里是一个平滑的“能量球”当它被投影到二维屏幕上时会变成一个平滑的、有渐变透明度的椭圆斑点。这个特性带来了两个巨大的好处。第一是填充能力。一个高斯“气球”可以覆盖屏幕上一片区域而不是一个孤零零的像素。这意味着我们不需要极其密集的点也能渲染出连续、无空洞的图像。第二是可优化性。高斯的形状由协方差矩阵控制是可以学习的。对于一块平坦的墙面算法可以让高斯在墙面方向上“摊成一张大饼”而在垂直墙面的方向上“压得很薄”。对于一根细长的树枝它则可以让高斯沿着树枝方向“拉成一根面条”。这种自适应形状的能力让场景表示变得极其高效和紧凑避免了在空白区域浪费资源。我最初看到这个思路时感觉就像是用一堆“智能橡皮泥”去捏出一个场景。每块橡皮泥高斯都可以被随意拉伸、旋转、压扁并赋予不同的颜色和透明度。通过优化这些橡皮泥会自己找到最合适的位置和形状最终严丝合缝地拼出整个场景。这比用固定大小的乐高积木体素或者一个死板的函数大MLP要灵活和高效得多。2. 交替优化与自适应密度控制让场景“生长”出来有了初始的一堆“气球”接下来就是最关键的步骤让它们变得“正确”。这个过程论文里称之为“优化”我更喜欢把它叫做场景的“生长”过程。它不是一蹴而就的而是一个动态的、迭代的、充满智慧的过程。优化的目标很简单让渲染出来的图片和咱们手里那些真实的训练照片尽可能一模一样。怎么衡量像不像呢这里用了一个组合损失函数一部分是逐像素比较颜色差异的L1损失另一部分是衡量图像结构相似性的D-SSIM损失。这个组合拳既能保证颜色准确又能保住画面的整体结构和纹理避免优化出一堆虽然颜色对但看起来糊成一团的“气球”。优化过程是交替进行的主要干两件事调参数和控密度。第一件事调参数。这就是标准的梯度下降。每次迭代我们随机选一个训练视角用当前所有“气球”的参数渲染一张图片然后计算它和真实照片的差距损失。接着这个损失会沿着渲染管线反向传播告诉每一个“气球”“你的位置应该往左挪一点”“你的形状应该更扁一些”“你的颜色应该更红一点”“你的透明度应该再降低些”。然后这些“气球”就根据这些指示微调自己的属性。球谐系数负责让颜色随着视角变化而变化比如一个玻璃球从不同角度看反光是不一样的这个就由球谐系数来刻画。第二件事控密度。这是3D高斯泼溅的神来之笔也是它区别于很多静态优化方法的核心。光调参数是不够的如果一开始“气球”数量不够或者分布不合理有些细节永远也重建不出来。所以算法会定期检查哪些地方还需要更多的“气球”哪些地方的“气球”又多余了。这里有两个关键的触发机制克隆和分裂。你可以理解为“气球”的繁殖方式。算法会监控每个“气球”在优化过程中的“位置梯度”。如果一个“气球”所在区域的几何结构非常复杂比如物体的边缘、纹理丰富的区域但它的体积又太大导致它“力不从心”优化时它的位置参数就会收到很大的调整指令梯度大。这时候算法就判定这个区域“欠重建”了需要更精细的表示。于是它就把这个大气球分裂成两个小气球让它们去更好地覆盖这个复杂区域。相反如果一个“气球”覆盖的区域其实很简单比如一面纯色墙的内部但它却长得很大这显然是一种浪费。不过在优化中这种气球的透明度α会逐渐变得非常低趋近于完全透明。算法会定期清理这些“透明气球”直接把它们移除掉节省计算资源。通过这种“优化-评估-增删”的循环场景的表示就像生物一样生长、演化。从一开始稀疏的骨架点云逐渐“生长”出肌肉和皮肤细节越来越丰富。最终一个复杂的场景可能只需要100万到500万个这样的“智能气球”就能完美表达这个数据量对于现代GPU来说处理起来游刃有余。3. 基于瓦片的光栅化实时渲染的魔法引擎参数调好了“气球”的数量和分布也合适了接下来就是最后一步也是实现实时承诺的关键——渲染。如何把这几百万个三维空间的“气球”快速地变成你屏幕上的一张二维图片这就是渲染器要干的事。传统的NeRF类方法采用体渲染需要沿着每条视线发射光线然后在光线上密密麻麻地采样几百个点去查询神经网络的输出最后累加颜色。这个过程计算量巨大是导致其速度慢的根本原因。而3D高斯泼溅的渲染器借鉴了传统图形学中光栅化的思想但做了革命性的适配。它的核心是一个基于瓦片的光栅化器。想象一下你的屏幕被划分成一个个小格子比如16x16像素的瓦片。渲染不是一条条光线去追而是反过来让每个“气球”自己去“认领”它会影响哪些瓦片。具体过程是这样的首先根据相机参数把每个3D高斯“气球”投影到二维屏幕上变成一个2D的椭圆高斯斑点这就是“泼溅”一词的由来。然后快速判断这个椭圆覆盖了屏幕上的哪些瓦片。接着对于每一个瓦片收集所有覆盖了它的椭圆斑点并按照它们深度离相机的远近进行排序。为什么排序这么重要因为我们要模拟真实的光线穿透效果。离相机近的不透明物体会挡住后面的物体。这里使用的混合方式是α混合。从远到近每个椭圆斑点像一层半透明的玻璃纸按顺序叠加到像素上。当前面斑点的不透明度累积到接近1时后面的斑点就对最终颜色几乎没有贡献了可以提前停止计算。这个过程完全在GPU上并行执行每个瓦片、每个像素的计算都是独立的效率极高。我实测过这个渲染流程它的速度优势是碾压级的。对于百万级高斯的场景在消费级显卡上达到上百甚至数百FPS的实时渲染帧率是轻而易举的。这意味着你不仅可以瞬间看到渲染结果还能实时旋转、缩放场景交互体验和玩一款三维游戏几乎没有区别。这背后基于瓦片的前后排序和α混合是关键它完美地将“气球”模型的优势离散、可排序发挥了出来避开了体渲染连续采样的性能陷阱。3.1 快速反向传播训练加速的秘诀一个优秀的渲染器不仅要“快”还要“可微”这样才能支持前面提到的优化过程。3D高斯泼溅的光栅化器在实现高速渲染的同时也精心设计了快速的反向传播通道。在传统的深度学习中反向传播求梯度是个标准操作。但在这个定制化的光栅化流程里我们需要知道最终像素颜色对每一个“气球”参数位置、形状、颜色、透明度的梯度。由于渲染过程包含了排序和混合直接求导并不简单。论文的巧妙之处在于它利用了α混合的数学性质。在混合过程中算法会跟踪每个像素点上前面所有“气球”的累计不透明度。当进行反向传播时这个累计不透明度信息可以被复用来高效地计算出每个“气球”对最终颜色的贡献权重从而得到准确的梯度。这个过程同样被高度并行化确保了训练速度也能跟上。这就形成了一个完美的闭环一个可微的、高速的渲染器既能用于最终展示又能用于前向推理和反向梯度计算驱动整个优化过程。训练时间和Instant NGP这类加速方法相当但渲染质量却达到了Mip-NeRF 360这种“慢工出细活”的顶尖水准。这种“鱼与熊掌兼得”的特性正是3D高斯泼溅最吸引人的地方。4. 实战拆解从数据到实时渲染的全流程说了这么多原理咱们来串一下整个工作流看看如果你手头有一组照片如何一步步得到这个可实时浏览的3D场景。这个过程其实非常清晰像一条流水线。第一步准备输入数据。你需要一组从不同角度拍摄的同一静态场景的照片。同时你需要知道每张照片对应的相机参数位置和朝向。这些参数可以通过运动恢复结构工具自动计算得到比如COLMAP。COLMAP在计算相机参数的同时会输出一个副产品——我们反复提到的稀疏点云。这个点云就是我们的“种子”。第二步初始化3D高斯。将SfM得到的每一个稀疏三维点都初始化为一个3D高斯。初始的协方差形状可以设为一个各向同性球形的小球透明度设一个中间值颜色球谐系数可以先粗略地从对应像素颜色初始化。至此你的场景就从一组照片变成了一群有初步位置和颜色的“气球”。第三步迭代优化与密度控制。进入主循环。在每次迭代中随机选择一个训练视角。前向渲染使用当前的3D高斯集合通过基于瓦片的光栅化器渲染出该视角的图片。计算损失比较渲染图与真实照片计算L1和D-SSIM组合损失。反向传播损失反向传播计算每个高斯参数位置、协方差、α、球谐系数的梯度。参数更新使用优化器如Adam根据梯度更新所有高斯的参数。定期密度控制每迭代一定次数比如100次执行自适应密度控制。检查所有高斯对位置梯度大的大高斯进行分裂对透明度α低于阈值的高斯进行移除。必要时在“欠重建”区域通过克隆来增加高斯密度。这个过程会持续数万次迭代直到损失收敛渲染质量达到满意程度。在3090这样的显卡上对于一个中等复杂度的场景这个过程通常在30分钟到1小时内就能完成。第四步实时渲染与导出。训练完成后你就得到了一组优化好的3D高斯参数。这些参数可以保存为一个紧凑的文件。要查看或使用时只需加载这个文件并运行那个高效的光栅化渲染器。你可以自由地切换视角渲染器会实时地将这些高斯“泼溅”到屏幕上形成流畅的动态画面。你也可以将这个渲染器集成到自己的游戏引擎或三维查看器中提供沉浸式的浏览体验。4.1 参数解析与调优心得在实际操作中有几个参数对结果影响很大这里分享一些我的经验。首先是初始高斯尺度不宜设置过大否则初始渲染一片模糊优化容易陷入局部最优也不宜过小否则覆盖能力太弱。通常可以设为与邻近点云平均距离相关的一个值。其次是密度控制的间隔和阈值。触发分裂的梯度阈值和触发剔除的透明度α阈值需要平衡。阈值设得太敏感会导致高斯数量爆炸式增长显存撑不住设得太迟钝细节又重建不出来。我一般会从论文推荐的默认值开始然后根据场景复杂度微调。对于纹理特别丰富的场景可以适当降低分裂阈值让算法更积极地增加细节。最后是学习率。不同参数的学习率应该不同。位置、颜色球谐系数的学习率可以设得高一些让它们快速调整。而协方差形状和透明度α的学习率通常要设得低一些因为它们的变化更细微太激进会导致不稳定。很多开源实现已经提供了分组的优化器设置直接使用通常就能得到不错的效果。踩过几次坑之后我发现对于大多数场景遵循论文的默认参数就能取得很好的效果。3D高斯泼溅的鲁棒性相当不错这降低了它的使用门槛。真正需要你关注的反而是输入数据的质量照片的清晰度、覆盖的视角是否充分、SfM重建的相机位姿是否准确。这些才是决定最终效果上限的关键。