第 1 章:快速开始
规范状态: Draft
本章范围: 本章说明 VFP 的典型工作流、VF 到 VFP 的编译关系、最小可用文件与标准加载流程。它不替代后续章节对二进制字段、区段 Payload 和校验算法的精确定义;如本章与后续字段定义冲突,以后续字段定义为准。
VFP(Voxel Format Package)是面向单体体素资产的二进制包格式。它将资产的权威体素数据、编辑加速数据、运行时渲染缓存和展示附加信息放入同一个可随机访问的容器中,使同一资产既可被准确编辑,也可被高效加载和渲染。
VFP 1.3 的根本原则是:
VOX0是唯一权威体素源。MSH0、VBUF、PMSH、RND0、ANM0、BORD、PRVW和THMB都是可删除、可重新生成的派生数据。
因此,VFP 不是仅用于预览的网格格式,也不是只方便编辑的逐体素文本格式;它是两者之间的资产交付层。
1.1 VFP 工作流
1.1.1 资产生命周期
一个 VFP 资产通常经历创作、编译、分发、加载、编辑和重新保存六个阶段:
各阶段的职责如下:
| 阶段 | 输入 | 输出 | 主要责任 |
|---|---|---|---|
| 创作 | 图片、AI 结果、VF、模型导入或编辑器内部数据 | 原始体素描述 | 表达“哪些体素存在、各自是什么颜色或属性” |
| 规范化 | 原始体素描述 | 规范化资产数据 | 统一坐标、颜色、空体素和边界表达 |
| 编译 | 规范化资产数据 | .vfp | 生成权威区段、目录、校验和可选缓存 |
| 分发 | .vfp | 本地文件、资源包或网络对象 | 保存、传输、版本管理与按需获取 |
| 加载 | .vfp | 内存中的体素资产和渲染资源 | 验证、解析、选择可用缓存并渲染 |
| 编辑 | 已加载资产 | 修改后的权威数据和失效缓存 | 只更新受影响体素、Chunk 与缓存 |
1.1.2 数据层次
VFP 将同一资产拆分为不同可信度和不同生命周期的数据层。实现者必须明确区分这些层,避免把缓存误当作源数据。
| 数据层 | 内容 | 主要区段 | 可否作为资产真实性来源 | 缺失时的处理 |
|---|---|---|---|---|
| 容器层 | 文件版本、区段目录、偏移、长度和校验 | Header、DIR0、Footer | 否 | 无法定位资产,应拒绝文件 |
| 权威资产层 | 尺寸、调色板、分块和体素内容 | META、PAL0、CHIX、VOX0 | 是 | 无法正确还原资产,应拒绝文件 |
| 编辑缓存层 | 面计划、贪心合面、局部编辑辅助数据 | MSH0 | 否 | 从 VOX0 重建 |
| 表现缓存层 | 顶点、索引、渲染批次、描边、动画和预览 | VBUF、PMSH、RND0、ANM0、BORD、PRVW、THMB | 否 | 忽略、降级或重建 |
读取器可以根据自身能力选择性加载缓存。例如文件列表可优先读取 THMB,移动端渲染器可优先读取 VBUF,通用编辑器可优先读取 MSH0,不支持体素渲染的兼容工具可以读取 PRVW。但无论选择哪条性能路径,资产语义必须由权威资产层确定。
1.1.3 VFP 的典型使用场景
VFP 1.3 适合以下场景:
- AI 将图像、文字描述或多视角输入生成体素资产后,输出可编辑、可交付的文件;
- 用户在体素编辑器中制作角色、宠物、家具、道具或建筑组件;
- 应用希望在首次加载后直接复用已生成的网格或 GPU 数据,减少逐体素合面开销;
- 资源系统希望按 Chunk 局部读取、局部编辑和局部更新模型;
- 不同平台或引擎之间需要交换原始体素资产,同时允许携带各自适用的表现缓存;
- 内容平台希望保存可靠的资产源,并在缓存版本过期时可重新构建预览和渲染数据。
VFP 1.3 不用于直接存储大型开放世界或无限地形。它的定位是“一个可独立使用的体素资产包”。由多个资产组成的场景或世界应在 VFP 之外通过场景系统、资源索引或未来的 VWFP 格式进行组织。
1.1.4 Chunk 工作方式
VFP 将资产逻辑网格切分为多个空间 Chunk。每个 Chunk 对应一个局部体素区域,并可拥有独立的 VOX0、MSH0、VBUF 或 PMSH 区段。
Chunk 的价值在于:
- 按需加载: 读取器只读取当前需要显示或编辑的空间区域;
- 局部编辑: 修改一个体素时,只重建所在 Chunk 及其受边界影响的邻接 Chunk;
- 局部缓存: 不必为整个资产一次性保存或更新所有网格数据;
- 增量存储: 写入器可以在追加式保存时只新增受影响区段与新的 Directory。
CHIX 负责描述 Chunk 的编号、坐标、尺寸和存在关系;VOX0 负责存储每个 Chunk 中的权威体素数据。Chunk 的默认推荐尺寸为 16 × 16 × 16,但最终可用尺寸、边缘 Chunk 规则和坐标映射以后续 META 与 CHIX 章节为准。
1.1.5 编辑后的更新原则
编辑器修改资产时,应遵循“先更新权威数据,再更新缓存”的顺序:
- 修改目标体素的存在性、颜色索引或其他权威属性;
- 更新对应
VOX0Chunk; - 更新必要的
META统计信息与 Source Hash; - 标记受影响的
MSH0、VBUF、PMSH、RND0和BORD为失效; - 立即重建、延迟重建或删除这些缓存;
- 写入新的 Directory 与 Footer,使新版本成为文件当前有效状态。
编辑器不得只修改网格或 GPU Buffer 后保存为 VFP。这样会导致显示结果与 VOX0 不一致,并破坏其他编辑器、验证器和运行时的兼容性。
1.2 VF 到 VFP 编译流程
1.2.1 VF 与 VFP 的职责边界
VF(Voxel Format)是推荐的上游体素描述格式。VF 通常为易读、易生成、易修改的 JSON 文档,适合 AI 生成、脚本处理、创作存档和跨工具交换。VFP 则是编译后的二进制资产包,适合加载、渲染、缓存和分发。
两者关系如下:
| 对比项 | VF | VFP |
|---|---|---|
| 主要用途 | 创作、生成、交换、调试 | 交付、运行时加载、编辑加速、分发 |
| 常见编码 | JSON / UTF-8 | 二进制容器 |
| 可读性 | 高 | 低,需要解析器 |
| 运行时性能 | 通常需要重新解析和编译 | 可直接读取权威数据和可选缓存 |
| 是否必须携带缓存 | 否 | 可选,但支持携带 |
| 是否唯一来源 | 否,VFP 可来自其他输入 | 否,VFP 不要求必须由 VF 生成 |
VF 是推荐输入,而非 VFP 的唯一合法来源。扫描、程序化生成、第三方模型导入、编辑器内部状态或网络服务输出,都可以直接编译为合规 VFP。
1.2.2 编译目标
VFP 编译器的最低目标是生成一个能被最小 VFP 读取器加载的资产包。编译输出必须包含:
- 文件 Header;
META;PAL0;CHIX;- 一个或多个
VOX0; DIR0;- Footer。
编译器可按目标平台、性能档位和资产用途额外生成 SCNE、MSH0、VBUF、PMSH、RND0、ANM0、BORD、PRVW、THMB 等区段。
1.2.3 编译步骤
推荐的编译流程如下:
各步骤的要求如下。
第一步:解析与语义校验
编译器读取输入后,必须确认至少存在以下语义信息:
- 资产的三维体素网格尺寸;
- 每个非空体素的位置;
- 每个非空体素对应的颜色或可映射到颜色的值;
- 坐标系或能够转换为 VFP 坐标系的信息。
输入中的越界体素、重复体素、非法颜色、非有限数值或无法确定尺寸的数据,必须在编译阶段被拒绝或以明确规则修正。编译器不应把不确定的解释留给 VFP 读取器。
第二步:规范化体素网格
编译器必须将输入转换到 VFP 的统一体素坐标语义:右手坐标系、单位立方体体素、原点与尺寸由资产元数据定义。体素坐标必须落在资产网格范围内。
VFP 1.3 的单轴逻辑尺寸不得超过 255。若输入超过此范围,编译器必须拒绝、拆分为多个资产,或改用不属于本规范的世界/大型资产流程;不得静默截断坐标或将其取模。
第三步:构建调色板
VFP 的 VOX0 不直接保存 RGBA,而是保存 uint8 调色板索引。编译器必须:
- 保留索引
0用于空体素; - 将所有非空体素映射到
1..255的颜色索引; - 生成对应的
PAL0颜色条目; - 在超过 255 种可引用颜色时,执行明确的量化、拆分或拒绝策略;
- 确保任何
VOX0中出现的非零索引都在PAL0中有定义。
为了获得可重复输出,编译器应采用稳定的调色板顺序,例如按首次出现顺序、固定色彩空间排序或明确的输入索引排序。不能依赖哈希表遍历顺序。
第四步:切分 Chunk
编译器根据目标 chunkSize 将逻辑网格划分为 Chunk。每个 Chunk 必须拥有稳定的 Chunk ID 与空间坐标;同一规范化输入在相同参数下应产生相同的 Chunk 切分和编号。
空 Chunk 是否写入 CHIX 由后续 CHIX 章节定义。推荐做法是仅为包含非空体素或承担明确逻辑作用的 Chunk 建立条目,以降低文件体积和目录数量。
第五步:编码 VOX0
对每个输出 Chunk,编译器按规定的线性顺序将每个体素写为 Palette Index。VFP 1.3 的基本线性顺序是:X 最快变化、随后 Y、最后 Z。
编译器可选择 RAW8 或 RLE8:
RAW8直接写入每个体素的uint8Palette Index,结构简单、随机访问友好;RLE8对连续相同索引进行游程编码,通常适合大量空体素或大色块的 Chunk。
所有 VOX0 编码选择必须由 Directory Entry 的 Codec 明确标识。编译器不得使用未声明的私有压缩方式。
第六步:生成 META、CHIX 与 Source Hash
META 应记录资产的基础信息,例如逻辑尺寸、坐标系、体素统计、Chunk 尺寸、资产标识和生成信息。CHIX 应记录每一个 Chunk 的索引信息。
Source Hash 必须在权威数据最终确定后计算。它用于确认缓存对应的是否是当前 META、PAL0、CHIX 和 VOX0。任何会改变资产真实体素内容的修改,都必须导致 Source Hash 改变。
第七步:生成可选缓存
缓存不是最小编译目标的必要条件。编译器应按产品需求选择生成:
| 缓存 | 适合的编译目标 |
|---|---|
MSH0 | 需要高频体素编辑、局部合面和选取的编辑器 |
VBUF | 需要快速 GPU 上传的特定运行时 |
PMSH | 需要向通用网格管线输出的环境 |
RND0 | 有固定渲染器、批次策略或 LOD 策略的产品 |
BORD | 需要描边、轮廓或特定像素风表现的产品 |
PRVW | 需要缩略图或不支持体素渲染的兼容预览环境 |
每个缓存必须记录或可关联到生成它时的 Source Hash。缓存与当前 Source Hash 不匹配时,读取器必须将其视为失效。
第八步:写入容器
写入器先写 Header 和全部区段 Payload,再写 DIR0,最后写 Footer。Directory 记录所有区段的 Tag、Codec、Chunk ID、Flags、Offset、Length、Raw Length 和 CRC32。Footer 记录当前有效 Directory 的位置与长度。
写入器必须保证所有 Offset、Length、对齐、CRC32 和保留字段符合本规范。文件完成后,应使用独立验证器或等价校验逻辑回读一次,确认输出是合规 VFP。
1.2.4 编译档位
产品可定义编译档位,但所有档位均必须保留完整权威数据。
| 档位 | 输出内容 | 适合场景 |
|---|---|---|
| Source | 仅最小权威区段 | 云端归档、版本管理、后续再编译 |
| Edit | 权威区段 + MSH0 | 编辑器、局部修改、高频创作 |
| Runtime | 权威区段 + 运行时所需缓存 | 移动端、游戏、实时展示 |
| Distribution | 权威区段 + 编辑/运行缓存 + PRVW | 跨平台分发、内容社区、离线资源包 |
“Runtime”或“Distribution”档位不能省略 VOX0 后只交付网格。那会将文件降级为预烘焙表现数据,失去 VFP 的可编辑和可恢复特性。
1.3 最小 VFP 文件
1.3.1 最小文件的定义
最小 VFP 文件是指:在不携带任何编辑缓存、运行时缓存或预览数据的情况下,仍能被合规最小读取器验证、解析并还原资产的 .vfp 文件。
它必须包含:
| 顺序要求 | 组成部分 | 是否必须 | 作用 |
|---|---|---|---|
| 固定 | Header | 是 | 识别文件类型与基础版本 |
| 任意 | META | 是 | 解释资产尺寸、坐标与基础属性 |
| 任意 | PAL0 | 是 | 定义 Palette Index 所指向的颜色 |
| 任意 | CHIX | 是 | 定义资产包含哪些 Chunk |
| 任意 | VOX0 | 是,一个或多个 | 保存每个 Chunk 的权威体素内容 |
| 接近结尾 | DIR0 | 是 | 定位以上所有区段 |
| 文件结尾 | Footer | 是 | 定位当前有效 Directory |
Data Section 的物理顺序并不表达语义。上表的“任意”表示 META、PAL0、CHIX 与 VOX0 可以以任何顺序存在于数据区;读取器必须通过 Footer 和 DIR0 定位它们。
1.3.2 最小文件布局
┌─────────────────────────────────────────┐
│ Header │
├─────────────────────────────────────────┤
│ META Payload │
├─────────────────────────────────────────┤
│ PAL0 Payload │
├─────────────────────────────────────────┤
│ CHIX Payload │
├─────────────────────────────────────────┤
│ VOX0 Payload for Chunk 0 │
├─────────────────────────────────────────┤
│ VOX0 Payload for Chunk N (可选) │
├─────────────────────────────────────────┤
│ DIR0 Header + Directory Entries │
├─────────────────────────────────────────┤
│ Footer │
└─────────────────────────────────────────┘
实际文件允许在区段之间插入对齐填充,并允许存在更多 VOX0 Chunk。Footer 必须位于最后 64 字节;读取器不得通过扫描整个文件猜测 Directory 的位置。
1.3.3 最小资产示例
以下概念示例描述一个边长为 16 的单 Chunk 资产:
- 逻辑网格尺寸:
16 × 16 × 16; - Chunk 尺寸:
16 × 16 × 16; - Chunk 数量:
1; - 调色板:索引
0为空,索引1为红色,索引2为蓝色; VOX0:保存 4096 个 Palette Index,或保存能够无损解码出该 4096 个值的 RLE8 数据;- 不携带
MSH0、VBUF、PMSH、RND0与PRVW; - Directory 中恰好包含
META、PAL0、CHIX和VOX0四个数据区段; - Footer 指向该 Directory。
这个文件虽然不含任何网格缓存,但任何最小读取器都可以:读取 VOX0、查找 PAL0、根据 META 和 CHIX 恢复体素网格,并自行生成渲染网格。
1.3.4 最小文件的约束
最小 VFP 文件必须同时满足以下条件:
- Header 和 Footer 的 Magic、版本、固定长度和保留字段有效;
- Footer 指向一个有效
DIR0; DIR0的每个 Directory Entry 指向有效且互不重叠的 Payload 范围;META、PAL0和CHIX均恰好存在一次;- 每个
CHIX中声明的 Chunk 均恰好对应一个VOX0; - 每个
VOX0的 Chunk ID 与对应CHIX项目一致; - 所有非零 Palette Index 都在
PAL0中有定义; - VOX0 解码后的体素数量、Chunk 尺寸和坐标范围一致;
- 每个区段的 CRC32 可验证;
- 缓存区段即使完全不存在,也不影响资产可用性。
1.3.5 最小读取器要求
要称为 VFP 1.3 最小读取器,软件至少必须能够:
- 读取 Header、Footer 和
DIR0; - 验证区段范围、区段 CRC32 与必需区段存在性;
- 解析
META、PAL0、CHIX和VOX0; - 支持
RAW8编码的VOX0; - 将 Palette Index
0解释为空体素; - 忽略它不支持的可选缓存区段;
- 在权威数据缺失、损坏、越界或互相冲突时拒绝资产,而不是用缓存猜测结果。
最小读取器不要求支持预览图、动画、描边、GPU Buffer 或指定渲染器的派生数据。
1.4 加载流程
1.4.1 加载原则
VFP 的读取入口是 Footer,而不是 Header 后的第一个区段。读取器必须先从文件结尾定位有效 Directory,再按 Directory 按需读取数据。
这种设计使 VFP 可以支持:
- 区段任意排序;
- 缓存按需加载;
- 追加式保存;
- 旧 Directory 保留;
- 不重写整个文件的局部更新;
- 对未知可选区段的安全忽略。
1.4.2 标准加载步骤
合规读取器应按以下顺序执行:
步骤 1:打开文件并进行基础检查
读取器应首先取得文件实际长度,并拒绝短于 Header 加 Footer 最小长度的文件。随后读取开头固定长度的 Header,检查:
- Magic 是否为
VFPK; - 主版本是否受支持;
- Header Size 是否符合当前版本;
- 未定义的强制特性位是否被设置;
- 保留字段是否符合规则。
Header 只用于识别文件和进行早期版本判断,不能用于定位数据区段。
步骤 2:读取 Footer
读取器必须读取文件最后固定长度的 Footer,检查:
- Magic 是否为
VFPF; - Footer Size 是否正确;
- Footer 中声明的文件长度是否等于实际文件长度;
directoryOffset和directoryLength是否位于 Header 之后、Footer 之前;- Directory 范围计算是否发生整数溢出;
- Footer 保留字段是否符合规则。
若文件曾经被追加式保存,文件中可能存在更早的 Directory 和 Footer;读取器只能使用最后一个 Footer 所指向的 Directory。
步骤 3:读取并验证 Directory
读取器根据 Footer 读取 DIR0,并验证:
DIR0Magic、版本、Header Size 和 Entry Size;- Directory 总长度是否等于 Header 与 Entry 数量所推导的长度;
- Directory 的 CRC32;
- 每个 Entry 的 Tag、Codec、Chunk ID、Flags、Offset、Length、Raw Length 和 CRC32 字段;
- 所有 Payload 范围是否位于数据区,且不与彼此、Header、Directory 或 Footer 交叠;
- 必需全局区段是否存在且唯一;
- Chunk 级区段是否具有有效的 Chunk ID。
此阶段不必立即读取所有 Payload。读取器可以只建立 Tag 与 Chunk ID 到 Directory Entry 的索引。
步骤 4:加载权威全局数据
读取器必须先加载 META、PAL0 与 CHIX,并执行区段级 CRC32、Codec 和长度验证。
此时读取器应获得:
- 资产的逻辑网格尺寸与坐标系统;
- 调色板条目与其属性;
- Chunk 尺寸、Chunk 数量、Chunk ID 与空间位置;
- 当前 Source Hash 或计算它所需的信息。
如果三者之间存在无法解释的矛盾,例如 CHIX 声明的 Chunk 坐标超出 META 网格、Palette 数量与 VOX0 索引约束不一致,读取器必须拒绝资产。
步骤 5:按需加载 VOX0
读取器根据场景需要选择要加载的 Chunk。例如:
- 预览器可先加载全部 Chunk;
- 编辑器可加载当前选择区域与相邻 Chunk;
- 流式运行时可只加载可见 Chunk;
- 校验器必须检查所有 Chunk。
对每个 VOX0,读取器必须验证该 Entry 的 Chunk ID 存在于 CHIX,然后使用 Entry 的 Codec 解码 Payload。解码后必须检查:
- 输出长度是否等于
rawLength; - 体素数量是否符合该 Chunk 的实际尺寸;
- 每个 Palette Index 是否在合法范围内;
- 边缘 Chunk 的无效空间是否未被误解释为有效体素;
- 线性体素顺序是否按规范解释。
读取器不得从 PMSH、VBUF 或 PRVW 反推出缺失的 VOX0。如果所需 Chunk 的 VOX0 无法验证,资产或该 Chunk 必须被报告为不可用。
步骤 6:选择缓存路径
权威数据可用后,读取器可选择最适合自身的缓存:
| 读取器类型 | 优先数据路径 | 回退路径 |
|---|---|---|
| 体素编辑器 | MSH0 + VOX0 | 从 VOX0 重新合面 |
| 通用实时渲染器 | VBUF / RND0 | 从 VOX0 或 MSH0 构建网格 |
| 网格导出工具 | PMSH | 从 VOX0 生成面片 |
| 兼容预览器 | PRVW | 使用权威数据生成简化预览 |
| 验证器 | 全部权威区段及可检查缓存 | 不以缓存作为验证成功依据 |
在使用任何缓存之前,读取器应确认其格式版本、Chunk 关联和 Source Hash 与当前权威资产一致。缓存校验失败时,读取器应丢弃缓存并回退,而不应拒绝原本有效的权威资产。
步骤 7:建立运行时对象
完成权威数据与可选缓存加载后,读取器可建立应用内对象,例如:
- 体素 Chunk 存储;
- 调色板和材质表;
- 可编辑体素选择和修改接口;
- GPU 顶点/索引资源;
- 默认相机、光照或姿态;
- 预览缩略图、动画和描边表现。
这些运行时对象属于实现内部状态,不是 VFP 文件本身的额外权威语义。实现可以采用不同的数据结构,但对同一个有效 VFP 文件必须得出相同的权威体素结果。
1.4.3 错误与降级原则
加载过程中应按数据的重要性处理错误:
| 问题 | 推荐行为 |
|---|---|
| Header、Footer 或 Directory 无效 | 拒绝整个文件 |
META、PAL0、CHIX 或所需 VOX0 缺失/损坏 | 拒绝资产或标记相应 Chunk 不可用 |
| 未知且必需的 Codec 或 Flag | 拒绝受影响资产 |
| 未知的可选 Tag | 忽略并保留兼容性提示 |
| 缓存 CRC32 错误或 Source Hash 不匹配 | 丢弃该缓存并从权威数据重建或降级 |
PRVW 无法读取 | 不影响资产加载;可不显示预览 |
| 表现特性不支持 | 使用实现的默认表现,不改变体素内容 |
读取器不得为了“尽量展示”而把损坏或不匹配的缓存当作权威结果。展示错误的资产通常比明确报告不可用更危险,尤其是在编辑、同步或资产再导出流程中。
1.4.4 最小加载伪代码
function loadVfp(file):
header = readAt(file, 0, 64)
validateHeader(header)
footer = readAt(file, file.length - 64, 64)
validateFooter(footer, file.length)
directoryBytes = readAt(file, footer.directoryOffset, footer.directoryLength)
validateCrc32(directoryBytes, footer.directoryCrc32)
directory = parseDirectory(directoryBytes)
validateDirectory(directory, file.length)
meta = readAndDecodeRequired(directory, "META", -1)
palette = readAndDecodeRequired(directory, "PAL0", -1)
chunkIndex = readAndDecodeRequired(directory, "CHIX", -1)
validateAuthoritativeGlobals(meta, palette, chunkIndex)
asset = createAsset(meta, palette, chunkIndex)
for each required chunk in requestedChunks(asset):
voxels = readAndDecodeRequired(directory, "VOX0", chunk.id)
validateVoxels(voxels, chunk, palette)
asset.attachChunk(chunk.id, voxels)
cache = findValidCompatibleCache(directory, asset.sourceHash)
if cache exists:
asset.attachCache(cache)
else:
asset.rebuildCacheIfNeeded()
return asset
该伪代码刻意省略了具体字段布局、压缩实现、资源上限和错误代码;这些由后续文件结构、区段、验证器和错误处理章节规定。
本章总结
VFP 的使用方式可概括为:先将可创作、可生成的体素输入规范化;再把权威 VOX0 与必要的元数据写入 VFP;最后按目标平台附加可重建缓存。
对于所有实现,以下顺序不可改变:
权威数据决定资产内容;Source Hash 判断缓存是否仍有效;缓存只负责提升性能;Footer 和 Directory 决定数据位置。
下一章将定义文件容器、Header、Footer、Directory、Section 与数据一致性的精确二进制规则。