光栅化是计算机图形学中,将连续的三维几何场景转换为离散的屏幕像素的图形处理管线;高斯泼溅中为了加速光栅化的渲染过程,基于 torch 的 CUDA 接口提供了 GPU 并行的编程实现。
GPU 并行编程基础
为了理清这部分的逻辑,有必要先介绍最基本的 GPU 并行编程概念。GPU 上并行执行的指令称为核函数;CUDA 在逻辑访问上的层级结构分为 grid、block 和 thread 三级。grid 下分为多个 block,一个 block 中包含多个 thread,一个 block 内可以使用共享内存(共享内存是 GPU 中比全局内存访问速度快得多的一种存储结构,但大小十分有限)。block size 表示一个 block 内含有的 thread 数,block dim 表示 block 内线 thread 的组织结构。组织结构可以这样理解,把一个 thread 想象成一个边长为 1 的小立方体,block dim = (4, 3, 2)表示立方体排列成长宽高分别为 4、3、2 的长方体,如下图所示。组织结构支持一维到三维,未指定的维度默认设置为 1。类似地,gird size 表示一个 grid 内含有的 block 数,grid dim 表示 grid 内 block 的组织结构。核函数以整个 gird 为范围、以 thread 为单位执行,即每个线程都会运行一次核函数,多个线程同时运行核函数即可实现并行数据处理。
并行渲染策略
高斯泼溅中将图像按分辨率向上取整划分为多个 tile,每个 tile 包含 16*16=256 个像素。整个并行逻辑分为 preprocess 和 render 两部分,preprocess 部分每个 thread 负责一个高斯椭球,block size 设置为(256, 1, 1),grid size 根据高斯椭球总数进行设置;render 部分每个 thread 负责一个像素,且每个 block 负责一个 tile,block size 设为(16, 16, 1),与 tile 中的像素对应,grid size 根据划分出的 tile 数进行设置。
例如,图像分辨率为 1920*1080,高斯总数为 30000,⌈x⌉为向上取整的符号,上述过程计算如下:
preprocess部分的block size为:(256,1,1)preprocess部分的grid size为:⌈25630000⌉=118⇒(118,1,1)render部分的block size为:(16,16,1)划分的tile数:⌈161920⌉×⌈161080⌉=120×68=8160render部分的grid size为:(120,68,1)
在 preprocess 部分,以某一个 thread 为例,按照前面介绍的高斯椭球转化过程,计算得到像素坐标系下的高斯椭圆,即 xy 位置和距离相机的深度、2D 协方差矩阵、颜色和不透明度。接下来需要得到这个高斯椭圆所涉及的 tile 索引范围和数量,为此对二维协方差进行特征值分解,取最大特征值的三倍(即高斯函数中的3σ)这个范围的 AABB(Axis Aligned Bounding Box,轴对齐包围盒,是边与坐标轴平行的外切矩形,可由一组点中的最小坐标和最大坐标确定)作为该高斯椭圆的影响区域,记录该区域涉及的 tile 索引范围且可以据此得出高斯椭圆影响的 tile 数量。
将所有高斯椭圆涉及的 tile 数求和,就是实际要处理的渲染任务总数。在高斯泼溅中,这些椭圆按照到相机的距离从近到远的顺序渲染到图像上,完成全部高斯椭球的处理后的结构类似于下图。高斯椭圆和 tile 之间是多对多的关系,即一个高斯椭圆可能影响多个 tile,一个 tile 也可能受多个高斯椭圆影响。
为了处理这种多对多的关系并实现这种按深度顺序进行渲染的逻辑,构造tile id | depth <--> gaussian id的键值对,将 tile 索引置于键的高位,深度置于键的低位,高斯椭圆的索引作为值构成一条 entry。这样的 entry 假定一个高斯椭圆影响一个 tile,根据高斯椭圆影响 tile 的数量对其进行复制,如某个高斯椭圆影响 5 个 tile,则将其复制 5 份并分别构造 entry。注:这里的复制只是一种理解方式,指的是重复使用这个高斯索引来与它影响的不同 tile 分别构成 entry,而不是在内存上将高斯参数进行复制。对这个 entry 表进行键值对的排序,就可以将高斯索引按 tile 索引排列好,同一个 tile 内按深度排列好,举例如下图。
注:高斯泼溅在构建这样的 entry 时其实还会根据高斯椭圆对 tile 的贡献值进行排序,小于一定阈值则认为影响可以忽略,并不会构造这个 entry。具体来说是,对某一个椭圆影响的某一个 tile,按照椭圆梯度的方法计算这个 tile 内高斯函数的最大值,大于阈值才认为这个椭圆对这个 tile 有贡献,才会构建这个 entry。
排序完成之后,就获得了每个 tile 上需要处理的高斯椭圆的索引,接下来进入每个 thread 负责一个像素、每个 block 负责一个 tile 的 render 部分。在一个 tile 内,为了利用 GPU 的共享内存机制,对所有的高斯椭圆进行分轮次处理,每一轮处理前都先将数据读入共享内存,高斯泼溅中将每一轮处理的高斯数量设为 256。在一个轮次内,遍历这 256 个高斯椭圆,计算它们对这个 tile 内所有像素的影响。对某一个高斯椭圆来说,一个线程处理一个像素,整个 block 内的 thread 并行即可处理完某一个高斯椭圆对整个 tile 的影响。由此经过轮次的循环和高斯椭圆的循环,即可处理完整个 tile 上所有高斯椭圆对这个 tile 中所有像素的影响。
α-blending 图像合成
一个轮次中的 256 个高斯椭圆对这个 tile 中某一个像素的颜色累积过程称为α−blending。每个椭圆参与合成的实际α,由像素中心和高斯函数中心之间的马氏距离进行椭圆平均加权。

