Three.js 场景编辑器开发全记录:从零构建基于Vue3的3D创作工具
项目背景与动机
作为一名Three.js学习者,我在使用官方编辑器时遇到了几个痛点:
- 源码结构复杂,对新手不够友好
- 与现代前端框架集成度低
- 定制和扩展门槛较高
基于这些观察,我决定开发一个基于Vue3和TypeScript的Three.js场景编辑器,目标是打造一个既适合学习又可用于实际项目的3D编辑工具。
技术架构设计
核心技术栈
- Three.js r174:负责3D渲染核心功能
- Vue3 + Composition API:构建响应式UI界面
- TypeScript:提供类型安全的开发体验
- Vite:极速的开发构建工具
- Pinia:状态管理解决方案
系统架构
src/
├── assets/ # 静态资源
├── components/ # 通用组件
├── composables/ # 组合式函数
├── core/ # Three.js核心逻辑
│ ├── controllers/ # 控制器
│ ├── loaders/ # 模型加载器
│ └── utils/ # 工具函数
├── stores/ # 状态管理
└── views/ # 页面组件
核心功能实现详解
1. 场景管理系统
对象树管理
// 使用Map存储场景对象
const sceneObjects = reactive(new Map<string, SceneObject>());
// 添加对象函数
function addObject(object: THREE.Object3D, parent?: THREE.Object3D) {
const sceneObject = {
uuid: object.uuid,
name: object.name || '未命名对象',
type: object.type,
visible: object.visible,
children: []
};
if (parent) {
parent.add(object);
const parentObj = sceneObjects.get(parent.uuid);
parentObj?.children.push(sceneObject);
} else {
scene.add(object);
}
sceneObjects.set(object.uuid, sceneObject);
}
属性编辑器
<template>
<div v-if="selectedObject">
<PositionEditor :object="selectedObject" />
<RotationEditor :object="selectedObject" />
<ScaleEditor :object="selectedObject" />
<MaterialEditor v-if="selectedObject.material" :material="selectedObject.material" />
</div>
</template>
2. 模型导入导出系统
模型加载器封装
async function loadModel(file: File): Promise<THREE.Group> {
const extension = file.name.split('.').pop()?.toLowerCase();
const loader = getLoaderByExtension(extension);
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = async (event) => {
try {
const arrayBuffer = event.target?.result as ArrayBuffer;
const model = await loader.parse(arrayBuffer);
resolve(model);
} catch (error) {
reject(error);
}
};
reader.readAsArrayBuffer(file);
});
}
动画控制系统
function setupModelAnimations(model: THREE.Group) {
const mixer = new THREE.AnimationMixer(model);
const animations = model.animations;
if (animations && animations.length) {
const actions = animations.map(anim => mixer.clipAction(anim));
return { mixer, actions };
}
return null;
}
3. 材质编辑系统
动态材质属性编辑
<template>
<div class="material-property">
<label>{{ property.label }}</label>
<input
v-if="property.type === 'color'"
type="color"
:value="getColorValue(property.value)"
@input="updateColor(property.key, $event.target.value)"
>
<input
v-else
type="number"
:value="property.value"
@input="updateProperty(property.key, $event.target.value)"
:step="property.step || 0.1"
>
</div>
</template>
4. 相机控制系统
第一人称控制器
class FirstPersonControls {
private camera: THREE.PerspectiveCamera;
private moveSpeed = 0.2;
constructor(camera: THREE.PerspectiveCamera) {
this.camera = camera;
document.addEventListener('keydown', this.handleKeyDown);
}
private handleKeyDown = (event: KeyboardEvent) => {
const direction = new THREE.Vector3();
const frontVector = new THREE.Vector3();
switch(event.key.toLowerCase()) {
case 'w':
frontVector.setFromMatrixColumn(this.camera.matrix, 0);
direction.add(frontVector.multiplyScalar(this.moveSpeed));
break;
case 's':
frontVector.setFromMatrixColumn(this.camera.matrix, 0);
direction.add(frontVector.multiplyScalar(-this.moveSpeed));
break;
// 处理其他按键...
}
this.camera.position.add(direction);
}
}
关键技术创新点
1. 拖拽式工作流实现
// 拖拽处理逻辑
function handleDragStart(event: DragEvent, item: DraggableItem) {
event.dataTransfer?.setData('application/json', JSON.stringify({
type: item.type,
config: item.config
}));
}
function handleDrop(event: DragEvent) {
const data = event.dataTransfer?.getData('application/json');
if (!data) return;
const { type, config } = JSON.parse(data);
switch(type) {
case 'model':
addModelToScene(config);
break;
case 'light':
addLightToScene(config);
break;
// 其他类型处理...
}
}
2. 场景序列化方案
function serializeScene(scene: THREE.Scene): string {
const sceneData = {
metadata: { version: 1.0 },
objects: [] as any[]
};
scene.traverse(object => {
if (object.userData.shouldSerialize !== false) {
sceneData.objects.push({
type: object.type,
uuid: object.uuid,
name: object.name,
position: object.position.toArray(),
rotation: object.rotation.toArray(),
scale: object.scale.toArray(),
userData: object.userData,
// 特殊对象处理...
});
}
});
return JSON.stringify(sceneData);
}
性能优化实践
1. 渲染循环优化
const renderLoop = () => {
requestAnimationFrame(renderLoop);
// 只在需要时更新
if (needsUpdate) {
renderer.render(scene, camera);
needsUpdate = false;
}
// 更新动画混合器
const delta = clock.getDelta();
animationMixers.forEach(mixer => mixer.update(delta));
};
2. 内存管理
function disposeObject(object: THREE.Object3D) {
if ('geometry' in object) {
(object as THREE.Mesh).geometry.dispose();
}
if ('material' in object) {
const materials = Array.isArray((object as THREE.Mesh).material)
? (object as THREE.Mesh).material
: [(object as THREE.Mesh).material];
materials.forEach(material => {
Object.keys(material).forEach(key => {
if (material[key] instanceof THREE.Texture) {
material[key].dispose();
}
});
material.dispose();
});
}
// 递归处理子对象
object.children.forEach(child => disposeObject(child));
}
项目成果与展望
已实现功能
- 完整的3D场景编辑工作流
- 支持10+种模型格式导入导出
- 实时材质编辑系统
- 动画控制系统
- 场景保存/加载功能
未来规划
- 插件系统:允许开发者扩展编辑器功能
- 协作编辑:实现多人实时协作场景编辑
- 性能分析工具:内置场景性能诊断工具
- Shader编辑器:可视化Shader编辑功能
开发心得
这个项目让我深刻理解了Three.js的核心原理和现代前端架构设计。最大的收获是学会了如何将复杂的3D图形概念封装成开发者友好的API,以及如何设计可扩展的编辑器架构。
项目已开源,欢迎贡献:链接