3D Gaussian Splatting简称3DGS场景由大量椭球状高斯基元组成,每个基元除了位置、颜色和不透明度之外,还保存着旋转四元数和尺度向量。尺度向量通常对应PLY文件中的scale_0、scale_1、scale_2三个通道,它决定了高斯椭球在局部坐标系下沿三个轴的长度。缩放编辑的本质,就是按某个中心点或轴向对这三个尺度值进行比例修改,并根据需要同步移动高斯中心位置。这个过程如果只改尺度而不动位置,很容易让物体膨胀后偏离原本的轮廓,因此理解尺度参数背后的数学结构非常关键。

尺度参数在3DGS渲染中的数学角色
在3DGS的原始实现中,每个高斯的协方差矩阵被分解为旋转矩阵和缩放矩阵两部分。设旋转四元数为r,尺度向量为s=(s0,s1,s2),那么缩放矩阵S通常是对角矩阵,对角线元素为s0、s1、s2。旋转矩阵R由四元数转换得到。最终世界空间下的协方差矩阵可以写作:
Sigma = R * S * S^T * R^T
这里S直接来源于尺度向量,因此当你想把某个高斯放大到原来的两倍时,最直接的想法就是把s0、s1、s2同时乘以2。但要注意,3DGS在优化时通常对尺度做指数激活,例如源码中常看到scale_activation = exp。这意味着如果直接读取优化器中的原始参数,它可能是未经过激活的数值。对于已经保存到PLY文件中的scale_0等字段,通常已经是激活后的实际尺度,可以直接进行乘法编辑。
尺度参数并不代表像素半径,而是协方差意义上的标准差。放大尺度会让高斯在屏幕空间投影后的椭圆变大,其不透明度权重会在更大的面积上扩散。如果某个局部区域内大量高斯同时被放大,可能出现颜色混合过度、边缘模糊,甚至因为深度排序变化而出现新的伪影。因此尺度编辑不能简单理解成几何缩放,它会影响后续的alpha blending结果。
全局缩放与局部缩放的不同处理方式
全局缩放是最简单的情况。假设你想让整个3DGS场景统一缩小到原来的0.5倍,遍历所有高斯点,把位置坐标乘以0.5,同时把尺度向量也乘以0.5。这里位置和尺度必须一起缩放,因为高斯中心的位置决定了它在空间中的分布。只缩放尺度而保留位置,每个高斯会围绕自己的中心膨胀,场景的物体轮廓不会等比例变化,反而会互相重叠。
代码层面的逻辑可以概括为:
for i in range(num_gaussians):
points[i] = points[i] * scale_factor
scales[i] = scales[i] * scale_factor
局部缩放则更复杂。比如你只想放大场景中的一张桌子,而桌子的高斯点可能与其他物体混合在一起。此时需要先定义一个空间区域或者用分割模型选出目标高斯,然后围绕该区域的中心center做变换。位置更新公式为:
new_position = center + scale_factor * (old_position - center)
尺度更新仍然是对目标高斯乘以scale_factor。旋转四元数保持不变,因为均匀缩放不会改变朝向。如果是非均匀缩放,例如只沿X轴拉伸,那么位置更新也要沿X轴单独处理,并且尺度向量只修改scale_0。这种情况下高斯椭球会被拉长,渲染结果可能出现各向异性增强,需要小心控制比例范围。
用Python批量修改PLY中的尺度参数
3DGS训练得到的模型通常保存为PLY文件,其中包含每个高斯的x、y、z、f_dc_0到f_dc_2、opacity、scale_0、scale_1、scale_2、rot_0到rot_3等字段。使用plyfile库可以方便地读取和改写这些属性。下面这段代码展示了如何对一个PLY文件做全局缩放,并在内存中完成位置和尺度的同步修改。
import numpy as np
from plyfile import PlyData, PlyElement
def global_scale_ply(src_path, dst_path, scale_factor):
plydata = PlyData.read(src_path)
vertex = plydata['vertex']
x = vertex['x'].astype(np.float32)
y = vertex['y'].astype(np.float32)
z = vertex['z'].astype(np.float32)
s0 = vertex['scale_0'].astype(np.float32)
s1 = vertex['scale_1'].astype(np.float32)
s2 = vertex['scale_2'].astype(np.float32)
new_x = x * scale_factor
new_y = y * scale_factor
new_z = z * scale_factor
new_s0 = s0 * scale_factor
new_s1 = s1 * scale_factor
new_s2 = s2 * scale_factor
vertex['x'] = new_x
vertex['y'] = new_y
vertex['z'] = new_z
vertex['scale_0'] = new_s0
vertex['scale_1'] = new_s1
vertex['scale_2'] = new_s2
PlyData([vertex], text=True).write(dst_path)
局部缩放可以在这个基础上增加掩码判断。比如先根据x、y、z的范围选出目标区域的索引,再对这些索引应用位置和尺度公式。需要注意,PLY中的数组是结构化的,修改后应保持数据类型一致,避免精度丢失。对于颜色和球谐系数,通常不需要改动,但如果缩放后光照或颜色分布发生变化,也可以在后处理阶段做一次颜色归一化。
如果使用图形界面工具进行框选缩放,底层逻辑也类似:编辑器把用户框选的范围转换为一组高斯索引,随后在GPU或CPU上执行上述数组运算,并将结果重新写回渲染器。区别在于编辑器可能还会记录一个撤销栈,保存修改前后的尺度与位置数据,方便交互式回退。
尺度编辑后的常见问题与优化建议
缩放编辑完成后,最容易出现的问题是高斯之间的空隙或重叠。由于3DGS的几何形状是连续椭球,而训练时这些椭球会尽量贴合表面,当尺度变化较大时,原本紧挨的高斯可能因为中心移动而拉开距离,露出背景像素。此时直接渲染会看到物体表面出现细小空洞。一种缓解方式是对尺度向量施加较小的比例系数,例如0.9到1.1之间,避免一次性剧烈缩放。
另一个常见问题是缩放之后物体边缘出现模糊。这是因为高斯椭球变大后,每个基元在屏幕上的投影面积增加,alpha blending时更多半透明像素叠加在一起。如果场景本身有复杂的遮挡关系,深度排序可能发生改变。对于这种情况,可以只缩放目标物体的中心位置与尺度,同时保持其外围高斯的尺度不变,或者在缩放后重新对局部区域做少量迭代优化,让高斯形状重新贴合物体轮廓。
最后要注意尺度参数与不透明度的耦合。尺度放大后,单个高斯在屏幕空间的覆盖面积变大,但其不透明度权重并不会自动降低。于是整个区域的亮度可能升高、颜色饱和度下降。为了平衡视觉效果,可以按照缩放倍率的平方关系适当降低目标高斯的不透明度。非均匀缩放还可能导致椭球在某些视角下变得非常薄,从而投影成细线,这种情况下建议优先使用均匀缩放,或者在三个尺度通道上分别设置合理下限。