FlashGS, Efficient 3D Gaussian Splatting for Large-scale and High-resolution Rendering

Zhangwenniu 于 2025-02-27 发布

核心内容讲解

这篇论文介绍了一个名为 FlashGS 的技术,它是一种用于高效渲染大规模和高分辨率3D场景的方法。简单来说,它通过优化3D高斯绘制(3D Gaussian Splatting)的过程,让计算机能够更快地生成高质量的3D图像。

以下是对论文内容的简化解读:

1. 研究背景

2. FlashGS的核心思想

3. FlashGS的主要贡献

4. 实验结果

5. 总结

FlashGS通过优化算法和硬件加速,显著提升了3D高斯绘制的性能,使其能够高效处理大规模和高分辨率的3D场景。这项技术对于需要实时渲染的应用(如虚拟现实、实时导航等)具有重要意义。

重点技术讲解

warp

在GPU编程和并行计算中,Warp 是一个重要的概念,特别是在NVIDIA CUDA架构中。理解Warp的工作原理对于优化GPU程序的性能至关重要。以下是对Warp的详细解释:

1. 什么是Warp?

在NVIDIA GPU中,Warp 是一组同时执行相同指令的线程(Thread)。具体来说,一个Warp通常包含32个线程,这些线程在同一个流多处理器(Streaming Multiprocessor,简称SM)上并行执行。

2. Warp的工作原理

当GPU执行一个Kernel时,它会将线程分组成Warp。每个Warp中的线程会同时执行相同的指令,但处理不同的数据。这种执行方式称为单指令多数据(SIMD)

3. 为什么Warp重要?

理解Warp的工作原理对于优化GPU程序的性能至关重要,原因如下:

4. Warp在FlashGS中的应用

在FlashGS的实现中,Warp被用于优化计算和内存访问效率。具体来说:

5. 总结

Warp是GPU编程中的一个核心概念,它通过将线程分组成32个一组的集合来提高并行计算效率。理解Warp的工作原理,并通过优化代码逻辑减少线程发散,可以显著提高GPU程序的性能。在FlashGS中,通过自适应调度策略、减少线程发散和粗粒度工作分配,Warp被有效地利用来提升渲染性能。

详细讲解

这篇论文的核心内容是关于 FlashGS,一种用于高效渲染大规模和高分辨率3D场景的优化方法。它基于3D高斯绘制(3D Gaussian Splatting,简称3DGS)技术,并通过一系列算法和硬件优化,显著提升了渲染性能。以下是对论文的详细介绍:

1. 研究背景与动机

1.1 3D渲染的挑战

1.2 现有方法的局限性

1.3 FlashGS的动机

2. FlashGS的核心设计与优化

2.1 精确交集算法(Precise Intersection Algorithm)

2.2 优化的渲染流程(Refined and Balanced Workflow)

2.3 硬件加速与优化

3. FlashGS的实现细节

3.1 预处理阶段(Preprocessing)

3.2 渲染阶段(Rendering)

3.3 内存管理与指令优化

4. 实验结果与分析

4.1 实验设置

4.2 性能对比

4.3 运行时间分析

4.4 敏感性分析

4.5 图像质量

4.6 内存使用

5. 结论

FlashGS通过优化算法设计和硬件加速,显著提升了3D高斯绘制的渲染性能。它不仅解决了3DGS在大规模和高分辨率场景下的性能瓶颈,还实现了高效的内存管理和高质量的图像渲染。FlashGS在多种场景和分辨率下均表现出色,证明了其在实时3D渲染领域的强大潜力。

6. 总结与展望

原文翻译

FlashGS:高效的大规模和高分辨率3D高斯绘制

作者:Guofeng Feng†, Siyan Chen†, Rong Fu*, Zimu Liao, Yi Wang, Tao Liu, Zhilin Pei, Hengjie Li, Xingcheng Zhang, Bo Dai

单位:上海人工智能实验室

摘要

本文介绍了FlashGS,这是一个开源的CUDA Python库,旨在通过算法和内核级别的优化,实现高效的可微分3D高斯绘制光栅化。FlashGS基于对渲染过程的全面分析开发,以提升计算效率并推动该技术的广泛应用。论文提出了一系列优化策略,包括消除冗余、高效的流水线设计、精细的控制和调度机制以及内存访问优化,这些策略被精心集成以提升光栅化过程的性能。我们在多种合成和真实世界的大规模场景中对FlashGS的性能进行了广泛的评估,涵盖了各种图像分辨率。实验结果表明,FlashGS在移动消费级GPU上平均加速了4倍,并且减少了内存消耗。这些结果突出了FlashGS卓越的性能和资源优化能力,使其成为3D渲染领域的一个强大工具。

关键词:3D高斯绘制、CUDA优化、大规模实时渲染

1. 引言

神经辐射场(NeRF)因其能够生成逼真的图像渲染而变得流行,但由于需要对每条光线采样大量点以进行像素渲染,从而限制了其实时能力。最近,一种名为3D高斯绘制(3DGS)的新表示方法作为一种可行的替代方案脱颖而出。该技术已被证明能够实现实时渲染速度,为室内环境的实时探索开辟了新的可能性,对沉浸式自由视角导航、虚拟房产参观和交互式虚拟现实游戏体验等实际应用具有深远意义。

