CT重建(二) | 3D高斯泼溅:实时渲染的革命

3D Gaussian Splatting 项目详解

仓库:graphdeco-inria/gaussian-splatting(Inria GRAPHDECO 官方实现)

论文:3D Gaussian Splatting for Real-Time Radiance Field Rendering (SIGGRAPH 2023, ACM TOG)


目录

  1. 项目介绍
  2. 它解决了什么问题
  3. [NeRF 是怎么做的](#NeRF 是怎么做的)
  4. 输入:从多视角照片到相机与点云
  5. [COLMAP / SfM 是什么](#COLMAP / SfM 是什么)
  6. [模型表示:3D 高斯是什么](#模型表示:3D 高斯是什么)
  7. [训练过程:渲染 → 比对 → 反传](#训练过程:渲染 → 比对 → 反传)
  8. 密度控制:哪里放高斯,哪里不放
  9. [渲染核心:splat / tile / 排序 / alpha 混合 / 可微光栅化](#渲染核心:splat / tile / 排序 / alpha 混合 / 可微光栅化)
  10. 为什么方向相反还能重建
  11. 为什么又快又好
  12. 输出与推理
  13. 代码地图与关键参数
  14. 学习路径
  15. [NeRF 与 3DGS 的技术栈对比](#NeRF 与 3DGS 的技术栈对比)

1. 项目介绍

这是论文 3D Gaussian Splatting(3DGS) 的官方实现。

一句话:输入一批从不同角度拍摄的照片,重建出一个可以实时渲染任意新视角的三维场景。

它的效果直观理解是:训练完成后,你可以像玩 3D 游戏一样,在场景里自由移动视角,实时看到画面。

整个流程只有四步:

  1. 拍照片(多视角照片)
  2. 用 COLMAP / SfM 做相机标定,得到相机参数和稀疏点云
  3. 用这些点云初始化一堆 3D 高斯
  4. 训练优化这些高斯,最后输出一个 .ply 模型文件

2. 它解决了什么问题

在 3DGS 出现之前,主流是新视角合成(Novel View Synthesis):

  • NeRF / Mip-NeRF360 :画质高,但极慢(1080p 通常不到 1 fps,训练要几十小时)
  • Plenoxels / InstantNGP :更快,但画质有损失,且在无界大场景 + 1080p下仍做不到实时

核心矛盾:「快」和「好」长期不可兼得。

论文原话:对于无界完整场景(而不是孤立物体)的 1080p 渲染,之前没有任何方法能实时。

3DGS 的目标就是同时拿到高画质 + 实时渲染 + 较短训练时间。

而它达成的关键,是三个设计(后面各节逐一展开):

  1. 用 3D 高斯做显式场景表示(而不是神经网络)
  2. 交错的优化 / 密度控制(自适应增删高斯)
  3. 可见性感知的快速可微光栅化(替代逐光线体渲染积分)

3. NeRF 是怎么做的

要理解 3DGS 的取舍,必须先把 NeRF 讲透。这一节从头讲一遍 NeRF 的完整原理。

3.1 核心思想:用一个函数表示整个场景

NeRF(Neural Radiance Field,神经辐射场)的核心假设是:

整个三维场景,可以用一个连续的数学函数来描述。

这个函数是:

复制代码
F(x, y, z, θ, φ) → (σ, r, g, b)
  • 输入 :空间中一个 3D 点的坐标 (x, y, z),加上一个观察方向 (θ, φ)(单位方向向量 d)
  • 输出 :
    • σ:该点的体积密度(有多不透光)
    • (r, g, b):该点朝这个方向看过去的颜色

NeRF 用一个 MLP(多层感知机) 来拟合这个函数。训练完成后,网络权重里就「装下了」整个场景。

关键点 :这是一个隐式表示------场景没有显式的几何形状,你无法直接从网络里拿出来一个「物体」,只能通过查询函数得到某个位置的信息。

3.2 渲染:从像素发射光线

渲染一张图时,NeRF 对每个像素做下面这套流程:

① 生成光线

用相机内外参,从像素 (u, v) 和相机光心 o 出发,确定一条光线:

复制代码
r(t) = o + t · d

其中 d 是归一化的射线方向,t 是沿光线的距离参数(近端 t_near 到远端 t_far)。

② 沿光线采样

在 [t_near, t_far] 之间取 N 个采样点 t₁, t₂, ..., t_N。

  • 原始 NeRF 用 粗网络 + 细网络 两级采样:先均匀粗采样,再根据粗网络的密度分布在「可能有物体」的地方细采样(重要性采样)
  • InstantNGP 等后续工作改用哈希网格加速查询,但思路一样:还是要沿光线取很多点

③ 位置编码(Positional Encoding)

直接把 (x, y, z) 喂给 MLP 会导致网络偏向学习低频(模糊)结果。所以 NeRF 先把坐标映射到高维:

复制代码
γ(p) = (sin(2⁰πp), cos(2⁰πp), sin(2¹πp), cos(2¹πp), ..., sin(2^{L-1}πp), cos(2^{L-1}πp))

这一招让 MLP 能表达高频细节(纹理、边缘)。位置用 L=10,方向用 L=4。

④ MLP 前向

复制代码
F_Θ( γ(x), γ(d) ) → (σ, c)
  • 网络通常是 8 层全连接 + ReLU,宽度 256
  • σ 只依赖位置 (γ(x)),因为密度是几何属性,与观察方向无关
  • c 依赖位置 + 方向(把方向拼接到中间层特征上),这样能表达镜面反射等视角相关效果

⑤ 体渲染积分

得到沿光线的 N 个 (σᵢ, cᵢ) 后,用经典的体渲染公式合成像素颜色:

复制代码
C = Σᵢ Tᵢ · (1 - exp(-σᵢ δᵢ)) · cᵢ
Tᵢ = exp( - Σ_{j<i} σⱼ δⱼ )

其中 δᵢ 是相邻采样点的间距。

直觉解读:

  • αᵢ = 1 - exp(-σᵢ δᵢ) 是第 i 个点的不透明度(密度越高、间距越大越不透明)
  • Tᵢ 是透光率 :光从相机走到第 i 个点,还没被前面挡掉的比例
  • 所以 C 就是「每个点贡献自己的颜色 × 自己的不透明度 × 前面剩下的透光率」的加权和

这和 3DGS 的 alpha 混合公式本质完全一样,只是 NeRF 的点是沿光线采样出来的,3DGS 的点是空间中的高斯。

⑥ 计算损失并反传

复制代码
L = Σ_pixels ‖ C_render - C_gt ‖²

梯度反传回 MLP 权重,更新网络。因为整套流程可微,所以能端到端训练。

3.3 为什么 NeRF 慢

把上面的流程算一下成本:

  • 一张 1080p 图像 ≈ 200 万像素
  • 每个像素一条光线
  • 每条光线采 N 个点(原始 NeRF 是 64 粗 + 128 细 = 192 个点)
  • 每个点都要跑一次完整的 MLP 前向(8 层 × 256 维)

结果:渲染一张图需要约 4 亿次网络前向的量级。这就是它慢的根本原因------不是实现不好,而是「逐像素 × 逐光线 × 逐点查网络」这个结构本身的计算量太大。

而且还有个浪费:光线穿过空白区域(天空、空气)时,照样要采样、照样要查网络,但这些点对最终颜色几乎没有贡献。


4. 3d高斯泼溅

输入:从多视角照片到相机与点云

训练需要三类输入,它们分别负责不同的事情:

数据 回答的问题 在 3DGS 中的作用
外参 (R, T) 相机在哪、朝哪 决定「从哪个视角渲染」
内参 (fx, fy, W, H) 如何投影成像 决定「3D 点落到哪个像素」
稀疏点云 场景大概长什么样 决定「高斯从哪里开始、初始多大」

为什么会需要这三样?

因为 3DGS 的训练本质是一个循环:

用相机参数把 3D 高斯投影成 2D 图像 → 和真实照片比对 → 反传修正高斯

  • 没有外参 → 不知道该渲染哪个视角,损失无从计算
  • 没有内参 → 不知道 3D 点该落在哪个像素,渲染尺寸对不上
  • 没有点云 → 只能随机初始化,优化更难收敛

4.1 内参与外参

用针孔相机模型来理解:

投影公式:

复制代码
像素坐标 p = K · [R | T] · 世界点 P
  • 外参 [R | T] :R 旋转 + T 平移,描述相机在世界坐标系中的位姿。代码里 getWorld2View2(utils/graphics_utils.py:38)把世界坐标转到相机坐标。
  • 内参 K :焦距 fx, fy 和图像宽高 W, H。代码把它们换算成视场角(utils/graphics_utils.py:73):
    • focal2fov(focal, pixels) = 2 · atan(pixels / (2 · focal))
    • 焦距越大 → 视场越窄 → 物体投影越大

注意 :3DGS 只支持 PINHOLE / SIMPLE_PINHOLE 这两种无畸变 的相机模型(scene/dataset_readers.py:88-97)。带畸变的模型(RADIAL、OPENCV 等)会直接报错,必须先让 COLMAP 去畸变。

4.2 稀疏点云

COLMAP 输出的 points3D 每个点只有 XYZ 坐标 + RGB 颜色 ,它不是高斯的全部参数。

剩下这些参数是 3DGS 初始化时构造出来的(scene/gaussian_model.py:119 create_from_pcd):

高斯参数 初值来源
位置 _xyz 直接用点的 XYZ
颜色 点 RGB 转成球谐的 DC 项(RGB2SH)
尺度 _scaling 到最近邻点的距离(distCUDA2),点密则小、点疏则大
旋转 _rotation 单位四元数(无旋转)
不透明度 _opacity 固定初值 0.1
协方差 Σ 由尺度和旋转算出,不是直接给的

所以准确说法是:COLMAP 点云提供高斯的「出生位置和基础颜色」,其余参数是构造出来的,再靠训练优化。


5. COLMAP / SfM 是什么

COLMAP 是一个开源的摄影测量 / Structure-from-Motion(SfM)工具。给它一堆从不同角度拍同一场景的照片,它反推出:

  • 每张照片拍摄时相机在哪、朝哪(外参)
  • 相机的焦距 / 成像特性(内参)
  • 场景中一批稀疏的三维点(点云)

这正是 3DGS 需要的全部输入。

5.1 名字的含义

  • Structure(结构):场景的三维结构
  • Motion(运动):拍摄时相机的运动轨迹
  • from Motion:从相机运动中恢复出三维结构

5.2 四个步骤

  1. 特征提取:在每张照片里找出特征点(角点、纹理丰富的区域)。纯色墙、天空提不出特征,这也解释了为什么这些地方后来没有高斯。
  2. 特征匹配:找出「照片 A 的哪个点和照片 B 的哪个点是同一个三维点」。
  3. 稀疏重建:用三角测量算出三维点坐标,用光束法平差(Bundle Adjustment)联合优化相机位姿和三维点。
  4. 稠密重建(MVS) :可选,3DGS 不需要这一步,它只用稀疏点云。

5.3 输出目录结构

复制代码
<scene>/
  images/                    # 去畸变后的照片
  sparse/0/
    cameras.bin   -> 内参(焦距、分辨率、相机模型)
    images.bin    -> 外参(每个相机的 R 旋转、T 平移)
    points3D.bin  -> 稀疏三维点(XYZ + RGB),高斯的初始位置

对应代码:scene/dataset_readers.py:144 读取。

5.4 验收标准

判断 COLMAP 跑得好不好:

  • 注册的相机数量:是否几乎覆盖所有照片
  • 重投影误差:一般应 < 1 像素
  • 点云密度和分布:是否覆盖场景主要结构

6. 模型表示:3D 高斯是什么

6.1 先理解 NeRF 在做什么

NeRF 的网络输入是空间中任意一个 3D 点的坐标 + 一个观察方向:

复制代码
F(x, y, z, θ, φ) → (密度 σ, 颜色 c)

也就是:给它空间中任意一点,它告诉你「这里有多不透光、朝某方向看是什么颜色」。

注意两个常见误解:

  • ❌ NeRF 的输入不是「带位置的图片」,也不是 2D 像素
  • ❌ NeRF 的输出不是直接的 2D 像素颜色

渲染时是从像素发射一条光线,在光线上采样很多 3D 点,逐点查网络,再积分算出像素颜色。

慢的根本原因:每个像素 → 发一条光线 → 沿光线采样几十上百个点 → 每个点跑一次 MLP 前向 → 再做体渲染积分。1080p 有 200 万像素,量级巨大。

可以用一个类比:想象空间里弥漫着一层半透明的彩色雾,每个位置的浓淡(σ)和颜色(c)由网络定义。NeRF 学的是这层「雾」的函数。

6.2 3D 高斯是什么

3DGS 不再用「连续函数 + 逐点采样」,而是直接在空间里放很多个半透明的椭球 (高斯球)。可以想成空气中悬浮的、可长可扁、可调朝向的彩色糖豆。

每个高斯的属性(scene/gaussian_model.py):

  • 位置 μ:中心在哪 (x, y, z)

  • 协方差 Σ:形状。由缩放 S 和旋转 R 决定:

    Σ = R S Sᵀ Rᵀ

  • 缩放让它能变椭球、能各向异性拉长

  • 旋转让椭球朝向任意方向------这是它能贴合薄面、细结构的关键

  • 不透明度 α:这颗糖豆有多「实」,介于 0~1

  • 颜色:用球谐系数(SH)表示,能随视角变化出不同颜色(镜面反光等)

6.3 高斯为什么是「连续」的

单个 3D 高斯本身就是一个连续函数:

复制代码
G(x) = exp( -½ (x - μ)ᵀ Σ⁻¹ (x - μ) )

离中心越近值越大,向外平滑衰减。

关键点 :所以 3DGS 依然是连续的、可微的表示,不是离散体素------这正是它能保留 NeRF 那种「可微体渲染、能优化」优点的原因。

区别只在计算方式:

  • NeRF 沿光线密集采样、逐点查网络
  • 3DGS 把每个高斯直接投影到屏幕变成 2D 椭圆,只对覆盖到的像素加权混合

6.4 3D 高斯与 NeRF 的关系

两者描述的是同一件事------空间的密度和颜色场------只是形式不同:

NeRF 3DGS
形式 隐式:存在网络权重里 显式:一颗颗高斯叠加
查询 必须过一遍 MLP 直接读参数
元素操作 无法直接查看/编辑 可逐个增删、移动

某个位置的「密度」≈ 该点落进各个高斯内部的概率之和;某方向的「颜色」≈ 各高斯加权平均。


7. 训练过程:渲染 → 比对 → 反传

主循环在 train.py:80,每次迭代:

  1. 随机抽一张训练相机 (train.py:96)

  2. 渲染 :用 render() 把当前高斯投到该视角,得到渲染图、可见性掩码、2D 半径、深度

  3. 算损失 (train.py:122):

    L = (1 - λ) · L1 + λ · (1 - SSIM)

默认 λ = 0.2。直觉:L1 保证像素级准确,SSIM 保证结构相似。

如果启用深度正则化,再加一项逆深度 L1。

  1. 反传 :loss.backward(),用 Adam 更新所有高斯参数
  2. 密度控制:增删高斯(见下一节)
  3. 位置的学习率按指数调度逐步衰减(get_expon_lr_func)

另外,球谐阶数每 1000 步升一级(gaussian_model.py:113 oneupSHdegree),逐步提升对视角相关外观的表达能力。


8. 密度控制:哪里放高斯,哪里不放

短答:不是所有区域都铺高斯。

高斯只出现在有内容的地方,靠训练中动态增删实现。

8.1 初始化已经在筛了

create_from_pcd 只在 COLMAP 的稀疏点上创建高斯,而这些点本身只存在于有纹理、可匹配的地方。天空、纯色墙、玻璃这类无特征区域,COLMAP 根本不出点,初始就没有高斯。

(如果是 Blender/NeRF 合成数据没有点云,程序会随机撒 10 万个点,dataset_readers.py:296------这是被迫「全区域铺」,效果通常更差。)

8.2 训练中的增删规则

核心是 densify_and_prune(gaussian_model.py:392),在 densify_from_iter(默认 500 步)到 densify_until_iter(默认 15000 步)之间,每 100 步执行一次。

加(克隆 / 分裂) ------判据是屏幕空间位置梯度,梯度大说明「这里没拟合好」:

  • 梯度 ≥ densify_grad_threshold(默认 0.0002)→ 候选
  • 候选里,高斯小 → 克隆(就地复制一份),增加局部密度
  • 候选里,高斯大 → 分裂 (拆成 2 个更小的),提高分辨率
    • 「大/小」的界:max(scaling) > percent_dense · scene_extent(默认 percent_dense = 0.01)

减(剪除)------三种都没用了就删:

  • 不透明度太低(< 0.005):几乎透明的废高斯
  • 屏幕投影过大(> 20 像素):横跨半张图,通常是错误的背景解
  • 世界尺度过大(> 0.1 · scene_extent):占满场景的超大高斯

不透明度重置 :每 opacity_reset_interval(默认 3000 步)把所有高斯不透明度压到 ≤0.01,让「滥竽充数」的高斯在下一轮被剪掉,同时给新位置腾出生长空间。这是防止高斯只增不减的关键机制。

8.3 结果

高斯数量从初始几千个 SfM 点,逐步长到几百万个,全部集中在有纹理、有细节、重建误差大的地方(树叶、栏杆、细纹理),而平坦区域、空旷区域高斯很稀疏甚至没有。

这就是它比 NeRF 均匀采样更高效的原因------自适应地匹配场景的复杂度分布。


9. 渲染核心:splat / tile / 排序 / alpha 混合 / 可微光栅化

这是整个方案最核心、也最「工程」的部分(submodules/diff-gaussian-rasterization)。用「往屏幕上撒彩色糖豆」的直觉来理解。

① 2D splat(把 3D 高斯投到屏幕)

  • 每个 3D 高斯是空间中的椭球,splat 就是把它投影到屏幕------3D 椭球投下来还是 2D 椭圆。
  • 投影用相机的内外参:外参决定从哪看,内参决定投到屏幕哪里、多大。
  • 投影后得到:屏幕上的中心位置、2D 椭圆形状、深度、颜色、不透明度。
  • 和 NeRF 最大的区别:NeRF 是「从像素射光线出去找点」,3DGS 是「从点投到像素上来」。方向反了,这是快的第一层原因。

② tile 划分(把屏幕切成小块)

  • 屏幕被切成很多个固定大小的正方形 tile(如 16×16 像素)。
  • 每个 splat 只登记到它覆盖的 tile 上。
  • 空区域没有任何高斯覆盖,对应 tile 完全零计算。
  • 同时一个 tile 内像素可并行处理,内存访问局部化。

③ 深度排序(保证前后遮挡关系对)

  • 同一个 tile 上会有很多 splat 重叠,必须按深度从近到远排序再混合。
  • 为什么必须排序 :alpha 混合不可交换 。顺序错了,近的物体就不能正确遮住远的,颜色会算错。
    • 例:近的红色不透明 1.0,远的蓝色不透明 1.0。正确顺序(近→远)结果 = 红色;顺序反了会得到错误结果。
  • 排序本身是为了正确性,不是可选项。

④ alpha 混合(怎么合成一个像素颜色)

从前到后依次混合,公式和体渲染一致:

复制代码
C = Σᵢ cᵢ · αᵢ · Π_{j<i}(1 - αⱼ)

直觉:每个高斯贡献自己的颜色 c,权重 = 自己的不透明度 α ×「被前面所有高斯挡住后还剩多少透光」。

早停(early termination) :当累积不透明度 Σα 接近 1(后面彻底看不见了),剩下的高斯直接跳过。

注意纠正一个常见误解:

  • ❌ 不是「先做前面的,后面的就不做了」
  • ✅ 是「按顺序累积,完全不透明后才停」
  • 部分遮挡的高斯仍会按不透明度贡献颜色,只是贡献很小

⑤ 可微光栅化(为什么能训练)

  • 普通图形学光栅化只求「画得快」,不可导,不能训练。
  • 这里每一步(投影、混合公式)都写成对高斯参数可微 的形式。渲染图和真实照片算损失后,梯度能反传回每一个高斯的位置、协方差、不透明度、球谐颜色。
  • 细节:为了让反向传播高效,作者把梯度计算放在 CUDA kernel 里手写实现,而不是靠 PyTorch 自动搭图。这既是它快、也是它能训练的原因。

全流程串联

复制代码
3D 高斯集合
   │  ① 用相机内外参投影(splat)
   ▼
屏幕上的 2D 椭圆
   │  ② 登记到它覆盖的 tile
   ▼
每个 tile 一份高斯列表
   │  ③ 按深度排序
   ▼
有序列表
   │  ④ 从前到后 alpha 混合(可早停)
   ▼
渲染图像
   │  ⑤ 和原图比 loss,梯度反传回高斯参数
   ▼
更新位置/协方差/不透明度/颜色 → 下一轮

10. 为什么方向相反还能重建

这是一个很本质的问题。

物理上只有一个方向:光从场景到相机。

复制代码
场景中的点 → 发出/反射光 → 沿直线传播 → 穿过小孔 → 打在传感器某个像素上

像素是「果」,场景点是「因」。

  • 光栅化(3DGS)顺着物理方向走:拿着场景点,正向投影,算出它落在哪个像素。
  • 光线追踪 / NeRF 逆着物理方向走:从像素反向发射光线,回溯它「当初是从场景哪个点来的」。

所以「方向相反」不是两件不同的事,而是同一条直线,从两端各看一眼。

数学上是一对手性相反的变换:

  • 正向(3DGS):世界点 → 像素。确定性投影。
  • 反向(NeRF):像素 → 射线。深度未知,所以才要沿光线采样很多点,靠体渲染积分「猜」深度。

重建能成立的核心:多视角约束 + 可微优化。

  • 同一个真实 3D 点,在多个视角的投影必须一致。
  • 3DGS 直接假设 场景由一堆高斯构成,投到某视角和照片比对,哪里不对就调整高斯。
  • 训练时随机换不同视角,每个视角都是对「同一批高斯」的一个约束。几十上百个视角一起约束,高斯就被逐步逼到正确解。

就像从不同角度给一个物体拍照,虽然每张都是 2D,但把多张放一起,形状就被「夹」出来了。

一句话:方向只决定计算路径,不决定能否重建;重建依据的是「多视角一致性 + 可微优化」。


11. 为什么又快又好

快的原因

  1. 表示显式:没有网络前向,直接读高斯参数
  2. 只算有内容的地方:空区域没有高斯,零开销(NeRF 在空空间照样采样)
  3. 光栅化 + tile 并行:GPU 天然适合,比串行逐光线高效
  4. 早停:完全遮挡后跳过剩余高斯
  5. CUDA 手写 kernel:前向反向都高度优化

好的原因

  1. 避免空区域伪影:只在高斯存在处渲染,边界更锐利
  2. 能表达高频细节:自适应增密 + 各向异性拉长,细结构表达力强
  3. 保留体渲染优点:alpha 混合等价于体渲染离散化,可微、可优化
  4. 无界完整场景:第一次在 1080p 无界场景下做到画质领先且实时

权衡(诚实说明)

  • 显存占用大:几百万个高斯,训练需 24GB 级别显存
  • 显式表示:文件体积比 NeRF 的 checkpoint 大,但换来可直接光栅化、易编辑(剪裁、变形、组合)
  • 依赖初始点云质量:SfM 失败的区域难以补全(无纹理区域容易空洞或模糊)

12. 输出与推理

训练完成后,最重要的产物:

  • output/<id>/point_cloud/iteration_XXX/point_cloud.ply :3D 高斯模型。PLY 里每个点存位置、SH 颜色系数、不透明度、缩放、旋转(gaussian_model.py:232)
  • cameras.json:相机参数记录
  • input.ply:原始稀疏点云副本
  • exposure.json:每张图的曝光参数(若启用)
  • chkpntXXX.pth:训练检查点,可续训
  • TensorBoard 日志(loss、PSNR 等)

推理 / 渲染:

  • render.py 加载训练好的 point_cloud.ply,对任意新视角实时渲染
  • metrics.py 计算 PSNR / SSIM / LPIPS
  • SIBR_viewers/ 提供 OpenGL 实时交互查看器

标准命令

shell 复制代码
# 训练
python train.py -s <path to COLMAP dataset>

# 训练(带评测划分)
python train.py -s <path> --eval

# 渲染
python render.py -m <path to trained model>

# 计算指标
python metrics.py -m <path to trained model>

13. 代码地图与关键参数

代码地图

文件 / 目录 作用
train.py 训练主循环
render.py 训练后渲染
metrics.py / full_eval.py 评测指标
convert.py 把自定义图片转成 COLMAP 数据集
scene/gaussian_model.py 高斯模型定义、优化、密度控制、存取
scene/dataset_readers.py 数据集读取(COLMAP / Blender)
scene/__init__.py 场景组装(相机 + 点云 + 高斯)
gaussian_renderer/ 渲染封装(调用 CUDA 光栅化器)
submodules/diff-gaussian-rasterization 可微光栅化 CUDA 核心
submodules/simple-knn 最近邻距离(初始化尺度用)
SIBR_viewers/ OpenGL 实时查看器
arguments/ 命令行参数定义

关键参数(默认值)

参数 默认 含义
--iterations 30000 总训练步数
--sh_degree 3 球谐阶数(≤3)
--lambda_dssim 0.2 loss 中 SSIM 的权重
--densify_from_iter 500 开始增密
--densify_until_iter 15000 停止增密
--densify_grad_threshold 0.0002 增密梯度阈值
--densification_interval 100 增密间隔
--opacity_reset_interval 3000 不透明度重置间隔
--percent_dense 0.01 「大高斯」判定比例
--position_lr_init 0.00016 位置初始学习率
--position_lr_final 0.0000016 位置最终学习率

14. NeRF 与 3DGS 的技术栈对比

这一节把两者的每一层技术选型摊开来对比,你能清楚看到 3DGS 是「在哪一层做了什么替换」才换来快和好。

15.1 分层对比表

层次 NeRF 3D Gaussian Splatting
场景表示 MLP 隐式函数 显式高斯集合
几何原语 无(靠密度场隐式表达) 各向异性椭球
外观表示 MLP 输出颜色 球谐系数(SH)
初始状态 随机初始化网络权重 SfM 稀疏点云
渲染原语 光线(ray) 高斯(splat)
采样方式 沿光线密集采样 不需要采样(直接在点上算)
渲染算法 光线步进 + 体积积分 光栅化 + alpha 混合
计算方向 像素 → 场景(反向) 场景 → 像素(正向)
加速结构 粗/细采样、哈希网格 tile 划分、深度排序、早停
可微机制 框架自动求导 手写 CUDA 可微 kernel
并行粒度 按光线 / 按像素 按 tile / 按高斯
几何更新 权重隐式变化 显式克隆/分裂/剪除
参数量级 网络权重(约 MB) 几百万高斯(约百 MB ~ GB)

15.2 逐层解读

① 表示层:隐式 vs 显式

  • NeRF 把场景编码进 MLP 权重。优点是紧凑、连续、参数少;缺点是每次查询都要算一遍网络,且无法直接操作几何。
  • 3DGS 把场景摊开成一堆高斯。优点是查询是 O(1) 的常数操作(直接读参数),且几何可直接增删编辑;缺点是存储大、需要显存。

② 几何层:连续密度场 vs 各向异性几何原语

  • NeRF 没有「几何」这个概念,几何是从密度场里「浮现」出来的。高密度区域就是物体,但边界模糊,且表面靠等值面提取。
  • 3DGS 每个高斯自带各向异性协方差 (RSSᵀRᵀ),可以拉成任意朝向的椭球,因此能精确贴合薄平面、细杆这类结构。

③ 采样层:密集采样 vs 无需采样(这是快的关键差异)

  • NeRF:一条光线必须采几十~上百个点,每个点都是一次网络前向。
  • 3DGS:高斯本身就是「点」,投影后直接混合,没有采样这一步。
  • 这是两者计算量差距最大的地方。NeRF 的成本是 像素 × 采样点数 × 网络深度;3DGS 的成本是 高斯数 × 覆盖像素数(且有空区域剪枝和早停)。

④ 渲染层:光线步进积分 vs 光栅化混合

  • 两者的数学本质一样 (都是 alpha 合成),差别在计算组织方式 :
    • NeRF 沿光线串行累积(虽然能按光线并行)
    • 3DGS 把高斯投影到屏幕,切成 tile,按 tile 并行、按深度排序混合
  • 后者更贴合 GPU 的并行架构,且天然支持早停。

⑤ 加速层

  • NeRF 系:InstantNGP 用哈希网格 + 多分辨率特征缓存,Mip-NeRF 360 用提议网络(proposal network)做重要性采样,都是为了减少无效采样。
  • 3DGS:tile 划分减少无效高斯处理,深度排序保证正确性的同时允许早停,空区域天然零成本。

⑥ 可微机制层(容易被忽略但很关键)

  • NeRF:整个前向是「光线采样 → 逐点 MLP → 积分」,用 PyTorch 自动求导就能反传,实现相对简单,但显存开销大、速度受限。
  • 3DGS:光栅化的前向和反向都在 CUDA kernel 里手写 (diff-gaussian-rasterization),包括对协方差、球谐的梯度。这让它既快又省显存,但实现复杂度高得多。

⑦ 几何自适应层

  • NeRF:网络容量固定。想让细节更好,只能加大网络或提高采样密度,无法「局部加密」。
  • 3DGS:训练中按梯度自适应地克隆/分裂/剪除,计算力自动集中到需要的区域,这是它画质能超过 NeRF 的重要原因之一。

总结

3D Gaussian Splatting 并不是对 NeRF 的简单「加速版」,而是一次表示层、渲染层与优化层的整体重构。

它把 NeRF 中「隐式网络 + 逐光线密集采样 + 体渲染积分」这条计算昂贵的路径,替换成了「显式高斯集合 + 正向光栅化 + 可微 alpha 混合」的高效路径。数学本质仍是体渲染,但工程组织方式彻底改变,从而第一次在无界完整场景、1080p 分辨率下同时做到了高画质、实时渲染与可接受的训练时间。

回过头看,3DGS 的成功可以归结为三个关键选择:

  • 表示显式化:用几百万个各向异性 3D 高斯代替 MLP,让查询从「跑一次网络」变成「直接读参数」;
  • 优化自适应:通过克隆、分裂、剪除与不透明度重置,让计算力自动集中到有纹理、有细节、重建误差大的区域;
  • 渲染可微化:把光栅化的前向与反向都写进 CUDA kernel,既保证速度,又让梯度能反传回每一个高斯的位置、形状、不透明度与颜色。

当然,它也有代价:显存占用大、模型文件体积大、依赖 SfM 初始点云质量,无纹理区域仍容易出现空洞或模糊。但这些问题恰恰构成了后续研究的方向------压缩、稀疏化、鲁棒初始化、动态场景扩展、与网格/神经场的混合表示等。

如果说 NeRF 教会了我们「用一个函数描述整个场景」,那么 3DGS 则证明了:当表示足够显式、渲染足够贴合硬件、优化足够自适应时,实时高保真三维重建可以从理想走进现实。

相关推荐
打工仔折腾 AI1 小时前
从零写一个CAD 04:中键拖动平移,抓住一个点让它一直待在鼠标底下
人工智能·后端·python·性能优化·计算机外设·ai agent 实战
workflower1 小时前
从生成式模型开始的技术延伸
人工智能·机器学习·机器人·云计算·无人机
kali-Myon1 小时前
ARTEX:AI 驱动的自动化漏洞挖掘平台
人工智能·安全·ai·自动化·web
论文复现现场1 小时前
RTX 3090 24GB 适合科研吗?单卡论文复现、PyTorch 显存检查与 OOM 排错
人工智能·pytorch·python·深度学习·cuda·rtx3090
7yewh1 小时前
SLAM 相机与图像(4)
人工智能·数码相机·计算机视觉
李游Leo1 小时前
HarmonyOS 7 + Spatial Recon Kit-C++:3DGS 输入帧内参与位姿的版本一致性门禁【鸿蒙心迹】
c++·3d·harmonyos
正经教主1 小时前
【FDE系列】阶段3:Day 71:高级检索 — 查询改写与 HyDE
人工智能·rag·fde
天空之城--1 小时前
Android一周动态:Android 18首次官宣、Compose Material3 1.4转正(5趋势+5资讯)
android·人工智能·flutter·架构·android jetpack