各向异性 (1) 数学意义 在计算机图形学中几何体的形态特征通常分为两类各向同性物体在各个方向上的性质如尺寸、密度都是相同的。例如一个完美的球体无论从哪个角度看它的“宽度”都一样。各向异性物体在不同方向上的性质表现出差异。例如一个椭球体它在长轴方向上看起来很宽但在短轴方向上看起来很窄。在 3DGS 的语境下各向异性指的就是高斯椭球体在三维空间中呈现出的这种“非均匀”的拉伸或压缩特性。这种特性并非随意产生而是由协方差矩阵Σ的内部结构严格决定的。以式 (8) 的三维高斯函数的协方差矩阵为例对角线元素σ2x,σ2y,σ2z—— 方差的大小它们决定了椭球体在x,y,z三个坐标轴方向上的扩散程度。如果这三个值相等σ2xσ2yσ2z那么该高斯点就是一个各向同性的球体。但在 3DGS 中我们通常需要让椭球在某些方向上更“胖”方差大在某些方向上更“瘦”方差小从而形成椭球的长短轴。这种方差在不同维度上的不均衡分布是产生各向异性的基础。非对角线元素σxy,σxz,σyz—— 协方差的耦合这些元素描述了不同维度之间的相关性。如果非对角线元素全为 0椭球的主轴将平行于坐标轴如果非对角线元素不为 0意味着x的变化会带动y或z的变化这种维度间的“联动”会导致椭球在空间中发生旋转使其主轴指向任意空间方向。通过特征分解式 (10) 我们将这种复杂的耦合关系解耦为直观的旋转R和缩放ΛΣRΛRT(11)缩放矩阵Λ直接量化了各向异性的强度。Λ的三个对角线元素特征值之比就是椭球长短轴之比。如果三个特征值差异巨大说明该高斯点具有极强的各向异性例如一个极扁的盘状或极长的棒状。旋转矩阵R决定了各向异性的方向。它将椭球的主轴由Λ定义的最长、最短方向旋转到世界坐标系中的正确朝向。(2) 物理意义在 3DGS 的渲染过程中各向异性赋予了算法“理解几何细节”的能力边缘保持Edge Preservation在物体的边缘或角落处光线的变化是剧烈且具有方向性的。各向异性允许高斯椭球在边缘方向上拉伸大半轴在垂直于边缘的方向上压缩小半轴。这样一个各向异性的椭球就能完美地贴合物体的锐利边缘而不会像各向同性的球体那样在边缘处产生模糊的“光晕”伪影。表面拟合Surface Fitting对于光滑的曲面各向异性椭球可以像“瓦片”一样平铺在表面上其扁平的形态能更好地模拟表面的切平面从而在较少的点数下实现极高的视觉保真度。细节表达Detail Representation各向异性是 3DGS 能够从简单的“点云”进化为复杂的“连续场”的关键。它使得每一个高斯点不再是一个模糊的色块而是一个具有明确几何指向的微小图元。总结来说各向异性是 3DGS 的“画笔”。通过精确控制每一个高斯椭球的Σ矩阵即 .ply 文件中的 scale 和 rot我们实际上是在控制这支画笔的笔触——是画一个圆点还是画一笔长线条或者是铺一片平面。正是这种对空间分布的精细控制使得 3DGS 能够以极高的效率和质量重建出复杂的 3D 场景。高斯的本质在理解了高斯椭球体的数学构造与各向异性特性后就能理解为什么可以使用“高斯椭球”来表达可视化场景的原因了。当我们用相机拍摄物体时受限于镜头的光圈衍射、景深限制以及物体表面微观几何的粗糙度光线在空间中的传播和汇聚从来都不是理想化的“锐利边界”而是充满了平滑的过渡和不可避免的模糊。而高斯函数则可以比较好地契合这个成像规律能量或概率高度集中在中心点μ且随着与中心点距离的增加呈指数级衰减边缘的值迅速趋近于 0。高斯椭球体本质上是一种基于物理的辐射场表达方式是一团“边缘柔和的发光云”每一个高斯点都在空间中定义了一个连续的、可微的辐射场。当无数个这样的高斯云在空间中叠加时它们能够自然地融合与过渡无需任何额外的抗锯齿处理就能复现出照片级的真实感。四、高斯投影已经了解了三维高斯椭球体的本质那么 3DGS 可视化是不是就是直接在 GPU 中绘制一堆三维椭球然后进行深度排序和透明度混合呢理论上可行但这在实际工程中是无法做到的。原因有二一是性能瓶颈数百万个动态图元的深度排序和混合会瞬间拖垮渲染管线二是硬件限制现代光栅化硬件最擅长处理的是三角形而不是这种数学定义的曲面体。因此3DGS 采取了一种极其聪明的视平面投影策略。它的核心思想是既然最终画面是二维的我们何不直接计算出每个 3D 高斯椭球在当前摄像机视角下投影到屏幕上的 2D 形态然后像绘制普通 2D 图像一样去绘制它我们将其转换成数学模型即一个三维高斯椭球由其在三维空间中的协方差矩阵Σ定义当我们将这个椭球投影到二维图像平面时它会变成一个二维高斯椭圆。这个新的二维椭圆同样可以由一个2×2的协方差矩阵Σ′来描述。那么如何从Σ得到Σ′呢这里就涉及到了雅可比矩阵Jacobian Matrix。雅可比矩阵想象一下我们将三维空间中的一个点x通过一个变换函数T映射到二维图像平面上的点x′。这个变换T包含了从世界坐标系到相机坐标系再到图像坐标系的整个过程即模型-视图-投影变换。x′T(x)(12)很显然由于透视投影的存在这是一个复杂的非线性变换。为了研究它我们无法直接处理全局的弯曲只能退而求其次研究其局部线性的性质。类比来说就好像在地球表面上行走时我们感觉地面是平的。在数学上我们可以利用泰勒公式对变换函数T在点x附近进行一阶展开。忽略高阶无穷小量后变换的增量Δx′与Δx之间近似满足线性关系Δx′≈∂T∂xΔx(13)这里的偏导数矩阵∂T∂x就是雅可比矩阵J。它本质上是变换函数T在局部的一阶导数J∂T∂x⎡⎢⎣∂x′∂x∂x′∂y∂x′∂z∂y′∂x∂y′∂y∂y′∂z⎤⎥⎦(14)这个2×3的矩阵J告诉我们三维空间中的一个微小变化或扰动会如何线性地映射并影响二维图像平面上的位置。正是通过这种“局部线性化”的手段我们将复杂的非线性投影问题转化为了简单的矩阵乘法问题。协方差传播在概率论和统计学中有一个极其重要的性质称为协方差传播定律Law of Propagation of Covariance。这个定律的完整表述是如果一个随机向量x的协方差矩阵是Σx它经过一个可微的变换T得到一个新的随机向量yT(x)那么新向量y的协方差矩阵Σy可以通过以下公式近似计算Σy≈JΣxJT(15)其中J就是变换T在x的均值点处的雅可比矩阵。这里的近似条件取决于以下两种情况线性变换如果变换T是严格的线性变换例如旋转、缩放那么上述公式是精确成立的。非线性变换如果变换T是非线性的例如我们这里的透视投影那么这个公式是一个一阶近似。它相当于在局部用一个切平面由雅可比矩阵定义来近似这个非线性的曲面。3. 投影公式具体到我们的场景中三维高斯椭球的协方差矩阵Σ在经过投影变换T后其在二维图像平面上形成的椭圆的协方差矩阵Σ′可以通过以下公式计算Σ′JΣJT(16)其中J就是我们在上一节中提到的、投影变换在当前高斯中心点处的雅可比矩阵。由于透视投影本质上是非线性的上述公式其实是协方差传播定律在一阶近似下的应用。它相当于在局部用一个切平面由雅可比矩阵定义来近似这个非线性的曲面。但在 3DGS 的实际应用中由于每个高斯椭球本身的尺寸相对于整个宏大的场景来说非常微小这种一阶近似已经足够精确并且计算效率极高。这个公式就是 3DGS 渲染管线的核心。它意味着输入每个高斯椭球在三维空间中的协方差矩阵Σ由 .ply 文件中的 scale 和 rot 参数实时计算得出。变换根据当前摄像机的位置和角度计算出从世界空间到当前帧图像空间的雅可比矩阵J。输出通过一次简单的矩阵乘法得到该椭球在当前视角下应该呈现的二维高斯椭圆的协方差矩阵Σ′。通过这种方式3DGS 极其聪明地将三维空间中的“不确定性椭球”精准地“压扁”成了二维图像上的“不确定性椭圆”完美地衔接了 3D 场景与 2D 屏幕。具体计算理论虽然完美但在代码实现中我们需要把这个过程拆解为具体的数学步骤。整个投影过程可以概括为三步视图变换、投影变换、协方差传播。(1) 视图变换首先我们需要将高斯椭球从世界坐标系转换到相机坐标系。这一步非常简单它是一个纯粹的刚体变换旋转 平移由视图矩阵W决定。假设高斯椭球在世界坐标系下的中心为μworld协方差为Σworld。经过视图变换后它在相机坐标系下的新中心μcam和新协方差Σcam为μcamWμworld(17)ΣcamWΣworldWT(18)因为视图变换是线性的所以协方差矩阵直接左乘右乘视图矩阵即可不需要用到雅可比矩阵。此时我们得到了一个位于相机前方的 3D 高斯椭球。(2) 投影变换接下来是最关键的一步将相机坐标系下的 3D 点(x,y,z)投影到 2D 图像平面(u,v)上。在针孔相机模型中这个投影过程是非线性的其数学公式为ufxxzcx(19)vfyyzcy(20)其中fx,fy是相机的焦距像素单位。cx,cy是图像的主点通常是图像中心。z是深度值。为了应用协方差传播定律我们需要计算这个非线性变换在点(x,y,z)处的雅可比矩阵J。根据偏导数的定义我们对u和v分别关于x,y,z求导∂u∂xfxz∂u∂y0∂u∂z−fxxz2∂v∂x0∂v∂yfyz∂v∂z−fyyz2将这些偏导数组合起来我们就得到了具体的2×3雅可比矩阵J⎡⎢⎣fxz0−fxxz20fyz−fyyz2⎤⎥⎦(21)现在我们将相机坐标系下的协方差Σcam和刚刚算出的雅可比矩阵J代入协方差传播公式Σ2DJΣcamJT(22)计算出的Σ2D就是一个2×2的矩阵。它描述了该高斯椭球在图像平面上的形状。(3) 完整流程为了更清晰地理解我们可以把整个过程串联起来输入从 .ply 文件读取 3D 高斯的参数位置、缩放、旋转、颜色、透明度。视图变换利用视图矩阵W将 3D 高斯从世界空间变换到相机空间得到Σcam。计算雅可比根据当前高斯在相机空间的位置(x,y,z)和相机内参(fx,fy)计算雅可比矩阵J。投影利用公式Σ2DJΣcamJT计算出 2D 协方差矩阵。光栅化准备对Σ2D进行特征分解得到 2D 椭圆的长短轴和旋转角度。绘制将这个 2D 椭圆实际上是一个带模糊边缘的贴片绘制到屏幕的对应像素上并根据深度z进行排序和混合。通过这一套严密的数学推导3DGS 成功地将复杂的 3D 渲染问题转化为了高效的 2D 图像处理问题。五、图形实现经过上一章的推导我们已经知道每一个三维高斯椭球都能够通过雅可比矩阵投影成屏幕上的二维高斯椭圆。那么GPU 到底应该如何把这个二维高斯椭圆绘制出来很显然由于我们已经通过高斯投影知道了屏幕空间的椭圆那么就不太适合“顶点(Vertex)-图元装配-光栅化(Rasterization)-片元(Fragment)-像素(Pixel)”这一套经典的光栅化流水线过程了。在论文原版 3DGS 渲染实现中是使用 GPU Compute Shader直接写 Image 的方法来实现的。这里笔者讨论是如何利用现有的图形渲染技术进行绘制的问题因为我们希望 3DGS 能存在于已有的图形渲染队列中。程序化渲染如果接触过粒子系统Particle System会发现这里其实非常类似一种经典技术——Billboard广告牌。Billboard 的思想非常简单GPU 并不会真正绘制一个三维物体。它只是生成一个始终朝向摄像机的二维矩形Quad然后在这个矩形内部完成纹理采样。例如一个火焰粒子Camera↑±-------------| || Texture || |±-------------无论摄像机如何旋转这个 Quad 都会始终面向屏幕因此用户看到的是一个始终正对自己的粒子。3DGS 的高斯椭圆与它非常相似区别在于 Billboard 绘制的是一个矩形 一张纹理而 Gaussian Splatting 绘制的是一个矩形 一个二维高斯函数。也就是说高斯椭圆并不是一张预先准备好的图片而是在 Shader 中实时计算出来的连续函数。由于二维高斯可以直接由数学公式计算因此实际上并不存在真正意义上的高斯模型。GPU 真正绘制的仍然只是一个十分普通的矩形。整个流程可以理解为Gaussian│▼计算二维椭圆│▼生成包围矩形(Quad)│▼Rasterization│▼Fragment Shader│▼计算二维高斯函数│▼输出颜色与透明度真正的高斯形状并不是由几何决定而是由 Fragment Shader 中的数学计算决定。因此这种方式通常也称为程序化渲染Procedural Rendering。这种技术在现代 GPU 渲染中十分常见总结起来就是几何尽可能简单而图形细节由 Shader 动态生成。着色实现在明确了“程序化渲染”的策略后我们需要将之前推导的数学公式落地到具体的 Shader 代码中。这里笔者将整个渲染流程拆分为两部分CPU 端负责投影与几何计算GPU 端则专注于的图元定位与颜色混合。(1) CPU 端在每一帧渲染开始前在 CPU 端遍历所有的高斯椭球完成从 3D 到 2D 的投影计算。这一步的核心是利用雅可比矩阵计算出屏幕空间的协方差矩阵并对其进行特征分解从而得到绘制所需的精确几何参数。// CPU 端逻辑为每个高斯计算投影参数for (每个高斯 gaussian) {// … 省略视图变换与雅可比矩阵计算假设已得到 2D 协方差矩阵 covariance_2d …// 1. 特征分解获取旋转和缩放 // eigenvalues: 两个特征值 (lambda1, lambda2) // eigenvectors: 对应的特征向量 (组成旋转矩阵) auto [eigenvalues, eigenvectors] decompose(covariance_2d); // 2. 计算半轴长度 (通常截取 3 倍标准差覆盖 99% 的能量) vec2 semi_axes 3.0 * sqrt(eigenvalues); // 3. 计算旋转角度 (从特征向量中提取) float rotation atan2(eigenvectors[0].y, eigenvectors[0].x); // 4. 打包数据发给 GPU GpuData data; data.center project_to_screen(position_cam); // 屏幕中心点 (u, v) data.semi_axes semi_axes; // 精确的半轴长度 data.rotation rotation; // 精确的旋转角度 data.inv_covariance inverse(covariance_2d); // 协方差矩阵的逆用于片元计算 data.color gaussian.color; data.opacity gaussian.opacity; // 学习到的基础不透明度 gpu_buffer.push(data);}(2) 顶点着色器顶点着色器的任务非常纯粹接收 CPU 传来的几何参数利用 gl_VertexID 动态生成一个包围该高斯椭圆的矩形Quad并将其变换到正确的屏幕位置。// 顶点着色器 (Vertex Shader)in vec2 u_center; // 屏幕中心in vec2 u_semi_axes; // 精确的半轴长度 (来自特征分解)in float u_rotation; // 精确的旋转角度out vec2 v_position; // 传递给片元着色器的局部偏移量void main() {// 1. 基础矩形的四个角 (-1 到 1)vec2 offsets[4] vec2[](vec2(-1,-1), vec2(1,-1), vec2(-1,1), vec2(1,1));vec2 local_pos offsets[gl_VertexID];// 2. 缩放将 -1~1 的范围拉伸到实际的半轴长度 vec2 scaled_pos local_pos * u_semi_axes; // 3. 旋转应用旋转矩阵 float cos_r cos(u_rotation); float sin_r sin(u_rotation); mat2 rot_matrix mat2(cos_r, -sin_r, sin_r, cos_r); vec2 rotated_pos rot_matrix * scaled_pos; // 4. 平移到屏幕中心 vec2 screen_pos u_center rotated_pos; // 5. 转换为标准化设备坐标 (NDC) vec2 ndc (screen_pos / u_screen_size) * 2.0 - 1.0; gl_Position vec4(ndc, 0.0, 1.0); // 6. 传递局部偏移量 (用于片元着色器计算高斯衰减) v_position rotated_pos;