尽管3DGS具有优势,但在城市级场景或由消费级GPS接收器记录的高质量场景中,实时渲染大规模或高分辨率区域仍然受到有限的计算和内存资源的限制。这是因为随着图像规模和分辨率的增加,高斯分布的数量和每个高斯的尺寸也会增加。现有工作广泛采用压缩或剪枝方法来避免存储或计算过多的高斯分布,但这些方法并未带来显著的性能提升。最近的一项工作,GScore,尝试分析原始3DGS算法,但主要通过设计一种新的特定领域的硬件来解决移动GPU的限制。为了克服现有工作的不足,我们对单个消费级GPU上的3DGS渲染过程进行了分析,识别出渲染性能瓶颈,例如高斯与瓦片在预处理阶段的粗略交集测试,以及体积渲染中的大量冗余计算。我们进一步进行了详细的性能分析,提取出阻碍性能的关键因素:1)在预处理阶段生成的一些高斯-瓦片对在实际渲染过程中并未使用。2)在整个光栅化算法的不同阶段存在严重的计算和内存访问瓶颈。基于我们的深入观察,我们提出了FlashGS,一种具有高效系统级优化的新型3DGS渲染算法。具体来说,我们首先引入了一种新的光栅化工作流设计,构建了更高效的渲染流水线。为了实现这一新的渲染过程,我们首先进行了几何和代数简化,以减轻高计算成本。我们提出了新的运行时调度策略,以利用硬件资源,包括线程之间的负载平衡和发散消除,以及两阶段预取执行流水线,以重叠计算和内存访问操作。我们还优化了内存管理,以更好地利用GPU内存层次结构。此外,我们通过直接应用汇编级优化进一步提升了性能。与3DGS、GScore和gsplat相比,FlashGS实现了高达4倍的速度提升和49%的内存减少,同时保持了相同的高质量指标峰值信噪比(PSNR)。据我们所知,这是首次深入分析、精心重新设计并高效实现基于单个消费级GPU的3DGS体积渲染的工作。我们使得在低开销的情况下实现大规模和高分辨率场景的高效渲染成为可能。我们的主要贡献如下:

2 背景与相关工作

我们简要概述了新视图合成及其现有方法,特别是辐射场方法。然后,我们介绍了当前最先进的技术——3D高斯绘制(3DGS)。最后,我们简要报告了3DGS的一些最新改进。

2.1 新视图合成

新视图合成是3D重建中的一个重要方法。它基于一组从某些视角(源姿态)拍摄的输入图像,生成从新视角(目标姿态)的新图像。在重建之前,提出了一些表示方法来构建3D场景。传统方法使用点云、体素或网格来描述空间结构。然而,这些传统的显式和离散表示可能导致信息丢失,从而导致场景表示不完整。

神经辐射场(NeRF)是一种广泛采用的有吸引力的表示方法,它利用深度学习技术。它通过多层感知器(MLPs)隐式地建模3D场景,这种连续函数能够实现对场景的精细表示。一些后续工作专注于通过优化模型来提升合成图像的质量。Mip-NeRF是一种多尺度模型,它通过投射锥体并编码视锥体的位置和大小来解决NeRF的混叠问题。一些方法利用点云的深度监督,使模型更快收敛。NeRF中MLP所需的大量计算导致了训练和渲染过程的缓慢,这激发了大量关于高效训练和推理的工作。一种常见技术是将预训练结果存储到简化的数据结构中,例如稀疏体素网格和八叉树。其他工作提出或整合了新的特征与MLPs,以实现更快的训练或推理,例如体素网格中的特征。一个显著的工作极大地加速了NeRF,Instant-NGP引入了一种多分辨率哈希编码网格,并同时与优化的MLPs一起训练。

2.2 3D高斯绘制

3D高斯绘制(3DGS)通过一组高斯椭球体来建模场景,允许通过将这些高斯分布光栅化为高质量图像来实现快速渲染。图2展示了3DGS的机制。光栅化器可以粗略地分为3个步骤:预处理(preprocessCUDA和DuplicateWithKeys)、排序和渲染。原始实现中的预处理步骤包含两个主要操作。首先,经过视锥体裁剪后的$P$个3D高斯分布被投影到图像平面上,形成具有均值$\mu’ \in \mathbb{R}^2$和各向异性协方差$\Sigma’ \in \mathbb{R}^{2\times2}$的2D高斯分布。然后,预处理管道为每个对应的2D高斯分配一个轴对齐边界框(AABB)。3DGS使用AABB来获取与椭圆相交的瓦片,并且对于由某个高斯分配的AABB,DuplicateWithKeys操作遍历所有与AABB重叠的瓦片,并将每个瓦片与高斯组织为键值对。键由瓦片索引和高斯的深度组成,值是高斯索引。排序过程根据深度对$N$个键值对进行排序,用于从前到后渲染,使用Nvidia CUB库中的RadixSort。最终的渲染过程获得像素颜色的时间复杂度为$O(N)$(工作量按瓦片划分),并且每个像素颜色根据不透明度$\alpha$和透射率进行计算。

2.3 3DGS的改进

