0. 计算机图形学
0.1 坐标变换
- 通过左乘一个4*4矩阵表达对3D空间点的平移、旋转,缩放
0.1.1 平移
- 平移矩阵相当于单位矩阵加上一个平移向量:
- 举例:将点[1,0,0,1]沿着x周平移1个单位:
0.1.2 旋转
https://blog.csdn.net/csxiaoshui/article/details/65446125
0.1.3 缩放
- 单位矩阵在各个轴乘以缩放系数
- 视锥体长方体 [l,r]×[b,t]×[n,f][l,r]×[b,t]×[n,f] 线性映射到 NDC 立方体的纯缩放部分
0.2 视图变换
- 拍照的过程:
- 模型变换:先把场景搭好(得到世界坐标系)
- 视图变换:把世界坐标变成「以相机为参考」(得到相机坐标系)
- 投影变换:把相机坐标压到裁剪体/屏幕
- 已知相机在世界坐标系里「站在哪、朝哪看、头朝哪」,怎么把所有 3D 点变到「以相机为原点、朝 -z 看、头顶是 +y」的标准相机坐标系?
0.2.1 视图变换流程
1. 定义相机坐标系
-
如何才能确定一个相机的摆放?
- 首先,相机的位置很重要,用位置向量
- 其次,往哪拍也很重要,用:
- look-at向量(相机往哪看):
- 向上向量(相机头顶的朝向):
- 首先,相机的位置很重要,用位置向量
-
默认相机在原点,
是-z轴, 是y轴,右手坐标系
2. world to camera
- 核心目的:将整个场景内的物体和相机做同样的一个变换,变换之后相机成了坐标系中心。这就是视图变换
-
将相机平移到原点,平移矩阵:
-
旋转相机,以对齐三轴:将
旋转到-z轴,将 旋转到y轴, 旋转到x轴- 直接将
旋转到 -z轴不太好操作,旋转矩阵不太好找,可以逆向考虑- 假设我要将x轴旋转到(a,b,c)这个单位向量:
- 同理,如果你要将y轴,z轴旋转到(a,b,c)只需要第二列第三列是abc就行,那么如果你要同时将世界坐标系的三轴旋转到相机坐标系的三轴只需要三列分别为xyz轴的目标单位向量即可
- 因此将-z轴根据
转到 ,然后由于旋转矩阵的逆矩阵只需要转置就行,求个逆就可以找到旋转矩阵。
- 假设我要将x轴旋转到(a,b,c)这个单位向量:
- 直接将
-
得到
和 后,将相机和其他物体都做一个这样的变换,保持相对关系,就完成了视图变换
0.3 投影变换
- 投影矩阵不是直接把 3D 点变成 2D 像素,而是先把相机坐标变成裁剪空间坐标。真正变成 2D 图像是在后面的“透视除法 + 视口映射 + 光栅化”阶段
- 为什么要先变成立方体(而不是直接二维)?
- 我们最终要在屏幕上画 2D 像素,但在画之前必须知道谁挡住谁、哪些部分该被裁掉、属性怎么做“透视正确插值”。这些都离不开 z/w,所以业界标准是:世界 → 相机 → 投影(得到裁剪坐标)→ 透视除法(NDC 立方体)→ 屏幕。
- 统一裁剪:无论正交还是透视,先把视见体变成统一的标准盒子(或在齐次裁剪空间的盒状约束),硬件用同一种裁剪逻辑就能处理,效率高。
- 保留深度:在产出 2D 像素前,需要 z 来做隐藏面消除、透明度排序、阴影/雾等效果。
- 数值/规范化:把范围归一到[-1,1](或 z∈[0,1])能简化插值、测试和精度控制。
- 为什么要先变成立方体(而不是直接二维)?
- 有两种投影方式:
- 正交投影
- 透视投影
- 有远小近大
0.3.1 正交投影
- 正交投影的作用是把一个长方体视见体映射到 NDC 立方体。
1. 流程
-
假设你要对空间中的一个立方体:
,里面的所有内容进行正交投影 -
先将立方体的中心移到原点,然后将xyz分别缩放到[-1,1]
0.3.2 透视投影
-
使用最广泛,远小近大,平行线就不再平行了

-
透视投影通过一个视锥观察物体,假设近平面是n,远平面式f,如上图(下),透视投影要做的就是将线的右端点投影到左端点,透视投影跟正交投影的区别就是远平面要大些。
1. 流程:
- 假设对远平面进行压缩,把他压缩成跟近平面一样大,(图下从左到右的过程),然后将远平面的点通过正交投影就可以投影到近平面。相当于将透视投影拆分成两个过程:
- 压缩平面,将远平面到近平面这里所有的平面都压缩成跟近平面一样大
- 对压缩后得到的立方体做正交投影
1)压缩平面
- 几个规定假设:
- 近平面压缩后不变(例如顶点压缩后还是原来的顶点,中点压缩后还是原来的中点)
- 远平面压缩后z值不会变化
- 远平面的中心点压缩后不变