椭圆加权平均(EWA)是一种高质量的采样和抗锯齿算法,适合处理椭圆形状分布的元素,核心思想是基于二维协方差的马氏距离。马氏距离是一种距离指标,可以应对高维线性分布的数据中各维度间非独立同分布的问题,修正了欧式距离中各维度尺度不一致且相关的问题,本质上是对 PCA 中的主成分进行标准化,将变量按主成分旋转(主成分就是特征向量的方向),使各维度之间相互独立,然后再标准化(缩放特征值)使它们同分布。马氏距离其实就是标准化之后的欧式距离。
DM(x)DM(x,y)二维高斯函数:G(x)其中 x=(x−μ)⊤Σ−1(x−μ)=(x−y)⊤Σ−1(x−y)=2π∣Σ∣1exp(−21(x−μ)⊤Σ−1(x−μ)),=[x1x2],μ=[μ1μ2],Σ=[σ11σ21σ12σ22]
设高斯椭圆中心坐标为(centerx,centery),像素中心坐标为(pixelx,pixely),EWA 中权重的计算公式如下,其中Σ为高斯椭圆的协方差矩阵。
[dxdy]power=[centerxcentery]−[pixelxpixely]=−21(DM(center,pixel))2=−21([dxdy]Σ−1[dxdy])
像素颜色累积公式如下,其中C为像素颜色,c为高斯椭圆颜色;α为高斯椭圆对像素的透明度贡献,即参与合成的实际α;T为像素在接受当前高斯椭圆渲染之前的透射率,即像素的剩余透明度。事实上,颜色值的更新,就是在当前处理的高斯椭圆颜色值,和截止当前的颜色累积值之间进行插值的过程。
颜色更新:剩余透明度更新:Cfinal=ci⋅αi+caccum⋅(1−αi)Tfinal=Taccum⋅(1−αi)即C=i=1∑NTiαici即Ti=j=1∏i−1(1−αj)
颜色积累的迭代更新实现见如下伪代码,值得注意的是:第一,透明度贡献α大于阈值的高斯椭圆才会参与对当前像素的渲染,这就是所谓的像素离散,将连续的高斯椭圆离散渲染到屏幕像素上。第二,剩余透明度T小于阈值时,表示对于当前像素而言,前面的高斯椭圆已经将光吸收完了,后面的高斯椭圆对该像素的颜色不再有贡献。

完成所有轮次后,tile 中像素的剩余透明度T参差不齐,再用统一的背景色 background color 填充每个像素的剩余透明度,就得到了所有像素最终的颜色,也就得到了完整的渲染图像。
Cresult=Cfinal+T×background color
参考资料
3DGS CUDA 代码解读
3DGS CUDA Kernel
评论
评论正在加载…