尽管3DGS在3D重建中取得了显著进展,但仍有许多改进空间。一些工作通过增强3DGS的采样过程进一步提升了图像质量(逼真度),以消除模糊和伪影。Yan等人分析了3DGS渲染中的混叠效应,并引入了一种多尺度3D高斯表示,用于渲染场景的不同细节层次,通过改进采样频率实现。Zhang等人提出了FreGS,这是一个利用正则化执行从粗到细的高质量高斯密集化的新型框架,以缓解过度构造问题。这些方面不是本文的重点,读者可以在一些综述中找到更多细节。在一些高分辨率、大规模场景中,3DGS可以生成数百万甚至更多的(巨大)高斯分布,对内存单元造成巨大压力。这激发了一些专注于优化内存使用的工作,可以分为两大类:1)利用模型压缩中广泛使用的技术,如剪枝和编码。Scaffold-GS利用底层场景几何结构,采用锚点生长和剪枝策略,有效减少冗余高斯分布,渲染时不会损失质量。EAGLES应用向量量化(VQ)对每个点的属性进行量化,以压缩3D高斯点云。LightGaussian进一步引入数据蒸馏,减少球谐系数的阶数,并提出了一种基于高斯全局重要性的VQ策略。2)引入多GPU并行化以避免单个GPU的内存限制。Grendel是一个可扩展(最多32个GPU)的分布式3DGS训练系统,将3DGS参数分布在多个GPU上,并使用稀疏通信和动态负载平衡机制。Chen等人提出了DoGaussian,它将场景分解为$K$个块,并应用交替方向乘子法(ADMM)分布式训练3DGS。然而,只有少数论文专注于原始3DGS算法的设计和实现,并尝试在计算过程中高效支持关键操作。这构成了我们工作的直接动机。gsplat是一个CUDA加速的可微分光栅化库,用于3DGS,实现了更快且更节省内存的渲染。Durvasula等人对基于光栅化的微分渲染(包括3DGS)进行了性能分析,并引入了一种使用warp级归约的新原语,以加速梯度计算中的昂贵原子操作。GScore是一种特定的硬件加速单元,用于在移动GPU上高效支持渲染流水线,并基于对基于高斯的渲染的分析进行了一些算法协同设计。

3 观察与动机

我们对3D高斯绘制的渲染过程进行了详细分析。首先,我们通过对运行时分解的分析,获得了渲染过程中的关键步骤。基于此,我们识别出几个显著的性能问题,这些问题是我们的优化设计的灵感来源。如图3所示,我们在A100和V100 GPU上使用代表性大规模数据集MatrixCity测试了3D高斯绘制。从上到下,它显示了渲染过程中关键操作的时间比例。渲染(renderCUDA)是主要的性能瓶颈,占总时间的约60%。高斯排序(SortPairs)和前面生成未排序键值列表(DuplicateWithKeys)的时间占了近20%。预处理时间在10%以内,但也不容忽视。鉴于这些步骤并非独立,我们对它们进行了全面研究。

3.1 观察1:过多的冗余计算阻碍了高效渲染

我们观察到光栅化流水线中存在几种计算冗余。图4显示了从MatrixCity数据集训练的场景中6帧的渲染过程中键值对分箱过程。分配的键值对数量远大于实际覆盖AABB或投影椭圆的瓦片数量。也就是说,预处理内核分配了未被投影椭圆覆盖的瓦片,创建了冗余的键值对,导致排序和渲染过程中的效率低下。图5中显示的三种冗余因素导致了这些现象。我们发现图5中的冗余I和II导致分配的AABB覆盖了比实际AABB更多的瓦片,如图4所示。冗余III导致AABB覆盖的瓦片数量与投影椭圆之间的差异。

3.2 观察2:不恰当的执行流水线降低了硬件利用率

我们发现3DGS的主要步骤之间存在计算和内存访问的不平衡,这可能会对相应的硬件单元造成过大压力,导致计算或内存瓶颈。预处理步骤(算法1中的preprocessCUDA)的内核具有线性复杂度$O(P)$,其中$P$是高斯分布的数量。每个高斯分布都与一个较大的球谐系数数组相关。这导致由于大量的读写操作(算法1的第17-22行)而对缓存带宽造成显著压力。在光栅化阶段,3DGS首先将高斯球投影到瓦片上以获得高斯-瓦片对(DuplicateWithKeys)。其渐进复杂度取决于高斯分布的数量和它们投影覆盖的瓦片数量$O(P + N)$。如算法2所示,在此步骤中,除了少数位运算外,所有操作都涉及对全局内存的读写。在渲染过程中,大量的像素级计算引入了计算瓶颈,而上述两个步骤主要是内存访问操作。这导致整个3DGS执行流程中计算和内存访问之间的不平衡,可能导致GPU硬件资源的利用率不足。

4 FlashGS设计

为了解决上述问题,我们分两个阶段优化高斯渲染过程。首先,我们设计了一个更精确的交集算法,以减轻后续与冗余高斯计算相关的计算瓶颈,并使用几何简化来避免昂贵的交集计算。此外,我们根据每个内核/微内核的特性重新组织和重构了计算过程,以防止工作负载不平衡。如图7所示,整个过程可以从FlashGS减少的误判瓦片键值对数量以及算法和运行时调度的优化中显著受益。

4.1 高效且精确的交集算法

如图8所示,精确交集算法可以减少后续计算中涉及的高斯分布数量,从而减轻计算和内存访问的强度/压力。这至关重要,因为对于两个关键瓶颈——排序和渲染,它们的工作量与要处理的高斯分布数量成正比。我们需要在执行精确交集计算之前确认由高斯分布投影的椭圆的有效范围。如第3.1节所述,对于3D图像重建,我们不仅要考虑一般高斯分布在有效范围上的影响,还要考虑不透明度的变化。3D高斯绘制应用三西格玛法则来获得有效椭圆:

\[r = 3\sqrt{\lambda} \quad (1)\]

