第 8 章:VOX0 区段
Directory Tag: VOX0
作用范围: Chunk 级区段,chunkId >= 0
必须性: 每个 CHIX Chunk 必须存在且仅存在一个
Directory Codec: 必须为 RAW8
内部 Voxel Codec: RAW8 或 RLE8
VOX0 是 VFP 1.3 唯一的权威体素源。它定义 Chunk 内每一个逻辑位置的 Palette Index:0 表示空体素,1..255 表示 PAL0 中定义的颜色条目。任何网格、顶点 Buffer、预览或渲染缓存均不得替代 VOX0。
8.1 权威数据定义
一个 VOX0 区段只描述一个 Chunk。Directory Entry 的 chunkId 必须与 CHIX 中的一条 Entry 相匹配,并且必须与 VOX0 Header 中的 chunkId 相等。
VOX0 的权威内容由以下要素组成:
- Chunk ID;
- 实际 Chunk 尺寸;
- 线性体素顺序;
- 每个体素的 Palette Index;
- 内部体素编码方式。
VOX0 不存储 RGBA、法线、顶点、面片或 GPU 资源。颜色解释由 PAL0 给出;Chunk 空间位置由 CHIX 给出;资产尺寸与坐标系由 META 给出。
8.2 Chunk 数据
8.2.1 VOX0 Payload Layout
VOX0 的 Directory Entry 必须使用 codec = RAW8。VOX0 Payload 自身由固定 32 字节 Header 和可变长度 voxelData 组成。这样读取器始终可以在不解压 Section 外层数据的情况下确认 Chunk 身份和内部编码。
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 2 | uint16 | voxVersion | 必须为 1 |
| 2 | 2 | uint16 | headerSize | 必须为 32 |
| 4 | 4 | int32 | chunkId | 必须等于 Directory 与 CHIX 的 Chunk ID |
| 8 | 1 | uint8 | sizeX | 实际 Chunk X 尺寸 |
| 9 | 1 | uint8 | sizeY | 实际 Chunk Y 尺寸 |
| 10 | 1 | uint8 | sizeZ | 实际 Chunk Z 尺寸 |
| 11 | 1 | uint8 | voxelCodec | 0 = RAW8,1 = RLE8 |
| 12 | 4 | uint32 | voxelCapacity | 必须为 sizeX × sizeY × sizeZ |
| 16 | 4 | uint32 | nonEmptyVoxelCount | 非零 Palette Index 数量 |
| 20 | 4 | uint32 | voxelDataLength | 紧随 Header 的编码数据长度 |
| 24 | 4 | uint32 | reserved0 | 必须为 0 |
| 28 | 4 | uint32 | reserved1 | 必须为 0 |
VOX0 Directory Entry 的 length 必须等于 32 + voxelDataLength,rawLength 必须等于 length。VOX0 的压缩或游程编码仅作用于 voxelData,而不作用于 Header。
8.2.2 线性顺序
对局部坐标 (x, y, z),其线性索引为:
linearIndex = x + sizeX × (y + sizeY × z)
X 最快变化,随后是 Y,最后是 Z。RAW8 解码后的第 linearIndex 个字节即为该局部体素的 Palette Index。
由 CHIX 可得 Chunk 原点后,全局体素坐标为:
globalX = chunkOriginX + x
globalY = chunkOriginY + y
globalZ = chunkOriginZ + z
8.2.3 Palette Index
0 表示空体素。每个非零值必须引用 PAL0 中已定义的条目。即使某颜色 Alpha 为 0,它仍是非空体素;不可用透明颜色代替空体素。
8.3 RAW8
当 voxelCodec = 0 时,voxelData 是恰好 voxelCapacity 字节的连续 Palette Index 数组:
voxelData[0] = voxel(0, 0, 0)
voxelData[1] = voxel(1, 0, 0)
...
voxelData[sizeX - 1] = voxel(sizeX - 1, 0, 0)
voxelData[sizeX] = voxel(0, 1, 0)
...
voxelData[voxelCapacity - 1] = voxel(sizeX - 1, sizeY - 1, sizeZ - 1)
RAW8 规则:
voxelDataLength必须等于voxelCapacity;- 每个字节直接是 Palette Index;
nonEmptyVoxelCount必须等于数组中非零字节的数量;- 编码和解码不允许跳过行、切片或 Chunk 边界。
RAW8 是所有 VFP 1.3 读取器必须支持的内部 VOX0 编码。
8.4 RLE8
当 voxelCodec = 1 时,voxelData 是连续的 Run 对。每个 Run 固定为 2 字节:
| Offset | Size | Type | Field | Description |
|---|---|---|---|---|
| 0 | 1 | uint8 | runLength | 连续体素数,范围 1..255 |
| 1 | 1 | uint8 | paletteIndex | 为这段 Run 重复的 Palette Index |
Run 按 RAW8 的同一线性顺序连续展开。解码器必须从 linearIndex = 0 开始,依次将每个 paletteIndex 写入 runLength 个位置,直到恰好填满 voxelCapacity。
RLE8 规则:
voxelDataLength必须为偶数;runLength不得为0;- 所有 Run 的
runLength之和必须恰好等于voxelCapacity; - 累积长度不得超过
voxelCapacity; paletteIndex为非零时必须在PAL0中定义;- 连续且 Palette Index 相同的 Run 应由写入器合并,除非合并后长度超过 255;
- RLE Run 可以跨越 X 行、Y 切片,但不得跨越 Chunk,因为一个 VOX0 只描述一个 Chunk。
RLE8 的名称指的是 Run 值和 Palette Index 都使用 8 位编码。对于大量空体素、单色面和大块填充区域,RLE8 通常比 RAW8 更小;对于噪声较大或颜色变化频繁的数据,编译器应优先使用 RAW8。
8.5 解码规则
8.5.1 通用解码流程
读取器必须按以下步骤解码 VOX0:
- 通过 Directory Entry 找到
VOX0Payload,并先验证 Section CRC32; - 读取并验证 32 字节 VOX0 Header;
- 比较 Header 的
chunkId、尺寸、容量和非空统计与 CHIX Entry; - 从 Header 后读取恰好
voxelDataLength字节; - 根据
voxelCodec使用 RAW8 或 RLE8 解码出长度为voxelCapacity的 Palette Index 数组; - 验证每个非零索引在 PAL0 中定义;
- 重新统计非零体素并比较
nonEmptyVoxelCount; - 将体素数组与 Chunk 坐标关联,作为该 Chunk 的唯一权威内容。
8.5.2 RAW8 伪代码
function decodeRaw8(data, capacity):
if data.length != capacity:
fail("VOX0_RAW_LENGTH_MISMATCH")
return copy(data)
8.5.3 RLE8 伪代码
function decodeRle8(data, capacity):
if data.length % 2 != 0:
fail("VOX0_RLE_ODD_LENGTH")
output = new uint8[capacity]
cursor = 0
for offset from 0 to data.length - 1 step 2:
runLength = data[offset]
paletteIndex = data[offset + 1]
if runLength == 0:
fail("VOX0_RLE_ZERO_RUN")
if cursor + runLength > capacity:
fail("VOX0_RLE_OVERFLOW")
fill(output, cursor, runLength, paletteIndex)
cursor += runLength
if cursor != capacity:
fail("VOX0_RLE_UNDERFLOW")
return output
8.5.4 验证与错误处理
VOX0 验证器必须检查:
- Directory Entry 的
chunkId >= 0、Tag 为VOX0、Codec 为RAW8; - VOX0 Header 版本、长度和保留字段;
- Header 与 CHIX Entry 的 ID、尺寸、容量和非空统计一致;
voxelCapacity = sizeX × sizeY × sizeZ;voxelDataLength与 Directory Entry 长度一致;voxelCodec为0或1;- RAW8 或 RLE8 按各自规则无损解码为准确容量;
- 非空体素数正确,且所有非零 Palette Index 在 PAL0 中定义。
VOX0 是权威数据。任何 Header 不一致、编码不完整、RLE 溢出/不足、未知编码或未定义 Palette Index 都必须使对应资产无效;读取器不得用网格或预览来修复它。
本章总结
VOX0 以每 Chunk 的 Palette Index 序列无损表达 VFP 的真实体素内容。RAW8 提供简单稳定的基础实现,RLE8 为稀疏或大色块 Chunk 提供紧凑编码;两者都必须产生相同的线性 Palette Index 结果。