☰
为什么高斯点要用Morton码排序?揭秘splat-transform的空间局部性技巧
2026/10/12 20:08:02 网站建设 项目流程

【免费下载链接】splat-transform

CLI tool and library for 3D Gaussian splat processing and conversion

项目地址:https://gitcode.com/gh_mirrors/sp/splat-transform
点击查看免费下载

如果你处理过大型 3D 高斯 splat 文件,大概率遇到过这样的现象:同样是几千万个点,有的文件加载快、压缩比高、LOD 切换流畅,有的却怎么都不行。splat-transform 给出了一个朴素却极其有效的答案——用Morton 码排序(Z-order 曲线)重排高斯点,换取更好的空间局部性。

本文带你搞清楚三件事:Morton 码到底是什么、为什么它能让高斯点云"听话",以及 splat-transform 是如何在一条命令行里完成这套排序的。

什么是 Morton 码:把三维空间"折叠"成一条线

想象你有一张 4×4×4 的三维网格,想把里面的格子排成一条一维的线。最笨的办法是沿 x 轴排完再换行,但这样相邻格子经常"跳"到很远的地方。

Morton 码的做法很巧妙:把三个坐标的二进制位交错排列。比如坐标(x, y, z)的位序x2 x1 x0/y2 y1 y0/z2 z1 z0,交错后得到z2 y2 x2 z1 y1 x1 z0 y0 x0。空间上相邻的点,其 Morton 码数值差通常很小;数值接近的点,空间上也往往挨着。

这样,三维的"邻居关系"就被"折叠"进了一维的"先后关系"。在 splat-transform 源码中,这个编码逻辑见 morton-order.ts:先将每根轴归一化到 0~1023(10 位),再用位运算把三位坐标交织成一个 30 位的 Morton 码。

为什么高斯点需要按 Morton 码排序

训练出来的原始 splat 文件(比如 PLY)里的点,顺序往往由训练过程决定,本质上接近随机。这带来几个实际问题:

场景随机顺序的痛点Morton 排序后
顺序读盘/读内存缓存命中差,随机跳跃访问相邻索引 = 相邻空间位置,预读命中率高
分块(chunk)存储一个块可能散落在全场景每个块只覆盖一小块区域,按需加载更精准
体块/八叉树构建块归属分散,树构建低效同父节点的块在码序上天然聚在一起
简化(decimate)读到的行碎片化,空间相干性差每个块的源行形成少数连续段,读放大显著下降

一句话:排序不改变任何点的属性,只改变它们的存放顺序,却能让后续所有"按空间范围取数据"的操作都变成顺序访问。这就是所谓空间局部性。

一条命令完成 Morton 码排序

splat-transform 的 CLI 把这件事包装成了一个 action:-m, --morton-order。

splat-transform input.ply -m output.ply
  • 输入可以是任意已支持格式(.ply、.spz、.sog等),-m会在流水线中重排所有高斯点;
  • 排序基于点当前的世界坐标,若之前做过-t/-r/-s变换,记得按"先变换、后排序"的顺序书写;
  • 处理逻辑见 process.ts:先生成 0..N-1 的索引数组,调用sortMortonOrder原地排好序,再用permuteRowsInPlace一次性置换整张数据表,无需逐行拷贝。

数据表层面的适配器在 morton-order.ts,只需 x/y/z 三列即可工作。

源码拆解:30 位码 + 基数排序 + 递归细化

splat-transform 的排序核心在 morton-order.ts,设计上有三个值得注意的点:

  1. 自适应量化:先扫一遍点云求出包围盒(min/max),再按各轴实际跨度把坐标映射到 10 位整数,保证分辨率"用满"而不是被固定网格浪费。

  2. LSD 基数排序:30 位 Morton 码拆成三段,做三轮各 10 位的稳定基数排序(前缀和定桶 + 稳定散列),复杂度 O(n),避免逐对比较的 O(n log n) 开销。稳定排序保证码相同的点保持原始相对顺序。

  3. 递归细化(这是精髓):1024×1024×1024 的网格装不下千万级点,同一码下可能挤着几百个点。实现里会找出所有"码相同且超过 256 个"的桶,对这个子区间重新求包围盒、重新量化、再排一遍——相当于对拥挤区域自动放大地图,直到点被真正分开。见 morton-order.ts。

同一套内核还提供列式与交错式两个入口(sortMortonColumns/sortMortonInterleaved),分别服务数据表列存储和 GPU 纹理行缓冲,避免无谓的数据重排拷贝。

Morton 码在 splat-transform 里的"隐藏用途"

-m只是显式入口,Morton 排序其实贯穿了多条输出管线:

  • Compressed PLY 默认就排好序:写出压缩 PLY 前会先做一次 Morton 排序,让每个压缩 chunk 只覆盖场景的一小块区域。见 write-compressed-ply.ts。
  • LOD 分块靠它定位:生成 Streamed SOG(lod-meta.json)时,按空间 extent 划分 chunk 的 partition/bounds 流程全部依赖 Morton 有序的数据。见 write-lod.ts。
  • 稀疏八叉树的骨架:碰撞体输出的.voxel.json把体块坐标编码成 51 位 Morton 码(每轴 17 位,支持 13 万块/轴),码序即八叉树的紧凑遍历顺序。见 morton.ts 与 sparse-octree.ts。
  • 简化管线的"体检报告":decimate 的分区模块会统计每个块的源行形成多少连续段(coherence runs)——Morton 有序的文件每个块只有少数几段,而训练顺序文件几乎一行一段。此时它会建议你做一次--morton-order预排序。见 partition.ts。

小结

回到开头的问题:为什么高斯点要用 Morton 码排序?因为空间局部性是图形数据的第一生产力。一次 O(n) 的重排,就能让后续读盘、压缩、LOD 分块、八叉树构建、几何简化全部受益,而点的颜色、形状、位置一个都没变。

如果你的 splat 文件体积偏大、加载偏慢,不妨先试一条命令:

splat-transform input.ply -m output.ply

再配合--stats观察各列统计,感受空间有序带来的变化。更多用法可查阅 README.md 中的 Actions 与 CLI Options 章节。

【免费下载链接】splat-transform

CLI tool and library for 3D Gaussian splat processing and conversion

项目地址:https://gitcode.com/gh_mirrors/sp/splat-transform
点击查看免费下载

相关推荐

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询