其中,$\lambda$表示投影椭圆的半长轴长度,对应于协方差矩阵的特征值,$r$是从椭圆抽象出来的圆的半径。考虑到第3.1节中的统计数据,65%的高斯中心的不透明度相当低([0, 0.35],这个范围将在接下来的段落中解释),这促使我们将不透明度$\alpha$纳入以获得有效范围:

\[\alpha = \alpha_0 \times \exp\left(-\frac{r^2}{2\lambda}\right) \quad (2)\]

其中,$\alpha_0$是高斯的初始(中心)不透明度。当$\alpha$衰减到某个阈值$\tau$时,我们认为这是椭圆的边界,如公式(3)所述:

\[r = \sqrt{2\ln\left(\frac{\alpha_0}{\tau}\right)\lambda} \quad (3)\]

鉴于RGB通常有256个离散值,通常取$\tau = \frac{1}{255}$。当$\alpha_0 \leq 0.35$时,我们的方法优于三西格玛法则;反之,则继续使用$3\sigma$。

我们采用精确交集算法来消除无效高斯分布。考虑到椭圆可能是相当细长的,直接计算高斯投影椭圆与瓦片之间的交集成本较高,这可能会引入大量对无效高斯分布的不必要计算。我们通过两阶段过滤过程减少直接交集计算的开销:(1)使用边界框(注意:我们计算一个与椭圆紧密相切的矩形,而不是像原始算法中那样使用包围椭圆的圆的正方形)来设置粗略范围。(2)对于与边界框相交或包含在边界框内的瓦片,检查它们是否与椭圆相交。

对于每个瓦片的交集,我们使用几何等价变换来避免直接求解多个二次方程的成本,考虑到矩形瓦片不容易用简单方程表示。我们的关键观察是,椭圆与矩形之间的交集可以转化为确定椭圆在矩形每条边所在直线上的投影线段是否与矩形边的线段重叠的问题,如图9所示。如果它们相交,我们找到被椭圆包围的线段上的端点。然后,我们使用这些端点来确定该线段是否与矩形的对应边重叠。如算法3所示,我们首先将椭圆与矩形之间的交集分为两种情况。第一种情况很直接:如果椭圆的中心在矩形内,则必定存在重叠。对于更复杂的情况,即椭圆的中心在矩形外,我们求解椭圆与矩形每条边所在直线的交集。

4.2 精细且平衡的工作流

3DGS渲染的工作流是一个涉及多个步骤的复杂过程,如上所述。此外,不同的子组件可能具有不同的计算模式,这些子计算并非完全独立。因此,我们需要从更全面的角度进行系统分析和优化。我们的关键思想是尽可能平衡每个部分的计算成本,以避免潜在的瓶颈。我们实现这一想法的方法是将不同类型的运算在时间线上摊销,以防止某个事务突发并造成过大压力。我们从不同操作中提取计算瓶颈和内存访问瓶颈,并将它们交织在一起,形成更高效的执行工作流。

我们观察到,在执行渲染之前,有几个重要的操作,包括前面提到的精确交集、计算和记录一些与高斯分布相关的参数,以及为排序构建键值对。在基线实现中,这两个函数都是带宽受限的,涉及大量的内存读写操作。然而,我们观察到后续的渲染过程是计算受限的。这是由于基线算法中粗糙的高斯交集方法导致了大量误判(false positives)。因此,我们可以在预处理阶段进行更多的计算。这不仅有助于平衡带宽受限的部分,还能减少后续逐像素渲染的开销。这正是我们采用前面提到的精确交集算法的关键动机之一。在预处理阶段,我们检查高斯分布的可见性,然后计算并存储后续步骤所需的各种信息,例如深度、高斯半径和坐标。在原始算法中,每个处理与瓦片相交的高斯分布的线程都需要访问全局内存,以读取球谐系数并在预处理的最后写入多个高斯信息。这导致了显著的带宽竞争,使得工作流在全局内存访问期间因内核之间的隐式同步而停滞。我们将新的精确交集算法与DuplicateWithKeys()结合,精确生成有效的高斯键值对。如前所述,准确计算瓦片与投影椭圆之间的交集是复杂的,需要求解多个方程。因此,在此过程中的许多操作都是计算密集型的,使得内存带宽资源有剩余。因此,我们重新组织了这两个操作。通过在全局内存写入操作的延迟期间交错执行精确交集计算,我们确保了在整个执行工作流中,GPU的计算单元和内存带宽都能高效利用。我们的方法实现了较高的计算与内存访问比率。如图10所示,精确交集操作减少了冗余的高斯分布,从而减轻了排序阶段的内存访问开销以及渲染阶段的额外计算。在我们的新预处理阶段,我们可以通过内核融合重用一些中间结果,直接从交集过渡到键值对生成,从而减少内存读写操作的次数。尽管精确交集和合并操作的总计算量和内存访问操作可能增加,但我们为编译器和硬件提供了更多的调度机会,以实现更好的平衡并在更长的时间线上摊销。

5 FlashGS实现

在本节中,我们在第4节提出的FlashGS算法基础上进行了高效的实现,以充分利用算法的优势,同时避免复杂计算带来的额外开销。在我们的实现中,我们通过在计算、调度和内存管理方面应用优化,进一步增强了运行时的硬件利用率。

5.1 预处理

