场景序列化与加载
目的与范围
本文档介绍 Three.js 的原生 JSON 序列化与反序列化系统,用于场景图,能够保存和加载完整的 3D 场景,包括对象层次结构、几何体、材质、纹理、动画和骨骼装备。该系统主要通过各类的 toJSON() 方法和 ObjectLoader 类实现。
有关加载 GLTF 或 FBX 等外部 3D 文件格式的信息,请参阅 GLTF 导入与导出 和 附加格式加载器。有关运行时几何体生成的信息,请参阅 程序化几何体生成。
序列化架构
toJSON 模式
Three.js 在其核心类中采用一致的 toJSON(meta) 模式。每个可序列化类实现此方法以将其状态转换为 JSON 兼容对象。meta 参数是序列化过程中累积所有引用资产的共享对象,以避免重复。
具有 toJSON 的关键类:
Object3D- 场景图节点与变换BufferGeometry- 顶点数据与属性Material- 表面属性与着色器参数Texture- 图像引用与采样参数AnimationClip- 关键帧动画数据Skeleton- 用于蒙皮的骨骼层次结构
JSON 场景格式
元数据结构
每个序列化场景包含一个描述格式版本和生成器的元数据头:
| 字段 | 类型 | 描述 |
|---|---|---|
| version | number | 格式版本(4.7) |
| type | string | 场景为 "Object",单个资产为 "Geometry"、"Material" 等 |
| generator | string | 标识序列化类(例如 "Object3D.toJSON") |
根结构
完整的场景序列化生成分层 JSON 结构:
Object3D 序列化
每个 Object3D 序列化以下属性:
| 属性 | 条件 | 描述 |
|---|---|---|
| uuid | 总是 | 对象引用的唯一标识符 |
| type | 总是 | 类名(例如 "Mesh"、"Scene"、"Group") |
| name | 非空时 | 用户指定名称 |
| matrix | 总是 | 表示局部变换的 16 元素数组 |
| up | 总是 | 上向量(默认 [0,1,0]) |
| pivot | 设置时 | 旋转/缩放的枢轴点 |
| castShadow | 为 true 时 | 对象是否投射阴影 |
| receiveShadow | 为 true 时 | 对象是否接收阴影 |
| visible | 为 false 时 | 可见性标志 |
| frustumCulled | 为 false 时 | 剔除标志 |
| renderOrder | 非零时 | 自定义渲染排序 |
| userData | 非空时 | 用户定义数据字典 |
| layers | 总是 | 层掩码值 |
| matrixAutoUpdate | 为 false 时 | 自动更新标志 |
| children | 有子节点时 | 子对象数据数组 |
特殊对象类型
InstancedMesh
序列化实例特定数据:
{
"type": "InstancedMesh",
"count": number,
"instanceMatrix": { ... },
"instanceColor": { ... } // 可选
}BatchedMesh
序列化批处理渲染数据,包括绘制范围、几何体信息和实例信息:
Scene
序列化场景特定属性,如背景、环境、雾和光照强度:
BufferGeometry 序列化
几何体序列化顶点数据和属性:
| 属性 | 描述 |
|---|---|
| uuid | 唯一标识符 |
| type | "BufferGeometry" 或子类 |
| name | 可选名称 |
| userData | 用户数据 |
| data | 含索引、属性、变形属性、组的属性数据 |
Material 序列化
材质序列化所有与着色器相关的属性、混合模式以及纹理引用(通过 UUID):
Texture 序列化
纹理序列化采样参数并引用其图像源:
| 属性 | 描述 |
|---|---|
| uuid | 唯一标识符 |
| name | 可选名称 |
| image | meta.images 中的图像 UUID |
| mapping | 纹理坐标映射类型 |
| channel | UV 通道索引 |
| offset, repeat | 变换参数 |
| wrap | 环绕模式(S, T) |
| format, type | 数据格式 |
| minFilter, magFilter | 采样滤波器 |
ObjectLoader 管线
加载流程
ObjectLoader 类通过多阶段管线编排反序列化:
解析器方法
每种资产类型都有专用的解析器方法:
| 方法 | 用途 | 依赖项 |
|---|---|---|
| parseShapes() | 为拉伸重构 Shape 对象 | 无 |
| parseAnimations() | 重构 AnimationClip 对象 | 无 |
| parseGeometries() | 使用 BufferGeometryLoader 重构几何体 | Shapes |
| parseImages() | 通过 ImageLoader 加载图像或反序列化数据 | 无 |
| parseTextures() | 重构纹理 | Images |
| parseMaterials() | 通过 MaterialLoader 重构材质 | Textures |
| parseObject() | 递归重构场景图 | 上述全部 |
| parseSkeletons() | 重构 Skeleton 对象 | 对象树(用于骨骼查找) |
资产引用管理
基于 UUID 的去重
Three.js 使用 UUID 确保每个资产仅序列化一次,即使被多次引用。meta 对象在序列化期间充当共享缓存:
辅助函数模式:
序列化使用 serialize() 辅助函数检查现有条目:
图像处理
图像可通过两种方式序列化:
- URL 引用:指向外部图像文件的字符串 URL
- Data URL 或类型化数组:嵌入的图像数据
反序列化期间,ImageLoader 处理 URL 加载,而数据数组直接重构:
反序列化过程
几何体重构
parseGeometries() 方法使用 BufferGeometryLoader 重新创建几何体实例:
材质重构
parseMaterials() 方法使用 MaterialLoader,处理材质特定属性:
对象层次结构重构
parseObject() 方法递归构建场景图:
骨骼绑定
整个对象树重构后,必须将骨骼绑定到其骨头:
- 解析骨骼:从 JSON 创建
Skeleton实例,在对象树中查找骨骼引用 - 绑定骨骼:通过 UUID 引用将每个
SkinnedMesh与其骨骼关联 - 绑定光源目标:解析光源目标引用(用于方向光/聚光灯)
纹理与图像加载
同步与异步解析
ObjectLoader 提供同步(parse())和异步(parseAsync())方法。关键区别在于图像加载:
- 同步:使用带回调的
LoadingManager,图像在后台加载 - 异步:使用
await与ImageLoader.loadAsync(),在返回前等待所有图像
同步路径:
异步路径:
纹理解析
parseTextures() 方法创建纹理实例并配置采样参数:
使用示例
序列化场景
// 假设 'scene' 是包含网格、灯光等的 Scene 对象
const json = scene.toJSON();
// 结果包含所有按 UUID 去重的资产
console.log(json.geometries); // { uuid1: {...}, uuid2: {...} }
console.log(json.materials); // { uuid1: {...}, uuid2: {...} }
console.log(json.object); // 带子节点的根场景对象加载场景
const loader = new ObjectLoader();
// 异步加载
const scene = await loader.loadAsync('path/to/scene.json');
// 或使用回调
loader.load('path/to/scene.json', (object) => {
scene.add(object);
});实现细节
类型系统集成
序列化系统依赖类型标志在反序列化期间区分对象类:
isObject3D、isBufferGeometry、isMaterial、isTexture标志用于类型检查type字符串属性用于精确类标识(例如 "Mesh"、"PerspectiveCamera")
矩阵序列化
变换序列化为表示 4x4 矩阵的 16 元素数组:
object.matrix = [
m11, m12, m13, m14,
m21, m22, m23, m24,
m31, m32, m33, m34,
m41, m42, m43, m44
];反序列化期间,使用 fromArray() 重构矩阵:
BufferAttribute 序列化
顶点属性以类型化数组数据序列化:
{
"itemSize": 3,
"type": "Float32Array",
"array": [x1, y1, z1, x2, y2, z2, ...],
"normalized": false
}局限性与注意事项
- Shader 材质:自定义
ShaderMaterial和RawShaderMaterial序列化 uniforms 但不序列化 JavaScript 函数(例如onBeforeCompile) - 节点材质:原生 JSON 格式不支持 WebGPU 节点材质;改用
NodeMaterialLoader - 外部引用:外部图像/资源的 URL 在加载时必须可访问
- 函数引用:
userData对象不能包含函数;它们将无法正确序列化 - 大型几何体:将大型顶点缓冲区作为 JSON 数组嵌入效率低下;生产环境考虑使用 GLTF 等二进制格式