跳到主要内容

体素格式

本页为体素格式 v1.1。输出以 .voxel.json 结尾时由 splat-transform 写出,Viewer 等运行时用作碰撞和射线。

用 Laine–Karras 节点布局的稀疏八叉树保存实心/空体素:JSON 描述网格,紧凑二进制保存树。

生成步骤见碰撞指南。

文件集合​

同前缀文件对,以及可选网格:

scene.voxel.json # 头部:边界、分辨率、树统计(本规范)
scene.voxel.bin # 二进制八叉树数据(nodes 与 leafData)
scene.collision.glb # 从体素提取的可选三角网格
  • 头名 *.voxel.json,将后缀替为 .voxel.bin 推导二进制名,头不显式引用。
  • 可选 .collision.glb 是标准 glTF,此处不展开。

*.voxel.json​

interface VoxelMeta {
version: string; // file format version, decimal string, e.g. "1.1"
asset?: {
generator?: string; // tool/version that produced the file, e.g. "splat-transform v3.1.6"
};
gridBounds: { // AABB of the voxel grid, aligned to 4-voxel block boundaries
min: number[]; // [x, y, z] — world position of the min corner of voxel (0, 0, 0)
max: number[]; // [x, y, z]
};
sceneBounds: { // AABB of the source Gaussians (informational)
min: number[];
max: number[];
};
voxelResolution: number; // edge length of one voxel, world units
leafSize: number; // voxels per leaf-block edge; always 4
treeDepth: number; // subdivision levels from the root cube down to leaf blocks (>= 1)
numInteriorNodes: number; // number of interior nodes in `nodes`
numMixedLeaves: number; // number of mixed leaves in `nodes`
nodeCount: number; // total uint32 entries in the `nodes` array
leafDataCount: number; // total uint32 entries in the `leafData` array (= 2 * numMixedLeaves)
}
  • gridBounds 定义权威坐标范围。体素 vx/vy/vz 从 gridBounds.min + (vx, vy, vz) · voxelResolution 到 gridBounds.min + (vx+1, vy+1, vz+1) · voxelResolution。各轴为完整 4×4×4 块数:nb = round((max − min) / (4 · voxelResolution))。
  • sceneBounds 记录源场景,各位置按旋转缩放范围及 sigma 截断扩展,仅供参考;依填充、雕刻、裁剪,gridBounds 可更大或更小。
  • nodeCount 为 numInteriorNodes、numMixedLeaves 与未独立保存的实心叶数量之和。

坐标空间​

全部边界使用 PlayCanvas 渲染坐标系,相比源 PLY 差一个标准导入变换,即绕 Z 180 度。

*.voxel.bin​

两个小端 uint32 数组串接,无头无填充:

字节偏移字节长度内容
0nodeCount * 4nodes
nodeCount * 4leafDataCount * 4leafData

总大小恰为 (nodeCount + leafDataCount) * 4。无实心时两计数零,二进制空。

八叉树​

每轴 2^treeDepth 个块,每块 leafSize³,即 4×4×4,从 gridBounds.min 开始。轴块数不是相同 2 次幂时,根立方体超出 max,多余区域始终空,父掩码中无对应子项。

treeDepth 深度为 4×4×4 叶块,nodes[0] 为根。

八分区编号​

按轴半区编号,X 最低位:

oct = x | (y << 1) | (z << 2) // x、y、z 在下半区为 0,在上半区为 1

与 Morton 序一致,从根到块 bx/by/bz 按高位到低位消费坐标位。深度 d(根 0)时,oct = ((bx >> s) & 1) | (((by >> s) & 1) << 1) | (((bz >> s) & 1) << 2),s = treeDepth − 1 − d。

节点字​

每条 uint32 按顺序测试:

条件类型含义
word == 0xFF000000实心叶全体积实心,无子或叶数据,任意深度,表示折叠子树。
(word >>> 24) == 0混合叶word 是叶对索引 i,低高掩码为 leafData[2i]、leafData[2i+1],只在 treeDepth。
其他内部节点childMask = word >>> 24,每八分区一位;firstChild = word & 0xFFFFFF,索引 nodes。

编码无歧义:

  • 内部节点至少一子,顶部字节不为零,零唯一识别混合叶。
  • 广度优先且子始终在父后,firstChild 不为零,0xFF000000 唯一识别实心叶。

子节点​

childMask 已置位子节点从 nodes[firstChild] 连续保存,按八分区升序。oct 位存在时位置为:

nodes[firstChild + popcount(childMask & ((1 << oct) - 1))]

未置位表示该八分区全空,不保存节点。

混合叶掩码​

64 个体素以两个 uint32 保存 64 位占用。局部坐标 lx/ly/lz 各在 [0,4):

bit = lx + (ly << 2) + (lz << 4)
solid = bit < 32 ? (lo >>> bit) & 1 : (hi >>> (bit - 32)) & 1

leafData[2i] 低 0–31 位,下一项高 32–63 位,置位为实心。混合掩码不会全零或全一,分别编码为缺失分区或实心叶。

查询域​

gridBounds 外不属于格式定义域,约定依用途。导航碰撞运行时如 Viewer 将其视为实心。导航输出依赖此行为,写入器正因如此裁掉可导航区之外全实体块。

限制​

firstChild 与混合叶索引为 24 位,上限各 16,777,216 节点/混合叶,写入器超出时报错。

JSON 示例​

5 厘米分辨率、块 0.2、32×14×32 块:

{
"version": "1.1",
"asset": {
"generator": "splat-transform v3.1.6"
},
"gridBounds": {
"min": [-3.2, -0.2, -3.2],
"max": [3.2, 2.6, 3.2]
},
"sceneBounds": {
"min": [-3.13, -0.08, -3.07],
"max": [3.11, 2.49, 3.08]
},
"voxelResolution": 0.05,
"leafSize": 4,
"treeDepth": 5,
"numInteriorNodes": 1201,
"numMixedLeaves": 5678,
"nodeCount": 9232,
"leafDataCount": 11356
}

版本与兼容​

  • version 为十进制 major.minor 字符串,本规范 1.1,应拒绝更高主版本。
  • 1.0 → 1.1:旧版源 PLY 坐标,1.1 改引擎坐标,二进制不变,见坐标。
  • asset 在 1.1 发布后加入,早期文件可能缺失。
  • 忽略未知字段,允许兼容增量。

另请参见​