在新的预处理阶段(如算法4所示),我们针对每个瓦片与椭圆的精确交集算法引入了大量的复杂计算,这可能会显著阻碍将该算法应用于当前最先进的工作中。尽管我们在第4.2节中通过将精确交集与一些内存访问操作绑定来平衡计算与内存比率,但高效实现这一算法仍然至关重要。我们在两个关键方面对算法进行了优化:1)应用代数简化以减少单个瓦片-椭圆交集的开销;2)提出一种自适应调度策略,以平衡整个高斯投影椭圆的交集任务。

5.1.1 代数简化

我们使用代数等价变换来避免高成本操作(指令)。在第4.1节中的几何变换中,为了判断两条线段是否重叠,我们可以比较它们的端点坐标。假设两条线段分别为$[a_i, b_i]$和$[a_j, b_j]$,如果它们的交集非空,则满足公式(4):

\[\min(b_i, b_j) - \max(a_i, a_j) \geq 0 \quad (4)\]

具体来说,这等价于检查公式(5),即一条线段的右端点(end)在另一条线段的左端点(start)之后,且一条线段的左端点(start)在另一条线段的右端点(end)之前:

\[a_i \leq b_j \quad \text{且} \quad a_j \geq b_i \quad (5)\]

寻找相交线段的端点需要求解二次方程,这涉及到高成本的操作,如除法和开方。假设一个瓦片的边由线段$[a_i, b_i]$表示,相交线段$[a_j, b_j]$(如果存在)是直线(瓦片边所在的直线)和椭圆形成的二次方程的两个根。如公式(6)所示,其中$\Delta = B^2 - 4AC$,且$A, B, C$是二次方程$AX^2 + BX + C = 0$的系数:

\[a_i \leq \frac{-B + \sqrt{\Delta}}{2A} \quad \text{且} \quad b_i \geq \frac{-B - \sqrt{\Delta}}{2A} \quad (6)\]

不失一般性,假设$A > 0$,则该条件可以转换为公式(7),以避免高成本操作,如除法和开方,其中两个表达式$e_1 = 2Aa_i + B$和$e_2 = 2Ab_i + B$:

\[(e_1 \leq 0 \quad \text{或} \quad e_1^2 \leq \Delta) \quad \text{且} \quad (e_2 \geq 0 \quad \text{或} \quad e_2^2 \leq \Delta) \quad (7)\]

5.1.2 自适应尺寸感知调度

精确的每瓦片交集算法还意味着不同高斯分布的交集任务的工作量可能会显著不同,因为不同大小的椭圆覆盖的瓦片数量可能会有很大差异。在原始3DGS交集算法中,基于边界框的方法不存在这个问题,因为可以通过边界框的顶点直接定位覆盖区域,而忽略边界框的大小。如图11所示,对于不同大小的高斯分布,得到的边界框(即需要相交的瓦片数量)可能会有很大差异。我们根据高斯分布的大小自动选择处理方法,以实现高效的调度。为了确保每个线程的工作量平衡,我们使用自适应映射。当椭圆很小时,仅需要对一个瓦片进行计算时,我们继续使用当前线程进行该高斯分布的精确交集。当椭圆较大时,我们将工作量重新映射到一个线程组(在我们的实现中,我们选择了一个warp来平衡同步开销,考虑到在实践中,椭圆覆盖的瓦片数量很少超过32个)。因此,我们实现了对整个高斯投影椭圆的高效计算,它可以与不同数量的瓦片相交。应用了上述所有优化后,我们的新处理内核如算法4所示。

5.2 渲染

为了解决3DGS执行过程中硬件利用率低下的问题,我们从上到下实现了两级流水线优化。我们调整了应用程序的执行工作流和指令分发。

5.2.1 低延迟执行流水线

在渲染过程中,每个瓦片都会执行与其相关高斯分布的计算,例如计算透射率和颜色。这些计算可能会根据累积的不透明度$\alpha$饱和度提前终止。这些计算依赖于预先从全局内存中加载的预处理和排序后的高斯列表,需要分两步从全局内存中读取高斯信息:(1)根据当前瓦片信息获取高斯索引;(2)使用索引检索存储的具体信息,例如高斯中心的像素坐标、椭圆的二次形式和不透明度等。全局内存访问的高延迟可能会阻碍计算流水线的高效执行,尤其是上述两个全局内存访问步骤存在依赖关系。

我们的核心策略是在内存访问延迟期间尽可能多地发出指令,以缓解由数据依赖导致的停顿。具体来说,我们引入了两级预取策略,以最大化计算和内存访问之间的重叠,如图12所示。采用软件流水线方法,在第$i$步的计算中,我们首先从全局内存中获取第$i+2$步的高斯索引。然后,使用已经获取的索引,从全局内存中检索第$i+1$步的具体高斯信息。最后,使用在第$i-1$步获取的高斯信息执行第$i$步的渲染计算。我们在细粒度的重新排列中尽可能多地重叠独立指令,从而实现更高效的指令分发流水线。

5.2.2 线程发散控制

Warp内的线程发散会导致某些线程停滞,影响执行效率。核心思想是最小化条件分支或将条件检查移至更粗粒度的层次(更粗粒度),从而确保Warp内的线程尽可能执行相同的路径。在原始3DGS算法的渲染步骤中,每个线程在计算像素时都必须检查不透明度和透射率,以执行早期停止,并在写回之前确定像素是否有效。我们将不透明度检查移至预处理阶段,以实现有效的高斯计算。这种在更粗粒度的高斯级别上的预先过滤消除了冗余,取代了在渲染过程中每个像素的条件检查。

