跳到主要内容

第 8 章:VOX0 区段

Directory Tag: VOX0
作用范围: Chunk 级区段,chunkId >= 0
必须性: 每个 CHIX Chunk 必须存在且仅存在一个
Directory Codec: 必须为 RAW8
内部 Voxel Codec: RAW8RLE8

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 身份和内部编码。

OffsetSizeTypeFieldDescription
02uint16voxVersion必须为 1
22uint16headerSize必须为 32
44int32chunkId必须等于 Directory 与 CHIX 的 Chunk ID
81uint8sizeX实际 Chunk X 尺寸
91uint8sizeY实际 Chunk Y 尺寸
101uint8sizeZ实际 Chunk Z 尺寸
111uint8voxelCodec0 = RAW81 = RLE8
124uint32voxelCapacity必须为 sizeX × sizeY × sizeZ
164uint32nonEmptyVoxelCount非零 Palette Index 数量
204uint32voxelDataLength紧随 Header 的编码数据长度
244uint32reserved0必须为 0
284uint32reserved1必须为 0

VOX0 Directory Entry 的 length 必须等于 32 + voxelDataLengthrawLength 必须等于 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 字节:

OffsetSizeTypeFieldDescription
01uint8runLength连续体素数,范围 1..255
11uint8paletteIndex为这段 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:

  1. 通过 Directory Entry 找到 VOX0 Payload,并先验证 Section CRC32;
  2. 读取并验证 32 字节 VOX0 Header;
  3. 比较 Header 的 chunkId、尺寸、容量和非空统计与 CHIX Entry;
  4. 从 Header 后读取恰好 voxelDataLength 字节;
  5. 根据 voxelCodec 使用 RAW8 或 RLE8 解码出长度为 voxelCapacity 的 Palette Index 数组;
  6. 验证每个非零索引在 PAL0 中定义;
  7. 重新统计非零体素并比较 nonEmptyVoxelCount
  8. 将体素数组与 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 长度一致;
  • voxelCodec01
  • RAW8 或 RLE8 按各自规则无损解码为准确容量;
  • 非空体素数正确,且所有非零 Palette Index 在 PAL0 中定义。

VOX0 是权威数据。任何 Header 不一致、编码不完整、RLE 溢出/不足、未知编码或未定义 Palette Index 都必须使对应资产无效;读取器不得用网格或预览来修复它。


本章总结

VOX0 以每 Chunk 的 Palette Index 序列无损表达 VFP 的真实体素内容。RAW8 提供简单稳定的基础实现,RLE8 为稀疏或大色块 Chunk 提供紧凑编码;两者都必须产生相同的线性 Palette Index 结果。