缩远闪烁来自 3D 频率超界,拉近浮肿来自 += 0.3f。两件事
必须分别用两个滤波器解决。
(a) 3D 平滑滤波器:用每个高斯在所有训练视图里被观察到的最高采样率
$\hat\nu_k$ 给它烙上一个 Nyquist 上限;
(b) 2D Mip 滤波器:把 += 0.3f 换成单位是像素的低通,
自然随渲染分辨率缩放。两个滤波器都是高斯卷积,开销 = 协方差对角线加一个常数。
2023 年 8 月 Kerbl 等人把 3DGS 推上 SIGGRAPH,第二天就有人发现: 缩远闪烁,拉近浮肿,相机一转色块还会"弹"——这不是工程瑕疵,是一个根植于 渲染管线的采样问题,根因可追溯到 1928 年的 Nyquist–Shannon 定理。
本文按时间线串起从 Mip-Splatting (CVPR 2024) 到 SAD-GS (arXiv 2026.04) 这三年里 所有 主要抗锯齿工作。预设读者:会基本的 NeRF、SDF、机器学习、 积分、卷积、矩阵与特征分解。
没碰过 3DGS:从 §1 顺序读到 §3,玩完前两个 demo 后, 直接到 §4 Mip-Splatting——它是该领域几乎所有后续工作的 baseline。 已经熟悉原始 3DGS:从 §5 Analytic-Splatting 开始; 想看最近一年的新东西:跳到 第八部 · Frontier。
在做任何 3DGS 修补之前,我们先把"锯齿"这个词在一维信号上讲清楚。 整篇综述里出现的每一种抗锯齿招数,本质都是在解决 1D 问题的某个 2D / 3D 变体。
下面是个最简单的实验。蓝线是真实的连续信号 $f(x) = \sin(2\pi\omega x)$, 红点是固定间隔的离散采样,橙虚线是把这些点用直线连起来后你"看到"的信号。 拖动滑动条改变频率和采样率:
要正确重建一个最高频率为 $\omega_{\max}$ 的信号,采样率必须满足
$$f_s > 2\omega_{\max}$$否则高频会被"折叠"进低频,产生混叠 (aliasing)。 电影里车轮反着转、电视画面摩尔纹、CG 远景闪烁——本质都是这一条不等式被违反了。
屏幕像素是采样点,3D 场景是连续信号。当二者节奏不匹配时,你会看到三种典型病症—— 本综述里几乎所有论文都在打这三只怪兽里的一只或两只:
远景中本应是 sub-pixel 的细节 (电线、树叶) 在帧之间忽明忽暗,像电视雪花。
原因:高斯比像素还小,点采样错过了它们。
同一个场景渲染到 8× 分辨率,边缘变软、光晕扩大,看起来比训练视图还糊。
原因:原始 3DGS 用 += 0.3f 强行把每个 splat 撑到 1 像素大,分辨率变了就过度膨胀。
平移相机时本来稳定的色块突然换色——像 PPT 翻页。
原因:每个高斯只用中心深度排一次序,相机一转排序就翻了。
如果你做过 NeRF:NeRF 把场景表示成隐式的连续函数 $F_\theta: (x,d) \to (c, \sigma)$,渲染时沿每条相机射线积分。 3DGS 把场景表示成显式的一堆 3D 高斯椭球,每个椭球有自己的位置 $\mu$、 协方差 $\Sigma$、颜色 $c$ 和不透明度 $o$。渲染时不积分,而是把每个椭球 投影到屏幕上变成一个 2D 椭圆,然后从前往后做 alpha 混合。
为了保证 $\Sigma$ 在梯度下降中始终半正定,3DGS 把它分解成 $\Sigma = R\,S\,S^\top R^\top$,其中 $R$ 来自一个单位四元数 (3 自由度), $S = \mathrm{diag}(s_x, s_y, s_z)$ 是各向异性的尺度 (3 自由度)。所以每个 3D 高斯有 3 (位置) + 3 (尺度) + 4 (四元数) + 1 (不透明度) + 颜色 个参数。
关键一步是把 3D 协方差 $\Sigma$ 变成 2D 屏幕空间的协方差 $\Sigma'$。 透视投影是非线性的 (除以 z),3DGS 借用 EWA Splatting (Zwicker 2001) 的局部一阶 Taylor 展开:
$$\Sigma' = J\,W\,\Sigma\,W^\top J^\top$$$W$ 是 world → camera 变换,$J$ 是透视投影在该高斯中心的 Jacobian。 $J$ 越靠近图像中心越准;越靠图像边缘 (大视场角),误差越大——这就为后面 OP3DGS 那一刀埋下了伏笔。
+= 0.3f
在原始 CUDA 代码 computeCov2D 计算完 $\Sigma'$ 之后,紧接着是:
// 原版 3DGS forward.cu:
glm::mat3 J = glm::mat3(
focal_x / t.z, 0.0f, -(focal_x * t.x) / (t.z * t.z),
0.0f, focal_y / t.z, -(focal_y * t.y) / (t.z * t.z),
0, 0, 0);
glm::mat3 T = W * J;
glm::mat3 cov = glm::transpose(T) * glm::transpose(Vrk) * T;
// "每个高斯至少 1 像素宽" 的小聪明
cov[0][0] += 0.3f;
cov[1][1] += 0.3f;
这两行代码在屏幕空间的 2D 协方差对角线上加了 $0.3\,\text{像素}^2$,等于给每个 splat 卷上了一个标准差约 0.55 像素的高斯低通。 表面上是为了防止 sub-pixel 的高斯漏过像素网格,本质上是一个粗暴的低通滤波。问题在于:
对于像素 $p$,所有与之相交的高斯按深度排序后混合:
$$C(p) = \sum_{i\in N} c_i\,\alpha_i \prod_{j \lt i}(1-\alpha_j),\quad \alpha_i = o_i\exp\!\Big(\!-\tfrac{1}{2}(p-\mu_i')^\top \Sigma_i'^{-1}(p-\mu_i')\Big)$$这里 $p$ 是像素中心的一个点,不是整个像素方块—— Analytic-Splatting 后面要修的就是这件事。
2023 年下半年,社区开始把同一个 3DGS 模型在训练时没见过的分辨率下渲染—— 这是 NeRF 早就做过的常规测试。结果一塌糊涂:缩小到 1/8 分辨率时图像闪烁、马赛克;放大到 8× 时物体轮廓变软,每个 splat 的边界肉眼可见。
"缩远闪烁 + 拉近浮肿"。这其实是 2001 年 EWA Splatting (Zwicker et al.) 早就解决过的问题——但
3DGS 为了追求实时速度把 EWA 的核心"重采样滤波器"砍掉了,只留下 += 0.3f 这个简化版。
两个高斯的卷积仍是一个高斯,且协方差相加: $$\mathcal{N}(\mu_1, \Sigma_1) * \mathcal{N}(0, \Sigma_h) = \mathcal{N}(\mu_1, \Sigma_1 + \Sigma_h)$$ 所以"加 0.3 到对角线"在 EWA 的眼里是"卷上一个高斯低通"。 Mip-Splatting、SA-GS、AAA-Gaussians 都靠这个公式把卷积变成 O(1) 的矩阵加法。
但 EWA 还做了一件更重要的事:它在对象空间也做了抗锯齿——保证每个 reconstruction kernel 本身不会比像素更细。3DGS 把这一步省掉了,于是闪烁问题没法在屏幕空间修复——它必须从 3D 空间下手。 这就引出了我们的第一位主角。
缩远闪烁来自 3D 频率超界,拉近浮肿来自 += 0.3f。两件事
必须分别用两个滤波器解决。
(a) 3D 平滑滤波器:用每个高斯在所有训练视图里被观察到的最高采样率
$\hat\nu_k$ 给它烙上一个 Nyquist 上限;
(b) 2D Mip 滤波器:把 += 0.3f 换成单位是像素的低通,
自然随渲染分辨率缩放。两个滤波器都是高斯卷积,开销 = 协方差对角线加一个常数。
训练时高斯被允许变得任意小——只要在训练视图上不漏渲染就行。一旦你在测试时用更低的采样率去看,这些 sub-pixel 高斯就闪烁。
诊断:没人在训练中限制过"高斯能携带的最高频率"。
+= 0.3f 是个常数,在训练分辨率上恰好工作。换到 $4\times$ 分辨率,$0.3\,\text{像素}^2$ 的低通就变得过宽,所以每个 splat 都被强行膨胀。
诊断:低通宽度应跟当前的采样率挂钩,不能是常数。
对每个高斯 $k$,遍历所有训练视图,找出它被"看得最清楚"的那一帧——即采样频率最大的视图:
$$\hat{\nu}_k = \max_n \frac{f_n}{d_n^{(k)}}\cdot \mathbb{1}[\text{visible}]$$其中 $f_n$ 是焦距,$d_n^{(k)}$ 是该高斯在视图 $n$ 中的深度。直观地说: "我在最近的那张照片里,每单位世界空间能采到 $\hat\nu_k$ 个像素"。 奈奎斯特说:这个高斯能携带的最高频率 $\le \hat\nu_k / 2$,再细就采不到了。Mip-Splatting 把这个上限烙进高斯本身:
$$\Sigma_k^{\text{reg}} = \Sigma_k + \frac{s}{\hat\nu_k^2}\,I,\quad s\approx 0.2$$因为高斯卷高斯还是高斯,这个"卷积低通"的成本就是对角矩阵加法——零运行时开销。
高斯变胖了,峰值会下降。为保持总不透明度积分不变 (总质量守恒),把每个高斯的不透明度乘以
$o' = o\cdot\sqrt{|\Sigma|/|\Sigma + \Sigma_K|}$。这就是 Mip-Splatting CUDA 里关键的一句 coef = sqrt(det_0 / det_1)。
扔掉 += 0.3f。改成接近像素方块的高斯——用一个标准差约 $\sigma_{\text{mip}}$ (像素单位) 的低通滤波器近似单像素 box filter:
关键区别:这个滤波器的宽度是固定的像素单位,会随着渲染分辨率自动缩放。 低分辨率渲染时,投影后的 $\Sigma'$ 小,滤波器相对作用大;高分辨率时反之。这恰好就是经典图形学里 mipmap 的行为。
def compute_3d_filter(gaussians_mu, cameras, s=0.2):
"""对每个高斯,找到所有训练视图中最高的采样率 ν̂"""
nu_max = torch.zeros(len(gaussians_mu))
for cam in cameras:
view_mu = cam.W @ gaussians_mu.T # (3, N)
z = view_mu[2]
visible = (z > cam.near) & in_frustum(gaussians_mu, cam)
nu = cam.focal / z # 每单位世界空间的像素数
nu_max = torch.where(visible & (nu > nu_max), nu, nu_max)
return torch.sqrt(torch.tensor(s)) / nu_max # 滤波器标准差 (世界空间)
def render_pixel(gaussian, pixel, sigma_mip=0.3, filter_3d=None):
# 3D 平滑:在协方差上加 (filter_3d)² I
Sigma_3d_eff = gaussian.Sigma + (filter_3d**2) * torch.eye(3)
Sigma_2d = J @ W @ Sigma_3d_eff @ W.T @ J.T
# 2D Mip:替代 += 0.3f,宽度以像素为单位
Sigma_2d_mip = Sigma_2d + (sigma_mip**2) * torch.eye(2)
# 不透明度补偿
coef_3d = torch.sqrt(torch.det(gaussian.Sigma) / torch.det(Sigma_3d_eff))
coef_2d = torch.sqrt(torch.det(Sigma_2d) / torch.det(Sigma_2d_mip))
opacity_eff = gaussian.o * coef_3d * coef_2d
# 点采样 alpha
d = pixel - project(gaussian.mu)
return opacity_eff * torch.exp(-0.5 * d @ torch.inverse(Sigma_2d_mip) @ d)
"max sampling rate" 是个保守上界:如果一个高斯在某一帧被很近地看过一次,它会永远保留那个高分辨率细节—— 哪怕你以后只远观它。2D Mip 仍然是高斯近似 box,不是真正的像素积分——这就是 Analytic-Splatting 接下来要解决的。
Mip-Splatting 修了"投影后的形状",但 alpha 仍然是像素中心一点的值—— 窄高斯擦过像素时点采样要么完全错过、要么完全包含。
把"点采样高斯"换成"在 1×1 像素方块上积分高斯"。 用 $\Sigma'$ 的特征分解把积分对角化为两个一维积分相乘,再用一个标定过的 logistic + 三次校正逼近高斯 CDF (最大误差 $\lt 10^{-4}$),整个 alpha 闭式可微。
原始 3DGS alpha:
$$\alpha = o\cdot \exp\!\big(-\tfrac{1}{2}\,d^\top \Sigma'^{-1}\,d\big),\quad d = p_{\text{center}}-\mu'$$但像素不是一个点,是一个 1×1 的方块。理想的 alpha 应该是高斯在这个方块上的积分:
$$\alpha_{\text{int}} = o\cdot\iint_{\text{pixel}} \exp\!\big(-\tfrac{1}{2}(x-\mu')^\top\Sigma'^{-1}(x-\mu')\big)\,dx\,dy$$第一步:对角化。2D 协方差做特征分解 $\Sigma' = R\,\mathrm{diag}(\sigma_1^2, \sigma_2^2)\,R^\top$。 把像素方块旋到主轴坐标系后,二维积分变成两个一维积分相乘:
$$\alpha_{\text{int}} \propto \big[\Phi_{\sigma_1}(\tilde u + \tfrac12) - \Phi_{\sigma_1}(\tilde u - \tfrac12)\big]\cdot\big[\Phi_{\sigma_2}(\tilde v + \tfrac12) - \Phi_{\sigma_2}(\tilde v - \tfrac12)\big]$$
第二步:CDF 的轻量近似。高斯 CDF 包含 erf,在 CUDA 里慢。Liang 等人提出一个条件化 logistic 近似:
最大绝对误差 $\lt 10^{-4}$,比朴素 logistic 准多了。
def analytic_alpha(mu_2d, Sigma_2d, opacity, pixel_center):
"""对单个像素的解析积分 alpha"""
# 1. 对 Sigma_2d 做 2x2 对称矩阵特征分解
eigvals, R = torch.linalg.eigh(Sigma_2d)
sigma1, sigma2 = torch.sqrt(eigvals) # 主轴标准差
# 2. 把像素中心变到主轴坐标系
d = pixel_center - mu_2d
u, v = R.T @ d # 旋转后的偏移
# 3. logistic CDF 近似 (条件化)
def S(x):
return 1.0 / (1.0 + torch.exp(-(1.6 * x + 0.07 * x**3)))
# 4. 二维积分 = 两个一维 CDF 差的乘积
Iu = S((u + 0.5) / sigma1) - S((u - 0.5) / sigma1)
Iv = S((v + 0.5) / sigma2) - S((v - 0.5) / sigma2)
return opacity * 2 * torch.pi * sigma1 * sigma2 * Iu * Iv
远景中几十个小高斯被压进同一个像素,既慢又锯齿。
像经典 mipmap 一样,把场景本身预存成多个 LoD 级别。每一级用体素池化聚合 sub-pixel 的小高斯,得到一个"代表性大高斯"。渲染时根据像素覆盖率自动选择 LoD。
对每个粗粒度级别 $\ell$:
使用一个相对尺度判据 (论文里 $S_T = 2$ 像素):
$$\Big(\tfrac{S_k}{S_k^{\max}}\le 1.5\Big) \;\wedge\;\Big(\tfrac{S_k}{S_k^{\min}}\ge 0.5\;\vee\;S_k\ge S_T\Big)$$Mip-Splatting 必须重训。能不能直接给已有 checkpoint 加滤波器?
在测试时给每个投影后的 splat 加一个采样率自适应的 2D 滤波器, 其宽度由当前视图的焦距和深度算出来 (而非 baked-in 训练 Nyquist);再叠 $K\times K$ 的 sub-pixel 超采样积分。
低通滤波只能"模糊"细节,不能在新尺度重新生成合适大小的高斯。
用源 3DGS 渲一张"目标尺度的伪 ground truth",然后微调高斯让它们去贴合这张图。 结果是高斯的颜色、位置、尺度都会向目标尺度漂移——而不只是被一个低通滤波器糊掉。
大视场角下,图像边缘的高斯被 Jacobian 近似拉成"针"或"香蕉"。
3DGS 用统一的 Jacobian 在图像平面做一阶 Taylor。OP3DGS 改成每个高斯有自己的切平面 ——沿高斯方向的"切球面"投影 $\varphi_p(x') = x'/x_p^\top x'$,等于每个高斯一个针孔相机, 局部 Jacobian 误差最小。
局部仿射近似只在高斯中心一点准。彻底丢掉它,行不行?
完全透视正确的光栅化 (无局部仿射) + 混合透明度 (前 N 层精确,尾部近似)。同时获得 2× 推理与 2× 训练加速。
3DGS 的 alpha 来自 Gaussian 在中心深度的一个点,物理上不一致。
在光栅化器里解析地沿射线积分高斯密度,得到正确的 transmittance, 再代入 alpha 合成。和 3DGS 同速,但物理一致——同时开箱即用于断层扫描重建。
原始 3DGS 每帧只做一次全局排序——相机一转,前后顺序翻了,颜色跳变。
对每条像素射线,计算高斯沿射线密度最大的深度: $$t_{\text{opt}} = \frac{\mathbf{d}^\top \Sigma^{-1}(\mu - \mathbf{o})}{\mathbf{d}^\top \Sigma^{-1}\mathbf{d}}$$ 然后按这个深度排序。配合三层 cascade (4×4 → 2×2 → per-pixel) 的层级排序保持实时。
t_opt 排序,颜色平滑变化。
已有方法分别解决 3D 频率、popping、广角投影;缺少一个统一管线。
三合一:(a) 动态 3D 平滑滤波器 (Mip-Splatting 升级版,实时跟随视图采样率); (b) 视图空间稳定 bounding,防止 popping; (c) 3D tile-based 剔除,层级光栅化。
原版的"跨视图平均梯度"判据下,覆盖多视图小角落的大高斯永远不会分裂——留下糊状斑块。
$\bar\nabla^{\text{pixel}} = \frac{\sum_v N_v\|\nabla_v\|}{\sum_v N_v}$ ——按高斯在每个视图中覆盖的像素数加权。 占整张图的视图说话声音大,欠重建大斑块开始正常分裂。
一个高斯收到方向相反的梯度,求和互相抵消——它误以为收敛了,留下高频细节缺失。
用同向梯度判据:$\nabla^{\text{abs}} = \sum_v |\nabla_v|$, 而不是 $|\sum_v \nabla_v|$。简单到看一眼就懂。
原版 clone/split/prune 是个一堆超参的启发式,对初始化敏感。
把每个高斯看成 MCMC 样本,SGD + 高斯噪声 = Langevin dynamics。 原来的启发式被一组确定性的"链转移规则"替代——理论清爽,初始化鲁棒。
原版 clone 出来的高斯,位置和大小都是猜的。
预训一个 iNGP 深度代理。3DGS 优化时找高误差像素,从该像素发一个视锥, 在代理深度处放一个新高斯,大小匹配视锥直径。"哪里需要、多大"一次解决。
在像素空间监督容易过/欠重建特定频段。
Loss 在傅里叶域计算振幅 + 相位差,并用一个随训练 step 提升的高通截止做 coarse-to-fine 课程学习—— $\mathcal{L}_f = w_l(d_{la} + d_{lp}) + w_h(d_{ha} + d_{hp})$。
Mip-Splatting / SA-GS 的滤波器宽度都是固定函数;过宽则失细节,过窄则失抗锯齿。
给每个高斯学习一个关于采样率的小基函数,预测最优滤波器宽度 (以及小幅外观调整)。 把"固定先验"换成"per-Gaussian 学习"。
CityGaussian 4K 渲染时,整张图都是高频抖动 + 锯齿——原版规模化方案没考虑 Nyquist。
(a) 金字塔多尺度监督——loss 是渲染图与高斯金字塔 GT 的对比,天然多尺度自洽; (b) 物理下界——强制每个高斯尺度 $\ge$ Nyquist,禁止 sub-pixel 退化高斯。
3D 椭球的"厚度"会让屏幕投影随视角变形——既损害几何,也产生 view-dependent 锯齿。
用2D 圆盘替代椭球,每个 primitive 有切平面 $(\mathbf{t}_u,\mathbf{t}_v)$ + 尺度 $(s_u, s_v)$。 光栅化时解析地求射线-圆盘交,跳过 Jacobian——天然透视正确。 加上 depth-distortion + normal-consistency 两个正则。
2DGS 没有 Jacobian,所以 Mip-Splatting 的两个滤波器都没法直接搬过去。
(a) 世界空间扁平平滑核:在圆盘自己的切平面里卷一个 2D 高斯; (b) 物体空间 Mip 滤波:用射线-圆盘相交的局部仿射 Jacobian 算每个 splat 的像素 footprint, 在圆盘局部坐标系做 mip。
高斯的软边在很多场景下是缺点——除非用大量窄高斯堆出来,否则总是糊的 (Gibbs 现象,高斯频谱物理限制)。 下面这些工作换掉了 primitive 本身:
| 方法 | 核函数 | 关键性质 |
|---|---|---|
| GES (CVPR 24) | $\exp(-|x|^\beta/\sigma^\beta)$, $\beta$ 学习 | $\beta \lt 2$ 更锐, $\beta \gt 2$ 更平。内存减半,FPS +39%。 |
| 3D-HGS (CVPR 25) | 切平面切的"半高斯" | 硬边复制能力强。 |
| 3D Convex Splatting (CVPR 25 HL) | 光滑凸体 | 平面/棱角友好;primitive 大幅减少。 |
| DRK (CVPR 25) | 极坐标可学习径向核 | 拐角、各向异性都来者不拒。 |
| Deformable Beta (SIGGRAPH 25) | Beta 分布 | 有界支撑,无长尾。 |
| Universal Beta (2025/26) | N 维 Beta | 空间/角度/时间统一。 |
| 3D Gabor (EG 25) | 高斯 × 振荡正弦 | 单个 primitive 携带条纹纹理。 |
| Student Splat & Scoop (CVPR 25) | Student-t + 负密度 | 重尾 + 锐边减法。 |
| BBSplat (ICCV 25) | 带 RGB 纹理的 billboard | 1200+ FPS at SOTA quality。 |
这些工作不是直接打抗锯齿,但它们改变了"primitive 表达力",间接缓解了由过度致密化引发的尺度不匹配锯齿。
4D-GS 在变速渲染下高频闪烁——动态场景没人推 Nyquist。
把 Mip-Splatting 的最大采样频率分析推广到 4D 时空,加上 4D 自适应滤波器和尺度损失。
DepthSplat / MVSplat 这类 feed-forward 3DGS 输出无可控 Nyquist——非训练分辨率下崩溃。
Opacity-Balanced Band-Limiting (OBBL)——一个 3D 带限后处理滤波器 接在 feed-forward 输出上,杀掉退化高斯;同时 rescale alpha 以保持合成正确。
SLAM 系统的 mapper 跨变化的相机分辨率时会抖动。
Elliptical Adaptive AA 光栅化 (比 Analytic-Splatting 更轻量的几何积分) + Spectral-Aware Pose-Graph 优化 (图拉普拉斯滤掉高频位姿漂移)。
alpha 合成里的 alpha 和 transmittance 都是标量——但物理上它们应该在像素内部空间变化。
把 alpha 和 transmittance 换成像素内部的分布。 前景 splat 只覆盖部分像素时,背景 splat 仍部分可见——同时治理缩远和拉近锯齿。零额外内存。
| 方法 | 年份/场合 | arXiv | 核心招数 | 解决的锯齿类型 |
|---|