5.2.3 粗粒度工作分配

我们通过调整任务分配的粒度,使单个线程处理多个任务组,从而促进公共数据共享。这种较大的粒度主要基于以下两个考虑因素。首先,它旨在充分利用快速内存单元(例如寄存器),因此我们不能将缓冲区设置得过小(例如,仅缓冲少量数据用于单个项目)。其次,它还考虑了共享数据的问题。在渲染过程中,还存在一定数量的公共计算。我们以组为单位向编译器提供一组任务,以暴露更多的潜在共享机会。具体来说,我们暴露了公共子表达式消除(CSE)的机会,通过减少FLOPs进一步提高计算性能。

5.3 通用优化

除了上述对一些关键内核的高效并行算法实现外,我们还应用了几种通用技术来优化FlashGS计算中的内存和指令使用,以实现进一步的性能提升。

5.3.1 内存管理

3DGS涉及大量的内存操作,包括读取、写入和分配。我们需要精心设计以利用GPU的层次化内存和内存带宽。

5.3.2 汇编优化

我们还利用显式和隐式优化,以更好地利用GPU支持的高效指令。在高斯计算中,由于高斯分布的指数部分,直接调用可能会导致高开销。通过将指数表达式中的直接乘法转换为对数形式,我们首先执行加法,然后执行指数运算。这种方法的好处是它允许编译器使用融合乘加(FMA)指令,其开销与单个MUL或ADD指令相同。此外,在执行对数和指数运算时,我们明确指定底数为2,因为GPU的特殊函数单元(SFU)硬件指令针对底数2进行了优化,允许直接高效地调用fast ex2.approx.ftz.f32 PTX指令。

6 评估

6.1 实验设置

6.1.1 测试平台

我们在两个NVIDIA GPU平台上进行了实验。大多数测试在消费级RTX 3090(Ampere架构,24GB GDDR6X内存,CUDA计算能力8.6)上进行。我们使用A100(Ampere架构,80GB HBM2e,CUDA计算能力8.0)数据中心GPU来研究性能敏感性。我们使用GCC 10.5.0和CUDA 12.0的NVCC编译代码。我们还将FlashGS集成到Python中作为一个可调用模块,以增强其可用性。

6.1.2 数据集

我们在6个代表性数据集上全面测试了FlashGS和3DGS,这些数据集涵盖了11个不同分辨率的场景,每个场景包含数百到数千帧。其中,Truck和Train是Tanks & Temple中的室外场景;Playroom和DrJohnson是DeepBlending中的室内场景。3DGS声称在这些数据集上能够以1080p分辨率实现实时渲染。我们还使用了另外两个大规模和高分辨率的数据集,这些数据集超出了3DGS实现实时渲染的能力。MatrixCity是一个包含数千张1080p分辨率航拍图像的综合性和大规模高质量数据集。Rubble是Mill19中由无人机拍摄的高分辨率图像。我们在每个数据集上使用3DGS训练了30K次迭代,以获得用于渲染的高斯模型。

6.1.3 基线方法

我们将FlashGS与3DGS和GScore的最新优化进行了对比。由于GScore是作为硬件加速单元实现的,我们无法直接对其进行测试。他们声称GScore通过其交集和调度技术实现了1.86倍的速度提升。

6.2 总体性能

如表1所示,FlashGS的光栅化流水线在所有性能指标上均优于原始3DGS算法,覆盖了不同场景和分辨率的所有数据集。我们可以在RTX 3090上始终实现>100FPS的渲染速度,即使在高分辨率和大规模数据集(如Rubble)中,最低帧率也能达到107.3 FPS。这表明FlashGS即使在极其大规模和高分辨率的情况下也能实现实时渲染。FlashGS在Matrixcity数据集的4K分辨率下实现了高达30.53倍的速度提升,平均速度提升为12.18倍。我们在所有11个场景中平均实现了7.2倍的速度提升,而在7个大规模或高分辨率测试中,速度提升达到了8.6倍,是GScore结果的3.87倍。FlashGS在不同场景中的所有帧中实现了2.29倍到30.53倍的速度提升,全面证明了我们设计和优化的通用性。

6.3 运行时间分析

在本节中,我们比较并分析了FlashGS与3DGS的运行时间分解,以揭示我们出色性能的来源。图13展示了6个代表性帧的光栅化时间和分解情况,这些帧展示了最大或最小加速比。这表明我们在预处理、排序和渲染等所有阶段都实现了加速。在FlashGS中,这些阶段分别占总时间的平均19.6%、29.1%和47.6%,而在3DGS中,它们分别占13.2%、25.4%和59.6%。预处理和渲染阶段的优化已经在前面讨论过,而排序阶段的加速主要是由于应用了我们的精确交集算法后,需要排序的键值对数量减少。我们将在下面进行更详细的分析。

6.3.1 性能分析结果

我们进一步通过内存和计算单元的分析结果来证明我们在渲染和预处理阶段的优化,如图14所示。图中显示,我们在渲染阶段减少了指令的发出,缓解了计算受限的问题。这证明了我们精确交集算法和低延迟渲染优化的有效性。与3DGS相比,发出的总指令数量显著减少了1到2个数量级。对于预处理阶段的内存访问事务,我们也减少了43%-87%的全局内存访问。

