Three.js 场景编辑器 (Vue3 + TypeScript 实现)

135 阅读3分钟

Three.js 场景编辑器开发全记录:从零构建基于Vue3的3D创作工具

image.png

项目背景与动机

作为一名Three.js学习者,我在使用官方编辑器时遇到了几个痛点:

  1. 源码结构复杂,对新手不够友好
  2. 与现代前端框架集成度低
  3. 定制和扩展门槛较高

基于这些观察,我决定开发一个基于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+种模型格式导入导出
  • 实时材质编辑系统
  • 动画控制系统
  • 场景保存/加载功能

未来规划

  1. 插件系统:允许开发者扩展编辑器功能
  2. 协作编辑:实现多人实时协作场景编辑
  3. 性能分析工具:内置场景性能诊断工具
  4. Shader编辑器:可视化Shader编辑功能

开发心得

这个项目让我深刻理解了Three.js的核心原理和现代前端架构设计。最大的收获是学会了如何将复杂的3D图形概念封装成开发者友好的API,以及如何设计可扩展的编辑器架构。

项目已开源,欢迎贡献:链接