- 对于视锥上的一条线,他在某平面(不一定是远平面)上的点为a (x,y,z),他在近平面上的点为b(x',y',z');a点压缩后(视锥压缩成立方体),会被压缩成(x‘,y’,z''),且根据三角形相似可知:
(z<0, n>0)。对于某平面(不一定是远平面)任意一点 会被压缩成 ,最后一步除以-z就是透视除法,透视除法不是直接变成 2D。透视除法后得到的是 NDC 坐标:
- 所以$M_ { persp \rightarrow ortho } = \left ( \begin{matrix} n&0 &0 &0 \\\\ 0 &n &0 &0 \\\\ ? &? &? &? \\\\ 0 &0 &-1 &0 \end{matrix} \right)$
- 对于近平面上的点,压缩后不变:
-
远平面的中心点压缩后不变:
-
根据
得:
- 最后
- 近大远小的来源:对于相机坐标系空间的点
,经过投影矩阵得到裁剪空间坐标,再经过透视除法得到:点 ,
- 因为相机前方 \(z<0\),所以这个式子会把:$z=-n$ 映射到:$z_{ndc}=-1$ 。把:$z=-f$ 映射到:$z_{ndc}=1$ ,透视投影的核心是:$x_{ndc},y_{ndc}\propto \frac{1}{z}$ 由于相机前方 \(z<0\),距离越远,\(|z|\) 越大,$\frac{1}{|z|}$越小,所以物体投影到屏幕上越小。
0.4 光栅化
- 目标:把 NDC 中的坐标映射到屏幕像素坐标。
- 注意:这里不是简单丢掉 (z)。(x,y) 用来确定屏幕位置,(z) 仍然用于深度测试。
0.4.1 流程
- 首先,不管z轴,只看x,y轴,先将[-1,1]范围的x,y映射到[0, width] x [0, height]的平面:
- 缩放加平移(之前的原点在立方体中心,变换后在左上角)
-
然后判断三角形是否覆盖某个像素。如果覆盖,就计算这个像素的颜色、深度等属性。
-
多个三角形覆盖同一个像素时,根据深度 (z) 判断谁在前面。
1. NeRF
- NeRF是什么
- NeRF的输入输出是啥
- 怎么把NeRF的输出转化为一张图片
- Loss怎么计算
1.1 NeRF是什么
- NeRF是:一种3D表征,类似mesh,点云; 他使用神经网络(MLP)来隐式存储3D信息
- 显式的3D信息:有明确的x,y,z值(mesh, voxel体素,点云)。
- 比如mesh会使用一个矩阵
表示有四个顶点,与这四个顶点的xyz坐标。使用 表示有两个表面(三角形)顶点1,2,3之间有连接,顶点1,2,4之间有连接。
- 比如mesh会使用一个矩阵
- 隐式的3D信息:无明确的x,y,z值,只能输出指定角度的2D图片。
- 显式的3D信息:有明确的x,y,z值(mesh, voxel体素,点云)。
- NeRF不具有泛化能力,需要针对每个场景进行训练,来一次训一次,不是feed forward模型。
1.2 NeRF模型结构
| 问题 | 回答 | 解释 |
|---|---|---|
| 模型 | 8层MLP | |
| 输入 | 5D向量,
|
这是粒子的空间位姿 ,
|
| 输出 | 4D向量,(密度,颜色) | 这是粒子对应的颜色以及密度 颜色包括RGB |
1.3 粒子假设
1.3.1 相机模型
- 真实场景成像过程:我们看到东西的时候他会通过多个反射源去打光,然后物体和物体之间也会折射和反射,这些光最后都会打进我们的眼睛。

- 相机模型:对上述过程进行建模,连接3D世界与2D图片,其中涉及到几个关键的坐标系:
- 世界坐标系
- 相机坐标系
- 归一化相机坐标系(物理成像平面)(CCD)
- 像素坐标系

- 3D建模(3D重建):通过图片去推测光源的位置和强度,以及物体的几何性质(比如他的材质)。这样操作建模难度巨大。因此在NeRF中就引入了一个概念:体渲染 。
1.3.2 体渲染
- 渲染技术的一个分支,在CG领域中,它是为了解决非刚体(云、烟、果冻)(刚体通常有较大密度)的渲染。我们通常将非刚体抽象成一团飘忽不定的粒子群。对于这些非刚体,他在成像的时候是光线在穿过的时候会光子和粒子发生一些碰撞。光子和粒子在发生作用的过程中会有四个过程:
- 吸收:光子被粒子吸收了
- 放射:粒子本身也会发光
- 外射光:其他例子向我们反射过来的光
- 内射光:我们对其他物体折射的光
- NeRF假设:
- 物体是一团自发光的粒子
- 粒子有颜色和密度
- 外射光和内射光抵消
- 多个粒子被渲染成指定角度的图片

1.3.3 NeRF的输入输出
- 输入:将物体进行稀疏表示的单个粒子的位姿
- 输出:该粒子的密度和颜色
- 思考:
- 模型看上去输入的还是一张图片,输出的也应该是一张图片,我们准备的训练集里面的图片在哪呢?
- 怎么得到这些粒子?
- 多少个粒子?这些例子怎么批量输入?
- 这些粒子是怎么渲染成新的图片的 ?
1.3.4 粒子的采集
- 对于空间中的某一个发光粒子:
- 空间坐标:(x,y,z)
- 发射的光线通过相机模型成为图片上的像素坐标 (u,v) (从相机出发链接到这个像素点的射线刚好穿过这个粒子)
- 粒子颜色即为像素颜色
- [[## 0.2 视图变换|视图变换]]:(u,v)与(x,y,z)的转换公式:相机坐标=相机内参x转换矩阵x世界坐标,该过程包括: [[## 0.2 视图变换|视图变换]] + [[## 0.3 投影变换|投影变换]] + [[#0.4 光栅化|光栅化]] ,呈现形式是一个W2C矩阵
- 其中,转换矩阵为:视图变换矩阵*透视投影矩阵得到归一化相机空间(不是NDC空间,他的xy范围不是[-1,1],他是焦距为1时的成像平面坐标) ; 内参矩阵为光栅化的缩放与平移
- 内参fx:当物理焦距f(相机与成像平面的距离)=1(相机常用单位mm)时取成像平面,相机常用像素尺寸
表示一个像素在传感器上的物理大小表示一个pixel代表多大的物理举例,内参 ,f=1时,fx表示,一个物理距离表示几个像素(x方向)。而归一化相机空间正是以这个物理距离为单位的,因此通过焦距可以做到物理空间与像素空间距离的转换。 - 内参cx:表示相机光轴穿过图像平面的那个点,在像素坐标系里的 x 坐标。理想情况下是图像中心。采用单位是像素坐标。
- 具体内参外参的获取可以参考 [[3DGS笔记#3. COLMAP与SfM]]
- 内参fx:当物理焦距f(相机与成像平面的距离)=1(相机常用单位mm)时取成像平面,相机常用像素尺寸
- 其中,转换矩阵为:视图变换矩阵*透视投影矩阵得到归一化相机空间(不是NDC空间,他的xy范围不是[-1,1],他是焦距为1时的成像平面坐标) ; 内参矩阵为光栅化的缩放与平移
- 从粒子到像素:已知相机位置A,图片上的像素点B(u,v),我们可以找到一条射线。像素点B的颜色可以看作射线上无数个发光点的“和”
- 一个像素点对应一条射线:
。 为射线原点, 为方向, 为距离
- 一个像素点对应一条射线:
- 由像素点P(u,v)反推射线
- 定义:
- 像素平面坐标系:
- 物理成像平面坐标系:
- 相机坐标系:
- 世界坐标系
- 像素平面坐标系:
- 像素平面 -> 物理成像平面
-
- 物理成像平面 ->相机坐标系:
-
- 归一化:
-
- 相机坐标系 ->世界坐标系:
-
- 根据相机位置(数据集中已知相机的的世界坐标),任意选择一个像素根据上述过程得到该像素的世界坐标。有了相机坐标,像素点坐标,你就知道射线了。
- 代码样例
- 定义:
1 | import torch |
- 理论上,由于射线无限长,t可以取0到正无穷,且可以连续。实际上t是取连续的,而且范围怎么选择呢?
- 通常设置near=2,far=6,在near和far之间均匀采样64个点
1 | N_sample = 64 |
- 训练时,通常一张图片取1024个像素,得到1024条射线,每条射线采样64个粒子,将粒子坐标输入模型[1024*64,3]
- 推理时,输入一个粒子的坐标,输出该粒子的颜色和密度
1.4 网络结构
- 8层全连接,首先输入粒子坐标,半路再次输入坐标,后半路输出密度
,密度主要由坐标决定。后半路输入视角(射线方向),最后输出粒子颜色。
1.4.1 模型输入-位置编码
- 通常网络输入位置信息如果只输入一个3D的数值,建模结果会有细节丢失,缺乏高频信息。因此需要引入位置编码(位置编码将一个数字编码到不同频率):
- p为一个标量,x/y/z,需要归一化到[-1,1]
-
表示使用多少组不同频率进行位置编码。模型对于粒子坐标取 ,因此一个数字编码成 2*10 = 20个数字 - 位置信息为:3*2*10 = 60D
- 模型对于视角(实际上视角使用世界坐标系下的射线方向向量表达)取
,因此视角信息为:3*2*4 = 24D - 代码中在加上原始坐标值:
是 63D; 是 27D
- 为什么这里位置的L设置的比视角的L大?
- 对于一个粒子来说他的空间坐标的重要性大于他的视角,所以说在模型结构里输出密度的时候还没加入方向信息,说明粒子的密度只跟它自己本身的绝对空间坐标有关系,跟视角无关;但是颜色在各个角度是不一样的
- 同一条射线上粒子方向是一样的,为什么每个粒子还要编码方向?因为每次采样会采样多条射线1024
1.4.2 模型输出
-
:查询位置这里有多大概率存在可见物质,即不透明程度。 - Color:查询粒子的颜色。如果这里存在可见物质,它朝观察方向 d 发出的辐射颜色。
- 这些粒子是怎么渲染成新的图片的?对于图片中每一个像素,计算该像素对应的光线和粒子将这些粒子通过公式累加得到该像素最终颜色
1)连续粒子求和
- 假设有粒子A和B,A在B的前面,如果A异常明亮,那么B的光就不会显示了,粒子之间有遮挡关系,但是实际上一个像素的颜色等于这条射线上粒子颜色的混合。怎么混合呢?用概率密度求期望。假设有AB两个点,那么最终颜色等于:
- Color是如果光线在这里终止,看到的颜色。
-
表示在该粒子之前,光线没有被阻挡的概率,如果这个概率等于0,说明光线无法到达这个粒子,这个粒子的颜色就不起作用了,他贡献的颜色就是0。 -
表示光线撞击该粒子(光线被粒子阻挡)的概率密度(光被吸收的概率)到达该粒子的位置之后,光线在单位长度内终止的概率密度,也叫体密度或消光系数(表示当前这个粒子挡光的能力)。
- 总结为连续情况,像素颜色:
-
的推导:对于s位置后面的点 ,它不被遮挡的概率是:点s不被遮挡且 这一段也都不被遮挡,由于 很小, 这一段被遮挡的概率都视作
2)离散粒子求和
- 离散假设:
- 将光线[0,s]划分为N个等间距区间
- 间隔长度为
- 假设区间内密度
和颜色 固定
- 将光线[0,s]划分为N个等间距区间
- 公式推导:
- 将连续情况下的积分变成N段的求和:
。对于其中的某一段- 令
最终:
- 令
- 将连续情况下的积分变成N段的求和:
- 最终颜色的计算形式就是:
多个粒子颜色加权求和
1 |
1.4.3 模型优化
- 一个问题:采集粒子时,前面时均匀采样64个点,均匀采样可能采样到无效区域(空白区域和遮挡区域),我们希望有效区域多采样,无效区域少采样。

- 解决方法:根据概率密度进行二次采样
- 将NeRF分成两个模型:
- 粗模型:输入均匀采样粒子,输出密度
- 细模型:根据密度,二次采样
- 粗模型和细模型结构相同,最后采用的是二次采样的粒子作为输入
- 首先根据公式
取粒子颜色前的权重做softmax: - 此时,新的权重和为1,可看作概率密度函数,假设分成三个区间,权重分别为:
- 取概率密度函数的CDF(概率累积到当前位置一共是多少):
,然后随机生成一个数,例如0.6,看它落在哪个区间说明应该在哪个区间采样。 - 具体采样操作:取CDF的反函数invert,用均匀分布drand48()生成一个随机数:invert(drand48)=r,得到的r就是符合pdf分布的随机数。
- 首先根据公式
- 对于每条光线,重新采样128个粒子,与之前的64个粒子加在一起,即每条光线采样192个粒子。
- 将NeRF分成两个模型:
1.5 训练流程
1)前处理
- 将图片中的每个像素通过相机模型找到对应的射线
- 每条射线上进行采样,得到64个粒子
- 对1024*64个粒子进行位置编码
- 位置坐标
-> 63D - 方向向量
-> 27D
- 位置坐标
2)粗模型处理
- 使用8层MLP
- 输入位置[1024,64,63]、方向[1024,64,27]
- 输出[1024,64,4](
)
3) 后处理
- 根据粗模型的输出,对射线进行二次采样
- 每条射线最终192个粒子
4)细模型处理
- 模型网络不变,还是8层MLP
- 输入为[1024, 192, 63] 和 [1024, 192, 27]
- 输出为[1024, 192, 4]
5)体渲染
- 输出射线像素颜色
6)损失函数
-
- R是采样射线条数(1024)
- GT是射线r对应的那个像素的RGB
- 预测颜色与GT颜色的MSE损失
1.6 推理
- 输入:h*w条射线上分别采样64个点
- 输出:h*w*192*4
- 根据4这个维度(
)进行体渲染
2. 3DGS
2.1 3DGS是什么
- 一种3D表征,类似mesh,点云,NeRF; 他使用3D高斯基元来显式存储3D信息
- 在点云的基础上给每个点云扩展成高斯球,然后进行循环优化。
- 3DGS与NeRF一样不具有泛化能力,需要针对每个场景进行训练,来一次训一次,不是feed forward模型。
2.2 3DGS模型结构
- 3DGS的原理就是将点云扩展成一个个雪球,然后雪球打到成像平面上,融合得到像素颜色。
2.2.1 Splatting
1)概念理解
- Splatting是一种体渲染方法:从3D物体渲染到2D平面
- NeRF中用的体渲染方法叫Ray-casting,它是被动的。我有一张图片,然后我从每个像素出发去找到影响这个像素的发光粒子,最后确定这个像素的颜色。这个过程中主角是像素。
- splatting是主动的,计算出每个发光粒子如何影响像素点,主角是粒子。
2)Splatting的流程
- 选选择基元(雪球)表达方式
- 渲染(抛雪球),从3D投影到2D
- 合成颜色
2.2.2 3D 高斯
1)定义
-
表示协方差矩阵,半正定; 是其行列式。 时维数 - 为什么选择3D高斯椭球作为基元?因为他又很好的数学性质:
- 仿射变换后高斯核仍然闭合
- 3D降维到2D后(沿着某一个轴积分)仍然为高斯
2)3D高斯椭球推导
- 椭球面表达式:
- 3D高斯表达式:
- 高斯明显不是上面的椭球那种形式啊?它的值是一个概率,他可能是0.1,0.2...,它为什么是一个椭球呢?
- 协方差矩阵
- 一维高斯分布的形状由均值和方差决定。二维时,方差就变成了协方差矩阵
- 该矩阵为对称矩形,决定高斯分布形状。对角线上元素为x轴/y轴/z轴的方差,反斜对角线上的值为协方差,表示x和y,x和z...的线性相关性
- 从3D高斯表达式到椭圆
-
这部分是常数,带变量的部分在指数上面。 - 当
中,x取一个值时,整个高斯的概率就求出来了。 - 一维时,
- 二维时 ,椭圆方程
- 三维时,椭球面方程
- 每个
对应一个c,对应一个椭球面方程。因此3D高斯定义了一个实心的椭球,大椭球壳套小椭球壳,每个椭球壳对应一个概率。
-
3)协方差控制椭球形状
- 标准高斯分布
- 均值[0,0,0]
- 协方差矩阵
- 对标准高斯分布进行仿射变换
- 均值变为
,改变了位置,协方差改变了形状 - 任意高斯可以看作是标准高斯通过仿射变换得到,也就是球经过仿射变换后变成椭球
4)协方差通过旋转与缩放表达
- 从标准高斯放射变换得到目标高斯:
- 放射变换的定义就是线性变换 + 平移组合,平移部分是b,线性变换部分是A。线性变换可以分解成:旋转、缩放、剪切,因此
,带入上式协方差部分 - 上式表明了协方差可以表达成旋转+缩放。那么如果已经知道协方差矩阵了,怎么求
呢?- 通过特征值分解将协方差分解成
-
是一个对角矩阵,他的对角线元素是他的特征值: - 将中间的
开方:
- 通过特征值分解将协方差分解成
2.3 训练流程
2.3.1 点云初始化
1)点云数据
- 3DGS通常采用点云数据进行初始化,数据集通常包含如下结构:
1 | <location> |
cameras.bin
- 相机内参,不一定只有一条,后面的images.bin里每个图片都有一个CAMERA_ID字段,指向这里的一条记录
1 | Camera list with one line of data per camera: |
- 相机ID
- 相机模型:3dgs采用的是 理想的小孔成像模型 PINHOLE (透视投影、无畸变)
- 图像宽度
- 图像高度
- x焦距:fx=f/sx,
sx指的是像素尺寸(pixel pitch):感光器上每个像素在 x 方向的物理长度。 单位通常是 mm/px 或 µm/px(毫米/像素、微米/像素)。 有了物理焦距f(mm),把长度换成“像素”要除以像素尺寸。 早期或特殊传感器可能像素非正方形( );或者图像后期做了非等比缩放。这都会导致 。 ,现代相机多为方形像素 , 差异常来自标定噪声或裁剪/缩放。 - y焦距
- x主点
- y主点
images.bin
- 相机外参,表示如何将每张图片转换到相机坐标系,每张图片对应一份,一份对应两行
1 | Image list with two lines of data per image: |
- 第一行
- 旋转四元素
- 平移向量
- 相机id
- 图片名
- 第二行
- 图片中特征点:每张图上有多个特征点,每个特征点用三元组表示,x,y表示像素位置(原点在左上),point3d_id为对应的三维点编号(见points3D.txt),-1 表示“这个 2D 点没有成功三角化成 3D 点”(只在匹配里出现,但没形成 3D 点)。
points3D.bin
- 3D点云参数
1 | 3D point list with one line of data per point: |
- 3D点id
- 3D点位置
- 3D点颜色
- 重投影误差:重投影误差的平均/均方根(像素单位)
- 轨迹:由一系列二元组(image_id, point2d_idx)组成,一个二元组表示这个 3D 点被哪张图片的第几个 2D 特征看到(“轨迹”的一次观测),image_id来自images.txt,point2d_idx是特征点id(在一张图像上通过特征检测器(默认 SIFT)找到的兴趣点/关键点),来自images.txt的第二行
2)3D高斯基元
属性
- 每个3D高斯基元有如下属性:
- 均值:表示位置
- 协方差:表示形状
- 颜色
- 不透明度
- 初始点云提供了位置与颜色,然后根据KNN算法计算当前点与邻居点的距离,以此作为半径初始化正球形高斯
球谐函数
- 3DGS中的颜色并不只是用RGB表示。由于3DGS跟NeRF一样也是采用粒子假设和体渲染,同一个粒子从不同角度反射的光是不一样的,因此3DGS用球谐函数来表达颜色。这样就把颜色和观测角度建立了联系,颜色就不会单一了。
- y是与视角相关的参数,可根据3D高斯坐标得到
- c是3维向量(RGB),通过学习获得
- 3dgs一共使用3阶级球谐系数,一共1+3+5+7=16个c
- 3D高斯初始化时使用0阶球谐系数,初始颜色采用点云颜色
- 训练过程中会逐步提高球谐系数
2.3.2 前向渲染
- 世界坐标系 -> 相机坐标系 -> 归一化坐标系 -> 2D像素坐标系 -> 渲染成图
1)坐标变换与高斯投影
- 假设一个3D高斯基元:
- 高斯核中心(均值):
- 协方差矩阵: - 高斯核(取高斯分布的指数部分):
1. 视图变换
- 仿射变换,详见[[3DGS笔记#0.2 视图变换]],根据相机外参的平移向量+旋转四元数 得到
,变换后:- 高斯核:
- 均值(线性变换):
- 协方差矩阵(类似方差平方变换):
- 高斯核:
2. 投影变换
- 非仿射变换,详见[[3DGS笔记#0.3.2 透视投影]],将视锥体压成立方体,然后归一化压成立方体,然后透视除法:
,这个过程是非线性的,且有形变- 高斯核:
- 均值(对于一个点可以直接应用):
- 协方差矩阵(非仿射变换,不能直接应用过来):?
- 高斯核:
基于雅可比矩阵的投影变换
- 由于透视投影是非仿射变换,因此不能直接将转换矩阵用到协方差上。在此需要引入雅可比矩阵。
- 雅可比矩阵做的是泰勒展开,线性逼近
- 举例:假设在坐标系中有一个点(x,y),对他进行一个非仿射变换:
- 上述变换将平面直角坐标系映射成了一个弯曲的坐标系,(关注某一个点,比如(-2,1)这个点变到了黄色框这个位置,展开这个小框,中心点是(-2,1),其他附近的变换是非线性变化)
- 采用微积分的思想,假设把黄色框框进一步缩的很小小,它附近的变换可以近似成一个线性的变换:

- 也就是说确定了一个点
,在他附近就可以通过导数去进行线性的逼近了。反映到一维坐标系,假设 , 我想要知道 是多少,真实值是4.41。如何用雅可比矩阵(导数)来逼近呢?- 将f(x)在x0处进行一阶泰勒展开:
- 因为刚刚假设的在很小范围内做逼近,因此这里x0取一个离2.1很近的值,比如2
- 带入x0=2, x=2.1后得到估计值4.4,与真实值果然很接近。
- 上述过程中的一阶倒数就是雅可比矩阵
- 对f(x)的均值做估计:
- 对f(x)的方差做估计:
- 也就是说对于方差使用非仿射变换相当于对他使用这个非仿射变换的雅可比矩阵。
- 将f(x)在x0处进行一阶泰勒展开:
- 回到例子,他对应的雅可比矩阵就是
- 对透视投影部分的变换矩阵:
求雅可比矩阵 , 对协方差应用透视投影变换就相当于:协方差矩阵 = , 具体展开时带入协方差矩阵的x0是高斯中心。 - 实际上,代码在计算雅可比矩阵的时候是一步到位的,直接从相机坐标系,不需要做正交投影压缩到NDC空间,直接用
以及视口变换求雅可比。这么做的原因可能是均值需要绝对位置,协方差只描述中心附近的相对偏移。- 对相机坐标的一个点(x,y,z)使用透视转正交变换:
- 我们发现变换关系是:
- 因此可以求雅可比矩阵(通常带入的是3d高斯的中心点):
- 再应用视口变换,转换到像素空间:
- 对相机坐标的一个点(x,y,z)使用透视转正交变换:
3. 视口变换
- 详见[[3DGS笔记#0.4 光栅化]],视口变换只需要对高斯核的中心去做,不用对协方差做
2)高斯光栅化与 Alpha 合成
- 光栅化部分类似NeRF,采用
进行颜色合成
1. 分块
- 选择目标视角
- 多线程并行执行,将该视角图片划分为多个
的像素块(tile) - 将3D高斯进行坐标变换后到了2D图片范围(z轴忽略),根据投影的2D高斯分布的
椭圆长轴半径作为半径画圆,以这个圆的外接矩形作为该高斯可以覆盖的区域(足迹)。 - 标记这个高斯可以投影到哪个tile,如果一个高斯的足迹覆盖了多个tile就把他复制多份,以tile为单位进行分桶

2. 排序
- 按照块号,深度(z轴),对所有分桶复制后的高斯排序

3. 光栅化
- 根据排序后的深度先后顺序进行渲染:
-
:通过球谐函数算的RGB值(可学习) -
:第i个高斯的不透明度(范围[0,1],值越大,对像素的遮挡越强)(可学习)。在一个tile中,高斯中心的不透明度是高斯本身的,在其他地方要乘以一个系数(这个系数根据2D高斯分布计算出来,距离均值的地方越近就越接近1) -
-
:光线穿过前i-1个高斯后剩余的比例(可根据 累积计算)
-
2.3.3 渲染损失
-
-
-
默认为0.2
2.3.4 反向传播与参数优化
- 通过可微Gaussian rasterizer,把梯度反向传播到每个 Gaussian 的参数。前面协方差矩阵的推导就是为了可微分

2.3.5 自适应密度控制
- 每迭代一定的步数(默认100)之后会进行自适应控制
2.3.5.1 高斯克隆
- 计算loss对于当前高斯的2D空间梯度
,也就是其投影中心在屏幕空间 x,y 方向的梯度。并在多个 iteration / view 上累积,最终取平均。 - 如果梯度较大(说明当前位置与最优位置差距很大,当前 loss 对移动这个 Gaussian 的屏幕空间位置非常敏感,这附近的表示还不充分)
- 如果当前高斯3D协方差尺度很小
(其中E是场景尺度,p默认0.01),说明延展不足,将其复制。- 直观上,一个 Gaussian 已经很小,但这个区域仍然有较大的优化需求。继续把一个 Gaussian 缩小意义不大,更合理的是增加 Gaussian 数量。
- 完全复制,高斯位置也一样,随着后面的梯度优化过程两个高斯会慢慢区别开。新增 Gaussian 的 Adam optimizer state 是新建的,moment的m和v都是0,而原 Gaussian 已经具有之前训练积累的 Adam moments。
2.3.5.2 高斯分裂
- loss对于当前高斯的2D空间梯度较大(说明当前位置与最优位置差距很大)
- 如果当前高斯3D协方差尺度很大,说明重建过度,将其分割并缩小
- 新 Gaussian 的中心是从原 Gaussian 自己的 3D 椭球分布内部随机采出来的。新生成2个高斯,复制原来的颜色、opacity、rotation 等参数;新 Gaussian scale 缩小(三轴都:
); - 删除原来的大 Gaussian。
2.3.5.3 高斯剪枝
- 当不透明度小于某个阈值(0.005)时,剔除
- 屏幕空间过大(
),剔除 - 世界空间过大(
,其中E是场景尺度),剔除
2.3.5.4 不透明度重置
- 每3000步执行一次opacity reset:
- 为什么要重置 opacity?因为训练过程中一些 Gaussian 可能很早就获得较高 opacity,从而遮挡后方 Gaussian,导致场景结构过早固化。reset之后所有 Gaussian 又需要重新通过后续优化证明自己的贡献:有用 Gaussian → opacity 再升高;没用 Gaussian → opacity 保持很低,最后被 pruning。训练循环确实把 opacity reset 和 densification/pruning 交替执行。
3. COLMAP与SfM
- SfM(Structure from Motion,运动恢复结构):一套计算机视觉算法理论 / 技术流程,不是软件 ,输入一堆不同视角、有重叠区域的 2D 照片,同时求解两件事:
- Motion(运动):每一张照片拍摄时相机的内参(焦距、畸变)、外参(相机在 3D 空间的位置 + 朝向);
- Structure(结构):场景里物体的三维稀疏点云。SfM 只输出稀疏点云(只有特征点,很稀疏);想要稠密完整模型,需要后续 MVS(多视图立体匹配)。
- 标准 SfM 完整流程(三步)
- 特征提取(SIFT 等局部特征)
- 图像匹配 + 几何校验(筛除错误匹配点)
- 增量重建:初始化模型→逐帧注册相机→三角化 3D 点→BA 光束平差全局优化
- COLMAP 是一套开源、工业级、学术界通用的完整软件工具,由慕尼黑工大开发,是实现 SfM+MVS 的标准工具链。- 支持 GUI 可视化操作 + 命令行批量处理;内置增量式 SfM作为核心稀疏重建模块;同时自带 MVS 稠密重建模块,能从稀疏点云生成稠密点云、三维网格;几乎所有 3D 重建、NeRF/3DGS 高斯泼溅、SLAM 论文都会用它预处理数据集。
4. 4DGS
5. 3DGS进阶
5.1 FastGS
- 通过重新设计裁剪与致密化策略,让训练过程中 Gaussian 数量始终维持在较低水平,从而让每一步训练都更便宜。
- 原始3dgs的训练主要有两件事情:1. 参数优化 2. ADC(Densification+Pruning)。原始3dgs的densification基本看image-space position gradient,梯度大,就认为这个 Gaussian 所在区域没拟合好,于是 clone/split。但是某个视角梯度大”并不意味着这个 Gaussian 在三维意义上真的需要 densify,它可能只是:某一视角遮挡;某一个 view 出现局部误差;高频纹理导致梯度大;已经存在别的 Gaussian 可以解释这个区域。于是 Gaussian 会不断膨胀到几百万。
5.1.1 VCD - Multi-view Consistent Densification
- 原始3dgs仅根据梯度判断是否densification,这里进一步增加multi-view error。
- 一次性取k(10)个视角算RGB L1 error然后颜色通道平均:
- u,v表示一个像素
- j表示一个视角
- C表示通道
- r表示某一个视角某一个像素某一个通道的渲染图颜色
- g表示GT图
- 对上面求得的逐像素error进行min-max normalization,然后根据预定义的阈值得到一个mask,mask=0的地方表示像素误差比较小,拟合的不错;mask=1的地方表示重建的比较差的像素。
- 对每个高斯,将他们投影到上面的k个视角下。统计他的足迹覆盖了多少误差较大的像素:
- K表示训练一次性取的视角
- j表示某个视角
-
表示高斯i在视角j下的足迹 - p表示足迹里面包含的像素
- 如果高斯i空间梯度大的同时
计算出来大于预定义的阈值,才允许densification。
5.1.2 VCP - Multi-view Consistent Pruning
- 对应的裁剪策略也需要优化。
- 计算某个视角j的整体error
- 对每个高斯,将他们投影到上面的k个视角下。统计他的足迹覆盖了多少误差较大的像素然后乘以该视角的整体误差:
- 其实就是在VCD基础上乘以像素误差
- 如果
很高,说明一个 Gaussian 在多个整体重建很差的视角里,又长期落在 high-error region,那么这个 Gaussian 更可能没有有效帮助 reconstruction,甚至与 reconstruction degradation 相关。所以不是将他增值而是裁剪掉。 - 为什么 VCD 高分要增加,VCP 高分反而要删除?
- VCD不是看到 error 大就直接 densify。他还要满足梯度条件,当前 Gaussian 梯度已经说明“它想发生明显变化”,同时多个视角又说明“这里确实一直没拟合好”。
5.1.3 Compact Box
- 原始3dgs以高斯分布的3sigma椭圆的长轴半径为半径的圆的外接矩形作为覆盖区域。
- FastGS先设定不透明度阈值,然后根据这个阈值结合高斯分布的方程,算出一个动态的椭圆长轴半径为k sigma, 这个椭圆范围内高斯不透明度大于阈值。以这个长轴半径作为规范。这样就可以根据不透明度动态选择覆盖范围。减少分桶排序复制的开销以及不透明度过低的区域。