图15进一步展示了渲染阶段发出指令数量显著减少的原因,这在光栅化中占主导地位。在基于瓦片的渲染中,发出的总指令数量是瓦片数量和每个瓦片的指令数量的乘积。因此,这一减少主要来自两个方面。如图15所示,由于我们的交集优化,渲染的键值对数量减少了68%-96%。每个瓦片的计算涉及的指令数量也减少了67%-71%,因为我们进一步在指令级别优化了renderCUDA内核。生成的键值对数量的减少也使排序过程受益,因为它显著减少了需要排序的列表大小。此外,存储这些键值对所需的内存量也减少了。

6.4 敏感性分析

在本节中,我们研究了FlashGS在不同GPU和场景下的性能敏感性。

6.4.1 A100上的性能

我们还在A100 GPU上进行了表1中展示的实验,结果同样展示在表1中。与RTX 3090相比,A100上的总光栅化时间平均慢了1.43倍。这主要是因为渲染步骤占主导地位,如图13所示,它严重依赖于FP32计算,而A100的FP32峰值性能仅为19.5TFLOPS,RTX 3090则为35.6TFLOPS。尽管排序等渲染前的操作主要是内存带宽受限的,且A100的带宽高于RTX 3090,但这些优势无法弥补渲染性能的下降。然而,我们在A100上对所有数据集的3DGS实现了显著的加速,在表1中的11个场景中,平均帧率为123.8-423.7 FPS,始终能够实现实时渲染。

6.4.2 不同场景下的性能

从表1中,我们观察到两个主要结果:

  1. 对于相同的数据集,4K分辨率下的加速比高于1080p。

  2. 在不同数据集中,大规模和高分辨率场景(如MatrixCity-4K)实现了最高的加速比。这主要是由于我们的精确交集计算,其中渲染中的计算负载与高斯分布的数量以及每个高斯分布与瓦片的交集数量成正比。在包含更多对象的大场景中,高斯分布的数量增加;对于高分辨率,高斯分布变大以渲染更精细的细节。在这种情况下,消除冗余的效果更为显著。如果场景非常小且简单,每个高斯分布可能非常小,仅与一个瓦片相交,这将导致我们的精确交集改进较低。

6.5 图像质量

图17展示了原MatrixCity大规模数据集在1080p分辨率下的最复杂帧,以比较输出质量。我们比较了FlashGS与3DGS之间的峰值信噪比(PSNR),这是计算机图形学中的标准指标(越高越好)。结果显示,FlashGS没有改变质量,保持了31.52的PSNR,同时加速了8.57倍。这是合理的,因为我们的精确交集算法仅减少了误判冗余。而且,我们没有在实现中应用剪枝或量化策略,因此没有精度损失。

6.6 图像质量

图17展示了MatrixCity大规模数据集的两个代表性帧。我们比较了FlashGS与3DGS之间的峰值信噪比(PSNR),结果表明FlashGS并未改变图像质量。这是合理的,因为我们的精确交集算法仅减少了误判冗余。此外,我们在实现中没有应用剪枝或量化策略,因此没有精度损失。

6.7 内存比较

FlashGS的内存分配少于3DGS和gsplat,最多可减少49.2%。表2比较了在NVIDIA A100 GPU上渲染第800帧之前和之后的不同模型的内存使用情况,包括gsplat(packed设置为TrueFalse)、原始3DGS和FlashGS。具体来说,当packed设置为False时,gsplat渲染后的最大内存分配为10.75 GB;而当packed设置为True时,最大内存分配减少到9.83 GB,显著降低了内存使用。这种减少是因为,当packed设置为True时,光栅化过程更加节省内存,将中间张量打包到稀疏张量布局中。这在每个相机只能看到场景一小部分的大场景中特别有益,大大减少了内存使用。然而,这也引入了一些运行时开销。

相比之下,原始3DGS使用了最多的内存,渲染后的内存分配为13.45 GB,这在处理复杂场景时是一个重要的考虑因素。FlashGS通过静态分配方法确保内存使用的连贯性和可预测性,最大内存分配为6.83 GB,低于其他模型。这表明其在处理大规模场景时的卓越效率和性能。静态分配方法有效地避免了内存分配和释放过程中的波动,从而实现更稳定的内存管理。

每种方法生成的键值对(kv pairs)数量是决定渲染时内存使用的关键因素。原始3DGS模型生成了56,148,670个键值对,导致其较高的内存消耗。相比之下,gsplat在packed设置为FalseTrue时分别生成了56,996,302和56,998,101个键值对,略有增加。这证明了其内存减少来自于压缩技术。而FlashGS仅生成了3,436,142个键值对,显著减少了内存使用。

7 结论

我们提出了FlashGS,它能够实现实时渲染大规模和高分辨率场景。在本文中,我们通过精心设计的算法和高度优化的实现,解决了原始3DGS中存在的冗余和不恰当的计算与内存比率问题,从而实现了一个快速的渲染流水线。FlashGS在GPU上的渲染性能显著优于现有方法,同时实现了高效的内存管理,同时保持了高质量的图像渲染。

如果还有其他需要补充或修改的地方,请随时告诉我!

算法1 preprocessCUDA

输入:$P$,相机位姿(cam_pose),$s$,$R$,$\sigma$,球谐函数(shs),视图矩阵(viewmatrix),投影矩阵(projm atrix),$o$

输出:半径(radii),$x_{I}’$,深度(depths),$\Sigma$,$c$,圆锥不透明度(conic_opacity),被触及的图块(tiles_touched)

1: 如果 $idx \geq P$,则

2:     返回

