球谐函数与哈希编码:NeRF秒级训练的关键技术解析

发布时间:2026/9/19 4:08:09
球谐函数与哈希编码:NeRF秒级训练的关键技术解析 1. 从必须训练十几个小时讲起NeRF的原始阿喀琉斯之踵如果你经历过2020年到2021年那个NeRF刚刚火起来的阶段一定对训练慢这件事记忆犹新。当年跑一个标准的NeRF就是Mildenhall那篇经典的ECCV 2020论文在一张高端显卡上训一个场景动辄需要几十个小时甚至两三天。你要是只想快速看一眼效果至少也得做好等待一晚上的心理准备。为什么会这么慢这就要追溯到NeRF设计里两个非常奢侈的底层选择。第一它把整个三维场景固化在了一个全连接的深度神经网络里。网络本身是个隐式的表达方式你问它空间点x在方向d上的颜色和密度是多少它得经过一层又一层矩阵乘法和非线性激活函数算出来。这个查询过程的计算量非常大。第二为了渲染一个像素NeRF必须沿着一条视线camera ray采样几十个甚至上百个三维空间点每个点都要丢进网络里做一次前向推理。你想想看一张1080P的图有大概两百万个像素每个像素沿着光线采样一百个点那就要做两亿次网络前向推理。每一次推理还涉及到深层的MLP计算这个计算量根本不是实时的量级。所以当时的主流优化方向是什么大家想尽办法减少采样点数量、用八叉树或者包围盒跳过空白区域、做重要性采样。说实话这些取巧的办法效果有限因为根子出在神经网络的推理方式上——每一次查询都要动用整个网络的计算图。我记得很清楚2022年英伟达的Instant-NGPInstant Neural Graphics Primitives论文放出来的时候几乎是炸了整个NeRF社区。同样是合成一个场景训练时间从十几个小时压缩到了几秒钟渲染速度直接跑到实时帧率。你甚至可以在显卡上交互式地拖拽视角看着画面从一片模糊快速收敛成清晰的三维模型。这个性能飞跃是怎么做到的答案就藏在这篇博客的标题里球谐函数和哈希编码。前者不是一个新概念它是经典数学物理里的老工具在传统图形学里早就用在光照渲染上了后者是Instant-NGP真正颠覆性的创新用一种多分辨率哈希网格结构取代了原始NeRF里那个又大又笨的MLP。这篇文章我想把这两块拆开揉碎讲清楚。先说说球谐函数是怎么被挪进NeRF做视角依赖的颜色建模的再看看哈希编码又凭什么能用几千个参数逼近原来几百万个参数才能表达的场景结构。最后聊聊这个组合从根本上改变了NeRF训练和推理的数学范式——把查询一张表和查网络结合了起来。如果你正准备上手NeRF相关的项目或者想深入理解Instant-NGP的实现原理这篇文章应该能帮你省下不少瞎翻代码和论文的时间。2. 球谐函数经典数学工具如何在NeRF里找到新位置2.1 球谐函数的数学直觉用正交基逼近球面上的函数很多人一听到球谐函数这个名字就觉得高不可攀其实它的核心思想并不复杂和我们在二维平面上用的傅里叶变换如出一辙。傅里叶变换做的事情是任何一个一维信号都可以表示成不同频率的正弦波和余弦波的叠加。球谐函数Spherical Harmonics简称SH就是这套思想在球面上的推广——任何定义在球面上的函数都可以表示成一系列球谐基函数的加权叠加。这些球谐基函数长什么样想象一下球面上分布着温度、亮度或者压强有些地方高、有些地方低形成了一个起伏的曲面。为了描述这个曲面SH基函数提供了不同频率的图案零阶(l0)是一个均匀的球面一阶(l1)是类似偶极子的明暗分布二阶(l2)有更复杂的条带或四极子图案……阶数越高能捕捉的细节越丰富。数学形式上球谐函数 (Y_l^m(\theta, \phi)) 是一组在球面上正交的基函数其中 (l) 是阶数band(m) 是序数。任何一个球面上的函数 (f(\theta, \phi)) 都可以写成[ f(\theta, \phi) \approx \sum_{l0}^{L} \sum_{m-l}^{l} c_l^m \cdot Y_l^m(\theta, \phi) ]这里 (c_l^m) 就是我们要拟合的系数。取到第几阶L就是一个关键的超参数——L越大基函数越多能表达的高频细节越丰富但相应的存储和计算开销也越大。对于L2来说共有 ((21)^2 9) 个系数L3有16个系数L4有25个系数。在传统图形学里SH早就是光照计算的老熟人。预计算辐射度传输PRT、环境光照的漫反射近似、天空盒的压缩表示都有它的身影。因为SH天然的数学结构很适合描述低频光照在物体表面的平滑变化而且用少量系数就能获得足够好的近似效果。2.2 从MLP输出到SH分解视角依赖解耦的巧妙设计NeRF的原始论文引入球谐函数是为了解决一个非常实际的问题同一个空间点从不同角度看过去颜色是不一样的。比如一个金属球表面高光会随着观察角度剧烈变化又比如半透明的材质从侧面看和正面看颜色和亮度完全不同。第一版NeRF是怎么做的它直接把观察方向(\theta, \phi)作为额外的输入拼接进MLP的网络里。这种方式的问题在于方向信息被搅进了整个神经网络的特征空间里网络必须在拟合场景结构的同时还要记住方向对颜色的影响模式。对于一个连续函数拟合问题来说这会显著增加网络的学习负担导致训练变慢、收敛困难。Instant-NGP里的做法干脆利落得多把颜色建模从MLP主体中完全剥离出来。具体来说空间位置x先经过哈希编码和一个小型MLP得到密度和特征向量然后这个特征向量和一个额外编码的方向向量一起再喂给一个独立的通常很浅的颜色头部网络输出RGB。那球谐函数在这里扮演什么角色它扮演的是方向编码器的角色。论文中使用的做法是将三维方向向量 ((d_x, d_y, d_z)) 映射到一组球谐基函数在各个阶数下的值形成一个固定维度的特征向量。比如取到L4的SH系数那方向就会被编码成16维的向量。这个向量和MLP中间层的特征拼接后再经过最后的反射层输出颜色。这样做的好处非常明显解耦了场景几何和视角变化。MLP主体只需要专注于这个空间点是不是在物体表面上、密度是多少这类与视角无关的信息而颜色随视角的变化规律被单独交给色彩网络处理。这符合三维场景的内在物理逻辑——几何是视角不变的颜色/亮度才随视角变化。减少了高频拟合的压力。MLP天生对高频信息不敏感难以快速拟合高光边缘的陡峭变化。球谐函数把方向到颜色偏移的映射预先编码成一个相对平滑的基函数组合让后续网络只需要在这些预整形的基上调整权重即可。球谐函数嵌入NeRF的路线可以理解为给网络指了一条更容易走的路——你不用自己在特征空间里凭空构造出方向的规律我先把方向的骨架递给你你只需要在这个骨架上填东西就行了。2.3 阶数的选择精度和性能的平衡艺术实际工程里阶数的选择需要仔细权衡。我用Mini-Splatting和3DGS跑实验的时候对这一点感触特别深。在3D高斯泼溅3D Gaussian Splatting里视角依赖颜色同样用SH建模但跟NeRF类的场景又有点不一样。3DGS每个高斯点独立存储自己的SH系数这就带来一个比较现实的问题SH系数的数量直接决定了每个高斯的存储大小和渲染时的计算量。以典型的四阶SH为例RGB三个通道各自需要 ((41)^2 25) 个系数合计75个系数。这个数量级对于现代GPU来说勉强能接受但如果你想要更精细的视角高光表现升到五阶就是36个系数/通道108个系数/高斯存储和计算开销会同步上升。更麻烦的是高阶SH在实际训练中经常出现一个让你血压飙升的问题过度拟合低频信息和产生高频噪声伪影。特别是在多视角不一致的输入数据、或者场景存在遮挡边缘时高阶SH为了强行匹配某个角度的观测值会在相邻角度之间产生剧烈的颜色抖动表现为渲染画面上的闪烁或条纹。所以在Instant-NGP的实践中L29个系数到L425个系数是一个很常用的区间。L2已经能表达相当不错的光照渐变和中等程度的高光L4则在渲染带镜面反射的物体时表现更好更高的阶数往往收益递减反而带来更大的数值不稳定性。我记得看过一个实验有人用L2的SH做NeRF合成数据集的训练效果跟原始NeRF用完整MLP输出的结果几乎看不出差别。这就说明方向影响在大多数自然场景里是个相对低频的信息SH作为压缩表示完全够用。3. 多分辨率哈希编码实现秒级训练的引擎3.1 为什么原始NeRF的MLP注定慢要理解Instant-NGP为什么快得先搞清楚原始NeRF慢的根源。除了训练过程中大量采样点反复前向推理之外还有一个隐藏得很深的痛点MLP对高频细节的拟合能力天生不足。Mildenhall那篇NeRF论文里已经意识到这个问题。他们的解决方案是位置编码Positional Encoding把坐标映射到一组高频三角函数上[ \gamma(p) (\sin(2^0 \pi p), \cos(2^0 \pi p), \sin(2^1 \pi p), \cos(2^1 \pi p), \ldots) ]这是个有效的技巧但代价是什么呢每个连续坐标被扩展成一个几十维甚至上百维的向量在原文中输入坐标被编码成60维的特征然后这个高维向量被喂进MLP。MLP的输入层规模变得巨大并且在每一层都要进行高维矩阵乘法计算量直线上升。更关键的是位置编码的定义方式决定了网络必须自己学会如何组合这些三角函数基来拟合场景的局部结构——这就好比给了你一堆积木但需要你自己摸索怎么拼出这个场景。Instant-NGP的想法是我为什么要让网络去学会这些位置编码的规律我为什么不干脆把场景空间切分成无数个小格子每个格子存一组可学习的特征向量然后让网络去查表呢查表比计算快得多。这就是哈希编码的直觉起点。3.2 空间剖分与多分辨率LOD思想的工程化具体来说Instant-NGP把整个三维空间分成多个分辨率的网格。最低分辨率可能是 (16 \times 16 \times 16)最高可能是 (512 \times 512 \times 512) 甚至更高。每一层网格都是一个独立的表表中每个条目是一个可学习的特征向量默认维度是2维你可以配置为4、8等。对于空间中的任意一个点x对每一层分辨率 (L)计算出x落在哪个体素格子voxel中然后取出该格子的8个顶点的特征向量。基于x在格子内的相对位置对8个顶点的特征向量做三线性插值得到这一层的特征向量。将所有层的特征向量拼接起来形成一个多分辨率特征描述。这样一个点在每一层分辨率都能拿到自己对应尺度的特征。低分辨率层捕捉大尺度的场景结构高分辨率层捕捉细节纹理和几何边缘。这本质上就是经典图形学里的LODLevel of Detail思想——用多级细节自适应地描述空间。这个设计的巧妙之处在于不同分辨率层各司其职网络不需要用一个巨大的连续函数去同时拟合大尺度和小尺度结构而是由不同分辨率的表项各管一段。在训练的时候高频细节只需要微调高分辨率层的那几个表项不影响低分辨率层的全局信息这让训练效率产生了质的改变。3.3 哈希冲突不是Bug是Feature这里有个特别有意思的设计——哈希冲突。当分辨率高到一定程度比如 (512^3)体素的总数是惊人的1.34亿个如果每个体素都分配独立的特征向量那内存开销大到完全不可接受。Instant-NGP的答案是不直接分配这么大的表而是用一个固定大小比如 (2^{19}) 条约50万条的哈希表来存储特征。通过一个空间哈希函数把 ((x, y, z)) 坐标映射到这个紧凑的哈希表中不同位置的体素可能映射到同一个表项这就产生了哈希冲突。如果放在传统数据库或缓存系统里冲突是要尽量避免的坏事。但在这里冲突反而成为了一种隐式的正则化。多个空间点共享一个特征向量意味着如果场景中两个不相关的区域碰巧发生哈希冲突它们会争抢这个特征向量的值最终的结果往往是特征向量收敛到两个区域的折中表达。这种折中带来的效果很微妙——它抑制了过度的细节刻画使得Instant-NGP对欠约束区域和噪声数据有天然的抗过拟合能力。当然这也不是完全没有代价。在光照剧烈变化的区域或者需要精确重建的几何边界哈希冲突会引入渗色bleeding现象让不同区域的纹理互相污染。所以工程实践中哈希表的大小、特征维度、层数都需要根据场景特点调优。在我的经验里把哈希表基数从默认的 (2^{19}) 调大到 (2^{22}) 往往能显著减少渗色伪影代价是训练速度略降。3.4 为什么MLP只剩一层皮就够用了在原始NeRF里MLP承担了几乎全部的场景表达职责——它是一个很深的结构8层左右每层宽度256个神经元。所有关于几何和颜色的信息都要经过这个网络的转化。在Instant-NGP里事情彻底反转了。哈希网格本身承担了90%的场景信息存储和表达职责MLP退化成一个轻量级的解码器只需要对查询到的特征向量做融合和映射。在论文的默认配置里这个MLP通常只有2-4层宽度64左右。为什么这样做可行原因在于特征表项是可学习的经过训练后每个表项的向量值就已经编码了该位置附近的几何/颜色信息。MLP要做的事情只是一个特征变换和压缩——把多分辨率拼接得到的特征向量映射为密度和RGB值。我做个不恰当的类比原始NeRF像是一个全科医生什么病都得自己诊断自己开药Instant-NGP像是建立了一座巨大的档案馆哈希表里面分门别类存好了各个区域的信息MLP只是档案馆门口的一个接待员负责把你导航到正确的架子上拿资料。前者的信息密度低全靠计算换取精度后者的信息密度高靠存储换计算。这套架构带来的直接影响是训练一个场景从分钟级慢的甚至小时级变成了秒级。用yyc那种风格说就是从下班之前把训练挂上第二天来看结果到去倒杯水的功夫模型已经训好了。4. 重构数学基础从连续函数逼近到离散查表范式4.1 位置编码 vs 哈希编码两种数学哲学的对撞现在我们来看这个标题里最有意思的问题Instant-NGP到底重构了什么数学基础传统NeRF里三维场景被建模成一个连续函数(F_{\Theta}(x, d) \rightarrow (c, \sigma))用神经网络的权重 (\Theta) 来逼近这个函数。这是一个典型的函数逼近问题网络训练的过程就是寻找一组权重使得这个连续函数在采样点上拟合观测值。而Instant-NGP的场景表达本质上变成了一个离散查表问题。场景信息被存储在有限个离散的表项里MLP只是在做从表项到物理量的小型映射。也就是说场景从一个可以被查询任意点的连续函数变成了一组稀疏记忆的集合。这两个范式哪个更好答案是都不绝对。连续函数逼近的优势在于你不必关心场景的边界、空洞、遮挡只需要一个统一的数学对象来表达一切它可以在任意空间点上给出合理的插值结果泛化性理论上有保障。缺点是计算复杂度高、高频细节拟合慢。离散查表的优势是存储效率极高、训练速度快、高频细节可以直接存储在表中而不需要网络绞尽脑汁去拟合但副作用是你必须显式定义空间范围、分辨率层数和哈希碰撞策略。我把两种哲学的差异整理成一个表格帮大家直观感受一下维度原始NeRF连续函数逼近Instant-NGP离散查表核心表达MLP权重 (\Theta)多分辨率哈希表 轻型MLP查询方式全网络前向计算哈希查表 三线性插值高频信息位置编码补足拟合难度大高分辨率表项直接存储泛化能力天然连续可插值到未见点依赖插值冲突区域有折中训练速度数十小时数秒至数分钟内存占用中等MLP参数量可控表大小可调4.2 哈希编码的免费午餐稀疏性中的数学巧合哈希编码一个很反直觉的特点在于它用了一个接近均匀分布的哈希函数来存储空间信息却获得了接近显式空间剖分的精度提升。如果按照传统思路要提升空间分辨率的表达精度存储开销是 (O(N^3)) 的立方级增长但Instant-NGP用哈希表把存储压缩到了 (O(T))T是哈希表大小并且取得了几乎一样的精度。这是一个重要的数学性质——空间中的信息往往是高度稀疏的。真实世界的大多数三维场景占据的体素空间远比整个包围盒体积小。一个房间里可能只有一张桌子一把椅子剩下的空间是空气一个雕塑周围的大片空间同样是空白的。如果给每个体素都分配特征向量那大量存储白白浪费在空区域。而哈希编码天然利用了这种稀疏性空区域的体素即使被查询到由于哈希冲突的存在和缺乏监督信号其特征向量也不会被有效优化相当于被忽略了。存储和计算都被聚焦到了真正有内容的表面附近。这就解释了一个有趣的现象为什么Instant-NGP对场景包围盒设置不敏感按道理说你把包围盒拉到很大的范围空间分辨率不变的情况下每个体素的物理尺寸变大精度会下降。但在实际测试中Instant-NGP即使把包围盒扩大两倍重建精度下降也非常有限。原因就是哈希表的大小并不随包围盒扩大而增长——无论空间被划分成多少个虚拟体素最终都映射到同一个固定大小的哈希表。这种无级变焦的能力是传统显式空间剖分方法完全做不到的。4.3 从推理角度看渲染方程的实现也随之变得轻巧数学基础的重构不仅体现在训练阶段还体现在渲染推理阶段。Instant-NGP沿用了NeRF的体渲染volumetric rendering公式[ C(r) \sum_{i1}^{N} T_i \cdot \alpha_i \cdot c_i ]其中 (T_i \exp(-\sum_{j1}^{i-1} \sigma_j \delta_j)) 是透射率(\alpha_i 1 - \exp(-\sigma_i \delta_i)) 是不透明度(c_i) 是颜色。这个公式本身没有变但查询 (\sigma_i) 和 (c_i) 的成本发生了天翻地覆的变化。在原始NeRF中沿着一条光线做一次前向推理可能需要数十次MLP计算每次都要过完整的深层网络在Instant-NGP中碰撞到哈希表 MLP的查询速度极快。更重要的是由于哈希表可以对空区域快速给出密度接近0的结果我们可以用一个很小的球树或者八叉树结构来跳过空采样点进一步提升渲染速度。这就为实时交互浏览提供了可能——你拖拽视角、旋转模型、修改光照参数画面都能实时刷新。你可能在有些文章里看到过这个说法Instant-NGP把NeRF从离线的离线变成了在线的工具。这个在线的含义不仅仅是训练快还包括多次迭代调参成为可能。以往你训练一个NeRF要十几个小时试错成本极高每次调整参数都要等一个晚上现在几秒到几分钟的训练时长让调参-训练-看效果-再调参的循环变得像在线机器学习一样顺畅。这对实际项目的推进效率影响是决定性的。5. 实践要点与踩坑经验我在工程落地中的笔记5.1 关键超参数的作用区间参考在实际运行Instant-NGP或使用其核心思想的三维高斯泼溅、各种NeRF实现时有几个超参数是值得重点关注的。这里结合我自己的使用经验给出一份参考范围参数名默认值推荐范围说明哈希表大小 (|T|)(2^{19})(2^{16} - 2^{22})越小越快越省显存但远小于体素数量时渗色加剧特征维度21 - 8维度越高单表项表达力越强插值越平滑分辨率层数168 - 32越多层越能覆盖更大的范围跨度但参数量线性增长最高分辨率512256 - 2048对应场景中最细的细节受包围盒大小影响SH阶数L2L1 - L4高阶在高光场景有帮助但容易过拟合稳定性下降这里特别提醒一下哈希表大小和最高分辨率的选择需要一起考虑。如果你把最高分辨率设得很高比如2048但哈希表只有 (2^{19}) 条那一定会出现非常严重的哈希冲突高分辨率层的特征基本在互相打架。我的经验是当最高分辨率超过1024时哈希表至少保持 (2^{21}) 以上。5.2 球谐函数在工程中的展开细节虽然SH在概念上很简洁但工程实现里有一些细节很容易踩坑。首先是归一化。方向向量输入球谐函数之前必须确保它是单位向量否则函数值会偏离预期的基函数取值范围。很多NeRF实现里直接从相机射线计算出的方向向量已经归一化但如果你对场景做了缩放或旋转要记得重新归一化。这个细节在调试时特别隐蔽——你可能会看到某个角度的颜色一片漆黑或整体偏色排查半天才发现是输入的((d_x, d_y, d_z))没有归一化。其次是SH系数的初始化。在NeRF训练初期所有SH系数通常初始化为0这样初始颜色输出为0黑色或者某个偏置值。如果你看到训练初期画面发灰、发暗不用慌这是网络在从无到有建立颜色信息。但如果你想要更快的初始收敛把 (c_0^0)零阶系数即球的平均亮度初始化成场景的平均颜色是一个常见的初始化技巧尤其适合背景比较亮的场景。再者是SH系数与高光的相互作用。SH在拟合高光时存在一个限制——高光的角宽通常远小于SH基函数的角分辨率。也就是说L2的SH可以很好地表达大面积的柔和高光但表达细锐的镜面反射点就有困难。一种工程上的补救方法是在颜色网络输出的基础上显式叠加一个高光残差类似Blinn-Phong的高光项。虽然这种方式偏离了纯学习式NeRF的路线但在实际项目中能显著改善金属表面的渲染效果。5.3 不同任务下网格分辨率的调整思路很多初学者拿到Instant-NGP代码直接用它默认的配置跑自己的数据集结果发现效果并不理想。这里要明白一个关键点默认配置是作者在合成数据集和有限真实场景上调出来的不一定适配你的具体场景。如果你跑一个精细的室内场景比如有大量文字或织物质感可以优先考虑把最高分辨率从默认的512提高到1024或2048同时把特征维度从2提高到4。这样模型有了更充足的容量去拟合高频纹理。如果你跑的是大场景重建比如整条街道或整栋建筑一味提高分辨率并不划算。因为大场景的包围盒很大限制细节能力的主要是每个体素的物理尺寸。这种情况下更推荐的做法是把场景分割成多个子块分别训练子模型最后融合。Instant-NGP本身没有多卡并行训练能力但分块训练是完全可行的只是在边界上需要额外的重叠和平滑处理。如果你处理的是动态场景或人有动作的视频序列哈希编码的方法就需要额外谨慎了。哈希表默认只编码空间位置不包含时间维度因此同一空间点在时间序列上的查询结果是完全一样的这会导致动态物体的边缘在移动时出现严重的拖影。你需要在哈希编码之外额外拼接一个时间特征或者将场景按时间段切分、每段时间分别维护一个哈希表。5.4 我对这套数学重构的一点延伸思考聊到最后想多说几句题外话。从球谐函数到哈希编码Instant-NGP重构的不仅仅是NeRF的数学基础更是一种思维方式的转变——从让网络自己搜索规律到先把规律的结构定下来让网络往里面填数据。球谐函数提供了方向维度的先验结构哈希编码提供了空间维度的先验结构。两者都是一种归纳偏置inductive bias把人类图形学几十年来积累的对场景的理解直接注入到网络架构设计中。这种思路其实可以推广到更广泛的领域。任何需要连续函数逼近的高维问题只要我们能找到合理的、可学习的基函数结构或者存储结构都可以借鉴Instant-NGP的这种设计哲学。这两年已经有不少工作在把哈希编码的思想扩展到动态场景重建、物理模拟甚至机器人感知领域这也是它真正的学术影响力所在——它让神经渲染真正变成了一个可以落到工程现场的实用工具而不是只能在论文里欣赏的学术作品。对我来说真正体验了Instant-NGP秒级重建的畅快感之后回头看球谐函数这一整套经典数学工具会有一种全新的感触那些看起来古老、学了也不知道能干嘛的数学其实一直都在等着一个合适的工程场景被重新激活。SH在NeRF里找到新生的故事也许就是最好的例子——你永远不知道下一个交叉领域会带来多大的技术跃迁。