 片元着色器)
片元着色器是真正“绘制”高斯形状的地方。对于包围盒内的每一个像素我们需要计算它相对于椭圆中心的马氏距离Mahalanobis Distance并据此输出颜色和透明度。这里有一个非常关键的细节我们不需要使用概率密度函数中的归一化系数12π√det(Σ)。在 3DGS 中我们的目标是 Alpha 混合Alpha Blending而非概率估计。高斯椭圆的“浓淡”是由我们单独学习的一个参数——不透明度Opacity,α 来控制的。如果强行加入归一化系数反而会导致颜色变得极暗因为协方差矩阵的行列式通常很小会把颜色“压”没。// 片元着色器 (Fragment Shader)in vec2 v_position; // 相对于中心的实际偏移量uniform mat2 u_inv_covariance; // 协方差矩阵的逆uniform vec3 u_color;uniform float u_opacity; // 基础不透明度out vec4 frag_color;void main() {// 1. 计算马氏距离的平方// 公式: d² (x - μ)ᵀ * Σ⁻¹ * (x - μ)// v_position 就是 (x - μ)float d_squared dot(v_position, u_inv_covariance * v_position);// 2. 计算 Alpha // 注意这里没有归一化系数直接由基础不透明度控制 float alpha u_opacity * exp(-0.5 * d_squared); // 3. 裁剪如果透明度过低直接丢弃提升性能 if (alpha 0.004) { // 0.004 约等于 1/255 discard; } // 4. 输出最终颜色 frag_color vec4(u_color, alpha);}3. 补充说明通过这套着色器组合GPU 就能高效地将数百万个数学定义的高斯椭圆渲染到屏幕上。顶点着色器负责“画框”片元着色器负责“填色”最终呈现出平滑、连续的 3D 场景。不过需要指出的是上述代码主要侧重于原理的直观展示属于一种理论化实现旨在帮助读者理解 3DGS 的渲染逻辑。在实际的工业级工程应用中为了追求极致的性能实现方式可能有所不同但其底层的数学原理——即利用二维高斯函数进行屏幕空间投影与混合——是完全一致的。六、球谐系数在上一节我们详细探讨了 3DGS 中每个高斯椭球的几何属性位置、缩放、旋转但一个逼真的 3D 场景显然不能只有形状还必须有丰富的颜色和光照信息。回到第三章提到的 .ply 文件属性列表你会发现除了 scale 和 rot 之外还有一长串以 f_dc 和 f_rest 开头的属性property float f_dc_0property float f_dc_1property float f_dc_2property float f_rest_0…property float f_rest_44这些属性并非简单的 RGB 颜色值而是球谐函数Spherical Harmonics, SH系数。在 3DGS 中每个高斯椭球的颜色不是固定的而是会随着观察角度的变化而变化以此来模拟真实世界中光照与材质相互作用产生的复杂视觉效果如镜面高光、菲涅尔效应等。球谐函数正是实现这一“视角相关颜色”的核心数学工具。傅里叶变换要解释什么是球谐函数我们先了解一下类似的傅里叶变换。在数字信号处理中傅里叶变换的核心思想是任何复杂的波形都可以被拆解成无数个频率、振幅各不相同的纯正弦波的叠加。为了理解这个过程我们需要先认识两个核心概念基函数 (Basis Functions)这些“纯正弦波”就是傅里叶变换的基函数。它们是构成复杂信号的“标准波纹”。分解与重构傅里叶变换的过程就是用这些标准波纹去“测量”原始信号计算出每个频率的波纹在信号中占多少“份量”即系数。在数学上这一过程可以用以下公式严谨地表达f(x)∞∑n0cn⋅ϕn(x)(23)其中f(x)是我们要分析的原始复杂信号。ϕn(x)就是第n个基函数即我们提到的“标准正弦波纹”。cn是对应的系数代表了该基函数在原始信号中的权重或“份量”。简单来说傅里叶变换就是在一条直线上用不同频率的正弦波作为“尺子”去度量和表达一个复杂信号。球谐函数现在我们将傅里叶变换的思想从一维的“线”推广到三维空间中的“面”——一个单位球面。在计算机图形学中我们常常需要描述一个点周围的光照环境。光线可以从四面八方射来这个光照信息是定义在一个球面上的函数称为光照函数 (Lighting Function)。那么如何表示这个复杂的球面函数呢答案就是球谐函数 (Spherical Harmonics, SH)。如果说傅里叶变换是用“平面正弦波”去拟合一条线上的信号那么球谐变换就是用“球面波纹”去拟合一个球面上的信号。基函数球谐函数的基函数不再是正弦波而是一系列定义在球面上的、具有不同频率和方向的“波纹图案”。0 阶 SH像一个纯色的球代表整个球面的平均光照环境光。1 阶 SH像一个从黑到白渐变的球能表示一个主方向的光照比如太阳从上方照射。2 阶及更高图案越来越复杂能表示更精细的光照细节和反射。因此球谐函数本质上就是“球面上的傅里叶变换”。它用一组预定义的球面基函数将复杂的环境光照压缩成一小撮系数。在数学上球谐函数的展开形式与傅里叶变换高度相似其公式为f(θ,ϕ)≈n∑l0l∑m−lcmlYml(θ,ϕ)(24)其中f(θ,ϕ)是定义在球面上的原始函数如光照分布。Yml(θ,ϕ)是球谐基函数由阶数l和频率m共同决定。cml是对应的球谐系数代表了该基函数在原始球面函数中的权重。进一步地球谐基函数Yml(θ,ϕ)的通用数学定义由三个部分组成Yml(θ,ϕ)Kml⋅Pml(cosθ)⋅eimϕ(25)Kml归一化常数这是一个为了保证数学严谨性而存在的系数确保基函数在球面上的积分为 1。具体计算式为Kml√2l14π(l−m)!(lm)!Pml(cosθ)关联勒让德多项式这是决定球谐函数“形状”的核心部分。不同的阶数l和频率m会对应不同的多项式形状如常数、线性、二次曲面等。eimϕ复指数项这一项代表了函数在水平方向方位角ϕ上的旋转变化。在渲染时我们只需根据观察方向用这些系数和基函数进行加权求和就能快速还原出该方向上的光照强度。PBR中的球谐函数在计算机图形学的基于物理的渲染PBR中球谐函数扮演着至关重要的角色主要用于高效地计算间接漫反射光照。当一个物体如角色、车辆在场景中移动时它不仅要被太阳光直接光照亮还会被周围环境如墙壁、地面反射的光线照亮。这部分光线是柔和且无明确方向的漫反射光。为了实现这个效果图形引擎会在场景中预先放置一些光照探针Light Probes。这些探针会“捕捉”周围环境的光照信息并将其编码为一组球谐系数。当动态物体经过时GPU 会根据其位置插值出对应的 SH 系数并实时计算出物体表面接收到的环境光。这样做的好处是用极少的计算量通常只需 2 阶 9 个系数就能模拟出非常逼真的全局光照效果让动态物体完美地融入静态场景。3DGS中的球谐函数在 3DGS 中球谐函数的用途与 PBR 略有不同。它不是用来计算环境光而是直接用来表示每个高斯椭球自身的视角相关颜色View-dependent Color。3DGS 的核心思想是一个高斯点在不同视角下看起来颜色是不同的这模拟了现实世界中物体表面的高光Specular Highlight和菲涅尔效应Fresnel Effect。例如一个光滑的桌面从某个角度看会有刺眼的反光换个角度则没有。因此在 3DGS 训练阶段算法会为每个高斯点学习一组球谐系数存储在 .ply 文件的 f_rest 字段中。这组系数编码了该点在所有方向上的颜色变化规律。而在渲染阶段当摄像机观察这个高斯点时GPU 会根据观察方向View Direction和这组 SH 系数实时计算出该点在当前视角下应该呈现的颜色。为了平衡渲染质量与性能3DGS 通常默认使用 3 阶球谐函数。这意味着公式 (24) 中的求和上限n取值为 3阶数l的取值范围是0,1,2,3。根据球谐函数的性质对于每一个阶数l频率m的取值范围是−l到l。因此总的基函数数量即系数个数为 1 3 5 7 16 个。将这 16 个基函数项代入公式 (24)我们可以将其展开为四个部分的加权和f(θ,ϕ)≈0∑m0cm0Ym00 阶: 基础漫反射1∑m−1cm1Ym11 阶: 线性方向2∑m−2cm2Ym22 阶: 二次曲面3∑m−3cm3Ym33 阶: 三次细节(26)具体展开后的 16 项如下f(v)≈c00(0.28)c−11(−0.48y)c01(0.48z)c11(−0.48x)c−22(1.09xy)c−12(−1.09yz)c02(0.31(3z2−1))c12(−1.09xz)c22(0.54(x2−y2))c−33(−0.59(3x2−y2)y)c−23(2.89xyz)c−13(−0.45y(4z2−x2−y2))c03(0.39z(5z2−3))c13(−0.45x(4z2−x2−y2))c23(1.44z(x2−y2))c33(−0.59x(x2−3y2))(27)在这个公式中常数项这些数字并非随意指定而是根据球谐基函数的通用数学定义代入不同的阶数l和频率m后计算出的归一化系数。它们确保了基函数在球面上的能量分布是标准的。x,y,z多项式球谐基函数原本是基于球体极坐标系θ,ϕ定义的包含复杂的三角函数如sinθcosϕ。但在 3D 图形学的实际应用中为了方便 GPU 进行高效的矩阵运算我们通常会将这些极坐标下的三角函数通过数学变换“翻译”成三维笛卡尔坐标系下的多项式形式。观察方向球谐函数本身是定义在“方向”上的3DGS 中计算的是“当前视角”下的颜色所以这个“方向”就是摄像机的观察方向也就是这里的笛卡尔坐标(x,y,z)。RGB 三通道上述公式中的每一个系数cml实际上都是一个包含 R、G、B 三个分量的向量。因此对于 RGB 三个通道总共需要存储16×348个浮点数。这使得 3DGS 能够用极低的存储开销使用 3 阶 SH共 16 个系数RGB 三通道共 48 个 float 值就模拟出复杂的光照交互效果让重建的场景看起来富有光泽和质感而不是像塑料一样平淡。3DGS 着色伪代码结合前面的知识我们可以写出 3DGS 中计算最终颜色的核心伪代码。这个过程发生在片元着色器Fragment Shader中// 片元着色器伪代码// 输入vec3 view_direction; // 观察方向float sh_coeffs[48]; // 完整的球谐系数 (f_dc_0~2 f_rest_0~44)float opacity; // 基础不透明度// 1. 计算 16 个球谐基函数值 (基于观察方向)float sh_basis[16];sh_basis[0] 0.282095; // 0 阶 (对应 f_dc)sh_basis[1] -0.488603 * view_direction.y; // 1 阶 (对应 f_rest 前3个)sh_basis[2] 0.488603 * view_direction.z;sh_basis[3] -0.488603 * view_direction.x;// … 计算 2 阶和 3 阶的基函数 (对应 f_rest 后42个)// 2. 统一计算最终颜色// 将 48 个系数与 16 个基函数进行加权求和vec3 final_color vec3(0.0);for (int i 0; i 16; i) {// RGB 三个通道分别计算final_color.r sh_coeffs[i] * sh_basis[i];final_color.g sh_coeffs[i 16] * sh_basis[i];final_color.b sh_coeffs[i 32] * sh_basis[i];}// 3. 计算最终 Alphafloat alpha opacity * exp(-0.5 * d_squared);// 输出frag_color vec4(final_color, alpha);通过这套机制3DGS 巧妙地将复杂的、视角相关的光照效果“烘焙”进了一小组球谐系数中实现了在极低计算成本下的高质量实时渲染。