3: $p_view \leftarrow$ 在视锥体中(in_frustum())

4: 如果 非$p_view$,则

5:     返回

6: $p_proj \leftarrow$ 投影(project())

7: $\Sigma \leftarrow$ 计算三维协方差(computeCov3D())

8: $\Sigma’ \leftarrow$ 计算二维协方差(computeCov2D())

9: $\lambda_1, \lambda_2 \leftarrow$ 特征值(eigenvalues($\Sigma’$))

10: $radii \leftarrow \lceil 3 \times \sqrt{\max(\lambda_1, \lambda_2)}\rceil$

11: $x_{I}’ \leftarrow$ 归一化设备坐标到像素坐标(ndc2Pix())

12: $rect_min, rect_max \leftarrow$ 获取矩形(getRect())

13: 如果 $(rect_max.x - rect_min.x) \times (rect_max.y - rect_min.y) == 0$,则

14:     返回

15: $c[idx] \leftarrow$ 从球谐函数计算颜色(computeColorFromSH())

16: $depths[idx] \leftarrow p_view.z$

17: $radii[idx] \leftarrow radii$

18: $x_{I}’[idx] \leftarrow$ 点图像(point_image)

19: $conic_opacity[idx] \leftarrow$ (圆锥,不透明度[$idx$])((conic, opacities[$idx$]))

20: $tiles_touched[idx] \leftarrow (rect_max.y - rect_min.y) \times (rect_max.x - rect_min.x)$

算法2 duplicateWithKeys

输入:$P$,$x_{I}’$,深度(depths),偏移量(offsets,每个高斯的图块计数前缀和),半径(radii)

输出:未排序的高斯键(gaussian_keys_unsorted,用于存储键值对的数组),未排序的高斯值(gaussian_values_unsorted,用于存储键值对的数组)

1: $idx \leftarrow$ 线程排名(thread_rank())

2: 如果 $idx \geq P$,则

3:     返回

4: 如果 $radii[idx] > 0$,则

5:     $off \leftarrow (idx == 0)? 0 : offsets[idx - 1]$

6:     $rect_min, rect_max \leftarrow$ 获取矩形(getRect())

7:     对于 $rect_min.y$ 到 $rect_max.y$ 的 $y$,执行

8:         对于 $rect_min.x$ 到 $rect_max.x$ 的 $x$,执行

9:             $gaussian_keys_unsorted[off] \leftarrow$ 键(key)

10:             $gaussian_values_unsorted[off] \leftarrow idx$

11:             $off \leftarrow off + 1$

算法3 判断矩形图块是否需要渲染的算法

输入:矩形$R$,椭圆$E$

输出:一个布尔值,指示该图块是否需要渲染

1: 如果椭圆$E$的中心点位于矩形$R$内,则

2:     返回 true

3: 否则

4:     对于矩形$R$的每条边$e$,执行

5:         $l \leftarrow$ 包含线段$e$的直线

6:         如果直线$l$与椭圆$E$的交集不为空,且交点位于边$e$上,则

7:             返回 true

8: 返回 false

算法4 preprocessCUDA

输入:$o$,$\sigma$,球谐函数(shs),视图矩阵(viewmatrix),投影矩阵(projm atrix),相机原点($o_{cam}$),宽度($W$),高度($H$),水平视场角正切(tan_foux),垂直视场角正切(tan_fovy),水平焦距(focal_x),垂直焦距(focal_y)

输出:$x_{I}’$,偏移量(offset),深度(depths),$\Sigma$,$c$,圆锥不透明度(conic_opacity),未排序的高斯键(gaussian_keys_unsorted),未排序的高斯值(gaussian_values_unsorted)

1: 如果 $idx_vec < P$,则

2:     初始化 $p_orig$,$p_view$

3:     如果 $p_view.z > 0.2$ 且 不透明度(opacity)> $1.0/255$,则

4:         $p_proj \leftarrow$ 投影(project())

5:         $\Sigma’ \leftarrow$ 计算二维协方差(computeCov2D())

6:         $x_{I}’ \leftarrow$ 归一化设备坐标到像素坐标(ndc2Pix())

7:         $rect_min, rect_max \leftarrow$ 获取矩形(getRect())

8:         $point_valid \leftarrow$ (边界框不为零)

9:     如果 $point_valid$ 且 边界框为单个图块,则

10:         如果图块与椭圆相交或包含椭圆中心,则

11:             $key \leftarrow$ 计算图块键(compute tile key)

12:             $offset \leftarrow$ 原子加(atomicAdd(curr_offset, 1))

13:             $gaussian_keys_unsorted[offset] \leftarrow key$

14:             $gaussian_values_unsorted[offset] \leftarrow idx_vec$

15:     对于 warp 中的每个线程,执行

16:         如果 是多个图块(multi_tiles),则

17:             $parameters \leftarrow$ 打乱值(shuffle values)

18:             对于从 $rect_min.y$ 到 $rect_max.y$ 的 $y$,执行

19:                 对于从 $rect_min.x$ 到 $rect_max.x$ 的 $x$,执行

20:                     如果 有效(valid),则

21:                         $key \leftarrow$ 计算图块键(compute tile key)

22:                         存储键和值(store key and value)

23:     如果 任何线程有有效点,则

24:         $c[idx_vec] \leftarrow$ 从球谐函数计算颜色(computeColorFromSH())

25:         存储深度(depth),点的 xy 坐标(point_xy),圆锥不透明度(conic_opacity)

评论