0%

3DGS

为什么选择 3D 高斯椭球,而不是其他基元

因为高斯核有很好的数学性质:

  • 仿射变换后高斯核仍然闭合(结果依然是高斯)
  • 3D 降维到 2D 后(沿某一个轴积分)依然是高斯

3DGS 为什么是椭球(实心椭球)

协方差矩阵如何控制椭球形状

\(\Sigma = A \cdot I \cdot A^{T}\) 可以看成对一个单位球做仿射变换,最后得到椭球。

仿射变换:本质上就是一个旋转矩阵乘一个缩放矩阵 A = RS

因为这里的 \(\Sigma\) 是对称矩阵,而对称矩阵的特征向量矩阵是正交矩阵,所以 \(U^{T} = U^{-1}\)

3D 高斯观测变换

3D 高斯投影到 2D 平面

每一次变换中,均值与协方差矩阵的变换表达式

世界空间中的 3D 高斯出发,一路跟随视图变换和投影变换,到最终图像空间中的 2D 椭圆高斯,在每个阶段都给出

\[ X \sim \mathcal{N}(\mu, \Sigma) \quad\Rightarrow\quad X' \sim \mathcal{N}(\mu', \Sigma') \]

步骤 0:原始 3D 高斯分布(世界空间)

  • 均值\(\mu_0 \in \mathbb{R}^3\)
  • 协方差\(\Sigma_0 = R S^2 R^\top \in \mathbb{R}^{3 \times 3}\)

步骤 1:应用视图变换(相机外参),从世界坐标到相机坐标

\(W \in \mathbb{R}^{3 \times 3}\) 为视图变换的旋转部分,\(d \in \mathbb{R}^3\) 为平移,则

\[ \mathbf{x}' = W \mathbf{x} + d \quad\Rightarrow\quad \begin{cases} \mu_1 = W \mu_0 + d \\ \Sigma_1 = W \Sigma_0 W^\top \end{cases} \]

步骤 2:透视投影变换(相机内参)

用非线性函数把相机空间坐标 \(\mathbf{x} = (x, y, z)\) 映射到图像空间:

\[ (u, v) = m(\mathbf{x}) = \left( f_x \cdot \frac{x}{z} + c_x,\; f_y \cdot \frac{y}{z} + c_y \right) \]

投影后均值直接代入即可:

\[ \mu_2 = m(\mu_1) \]

投影后协方差因为是非线性变换,需要用 Jacobian 做一阶近似。先计算雅可比矩阵

\[ J = \left. \frac{\partial m(\mathbf{x})}{\partial \mathbf{x}} \right|_{\mathbf{x} = \mu_1} \in \mathbb{R}^{2 \times 3} \]

然后变换协方差矩阵

\[ \Sigma_2 = J \Sigma_1 J^\top \]

α Blending

Note:上图中假设了 \(I_{BK}\) 是黑色,也就是全为零。

\(\alpha\) Blending 在数学定义上是一个从远到近融合的过程。

数学上 α Blending 等同于体渲染

两者做法完全不同,前者速度比后者快很多,但公式可以互相对应。

注意:代码中实际是由近到远做 alpha blending

这一点容易让人疑惑——传统 alpha blending 要求从远到近,为什么 3DGS 的实现反过来?

传统 alpha blending 的前提:半透明物体需要按从远到近的顺序渲染,才能正确叠加颜色和不透明度。

3DGS 的特点:每个高斯点代表一个小的半透明面片,数量非常大,而单个点的不透明度通常很低。渲染时需要在性能和视觉效果之间平衡。

采用从近到远的原因

  • 性能优化:从近到远渲染可以利用提前终止(Early Termination)。当累计不透明度达到一定程度时,后续高斯点对结果影响很小,可以直接跳过,节省计算。
  • 视觉误差可控:由于每个高斯点的不透明度很低,后续点对最终像素颜色的贡献很小,因此从近到远导致的误差在可接受范围内。

具体做法是维护累计不透明度 \(A\),初始化 \(A = 0\),对每个高斯点更新像素颜色并累加不透明度:

\[ \begin{aligned} C_{\text{out}} &\leftarrow C_{\text{out}} + (1 - A)\,\alpha_i\, C_i \\ A &\leftarrow A + (1 - A)\,\alpha_i \end{aligned} \]

\(A\) 接近 1 时,认为该像素已经完全不透明,可以提前终止后续计算。

在实现中,排序和渲染的流程如下:

  1. 排序高斯点:按从近到远的顺序排序。
  2. 渲染循环:遍历排序后的高斯点,累积颜色和不透明度。
  3. 提前终止:当 \(A\) 超过阈值(如 0.99)时跳出循环。
1
2
3
4
5
6
7
8
9
float A = 0.0f;              // 累计不透明度
for (每个高斯点) {
// 计算当前高斯点的不透明度 alpha_i
C_out += (1 - A) * alpha_i * C_i; // 更新输出颜色
A += (1 - A) * alpha_i; // 累加不透明度
if (A > 0.99f) { // 提前终止
break;
}
}

为什么不采用从远到近:从远到近时,先处理的远处高斯点对累计不透明度贡献很小,无法有效提前终止,必须处理所有高斯点,计算量巨大;而换成从近到远带来的颜色混合误差,由于单点不透明度很低,是可以接受的。

小结:渲染顺序从近到远,混合方式采用累计不透明度 \(A\),通过提前终止减少计算量,在保证性能的前提下尽可能接近正确的渲染结果。

每个 3DGS 椭球的参数

参数 表示 维度
中心点位置 \((x, y, z)\) 3
旋转 \(R\)(四元数) 4
缩放 \(S\) 3
球谐函数系数 SH \(16 \times 3 = 48\)
透明度 \(\alpha\) 1

总共 59 个系数决定一个 3D 高斯。

Densification 判据

判断是否需要分裂/克隆高斯时,用的是损失对 2D 投影均值的梯度在所有视角上的累积:

\[ \text{grad2d}_i = \sum_{t=1}^{T} \left\| \frac{\partial \mathcal{L}}{\partial \mathbf{m}_{2D,i}^{(t)}} \right\|_2 \]