
在三维可视化项目开发中很多开发者都会遇到一个共同的问题网上 Three.js 的案例虽然很多但要么过于简单无法满足业务需求要么代码不完整难以直接复用。特别是当需要实现复杂交互、模型处理或性能优化时往往需要花费大量时间拼凑各种零散方案。本文整理了 110 个经过实战检验的 Three.js 案例涵盖从基础渲染到高级特效的完整解决方案。每个案例都提供可运行的源码和详细实现思路无论是初学者想要系统学习还是有经验的开发者需要快速解决特定问题都能在这里找到可直接复用的参考方案。1. Three.js 核心概念与基础环境搭建1.1 Three.js 是什么及其应用场景Three.js 是一个基于 WebGL 的 JavaScript 3D 图形库它封装了底层的 WebGL API让开发者能够用更简单的方式创建三维场景、相机、灯光、材质和几何体。相比于直接使用 WebGLThree.js 大大降低了三维图形编程的门槛。主要应用场景包括数据可视化3D 图表、地理信息展示、网络拓扑图产品展示电商商品 360° 展示、房地产漫游、汽车配置器游戏开发网页游戏、互动教育应用建筑可视化BIM 模型展示、室内设计预览创意特效艺术网站背景、交互式艺术装置1.2 环境准备与项目初始化在开始 Three.js 开发前需要准备基础的开发环境。推荐使用现代前端开发工具链确保代码的可维护性和开发效率。开发环境要求Node.js 16.0 或更高版本现代浏览器Chrome 90、Firefox 88、Safari 14代码编辑器VS Code 推荐创建基础项目结构# 创建项目目录 mkdir threejs-projects cd threejs-projects # 初始化 package.json npm init -y # 安装 Three.js npm install three # 开发依赖可选 npm install --save-dev vite types/three基础 HTML 结构!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleThree.js 基础模板/title style body { margin: 0; overflow: hidden; } canvas { display: block; } /style /head body script typemodule src./main.js/script /body /html1.3 Three.js 核心组件详解Three.js 的核心架构包含几个基本组件理解这些组件的关系是掌握 Three.js 的关键。场景Scene所有 3D 对象的容器相当于一个虚拟的 3D 空间。import * as THREE from three; // 创建场景 const scene new THREE.Scene(); scene.background new THREE.Color(0x87CEEB); // 设置背景色为天蓝色相机Camera定义观察者的视角最常用的是透视相机。// 创建透视相机 const camera new THREE.PerspectiveCamera( 75, // 视野角度FOV window.innerWidth / window.innerHeight, // 宽高比 0.1, // 近裁剪面 1000 // 远裁剪面 ); camera.position.set(0, 5, 10); // 设置相机位置渲染器Renderer负责将 3D 场景渲染到 2D 画布上。// 创建 WebGL 渲染器 const renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.setPixelRatio(window.devicePixelRatio); document.body.appendChild(renderer.domElement);2. 基础几何体与材质渲染案例2.1 创建基本几何体Three.js 提供了丰富的内置几何体这些是构建复杂模型的基础。每个几何体都有特定的参数来控制其形状和尺寸。立方体创建示例// 创建立方体几何体 const geometry new THREE.BoxGeometry(1, 1, 1); // 创建基础材质 const material new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: false // 是否显示线框 }); // 创建网格Mesh对象 const cube new THREE.Mesh(geometry, material); scene.add(cube);多种几何体组合场景// 创建球体 const sphereGeometry new THREE.SphereGeometry(0.5, 32, 32); const sphereMaterial new THREE.MeshBasicMaterial({ color: 0xff0000 }); const sphere new THREE.Mesh(sphereGeometry, sphereMaterial); sphere.position.set(-2, 0, 0); // 创建圆柱体 const cylinderGeometry new THREE.CylinderGeometry(0.5, 0.5, 1, 32); const cylinderMaterial new THREE.MeshBasicMaterial({ color: 0x0000ff }); const cylinder new THREE.Mesh(cylinderGeometry, cylinderMaterial); cylinder.position.set(2, 0, 0); // 添加到场景 scene.add(sphere); scene.add(cylinder);2.2 材质与光照系统材质决定物体表面的外观而光照则影响材质的显示效果。不同的材质对光照的反应各不相同。基础材质类型// MeshBasicMaterial - 基础材质不受光照影响 const basicMaterial new THREE.MeshBasicMaterial({ color: 0xffffff, transparent: true, opacity: 0.8 }); // MeshLambertMaterial - 朗伯材质响应光照适合漫反射表面 const lambertMaterial new THREE.MeshLambertMaterial({ color: 0xffffff, emissive: 0x072534 }); // MeshPhongMaterial - 冯氏材质支持高光反射 const phongMaterial new THREE.MeshPhongMaterial({ color: 0x156289, emissive: 0x072534, specular: 0xffffff, shininess: 100 });光照设置// 环境光 - 均匀照亮所有物体 const ambientLight new THREE.AmbientLight(0x404040, 0.4); scene.add(ambientLight); // 平行光 - 模拟太阳光 const directionalLight new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(10, 10, 5); scene.add(directionalLight); // 点光源 - 从一点向所有方向发射光线 const pointLight new THREE.PointLight(0xffffff, 1, 100); pointLight.position.set(0, 10, 0); scene.add(pointLight);3. 模型加载与处理实战3.1 外部模型加载技巧在实际项目中我们通常需要加载外部 3D 模型文件。Three.js 支持多种格式如 GLTF、OBJ、FBX 等。GLTF 模型加载推荐格式import { GLTFLoader } from three/examples/jsm/loaders/GLTFLoader.js; const loader new GLTFLoader(); loader.load( models/model.gltf, function (gltf) { const model gltf.scene; // 调整模型尺寸和位置 model.scale.set(0.1, 0.1, 0.1); model.position.set(0, 0, 0); // 遍历模型所有子对象设置阴影 model.traverse(function (child) { if (child.isMesh) { child.castShadow true; child.receiveShadow true; } }); scene.add(model); }, function (xhr) { // 加载进度回调 console.log((xhr.loaded / xhr.total * 100) % loaded); }, function (error) { // 错误处理 console.error(加载模型时出错:, error); } );处理 Blender 导出的复杂模型当从 Blender 导出包含空物体的复杂层级结构时需要注意 Three.js 的层级处理方式。// 处理三级空物体下的物体层级关系 function processModelHierarchy(model) { model.traverse((child) { // 处理空物体非网格对象 if (child.isObject3D !child.isMesh) { // 可以在这里添加空物体的特殊处理逻辑 console.log(发现空物体:, child.name); } // 处理网格物体 if (child.isMesh) { // 确保材质数组正确处理 if (Array.isArray(child.material)) { child.material.forEach(mat { mat.side THREE.DoubleSide; // 双面渲染 }); } else { child.material.side THREE.DoubleSide; } } }); }3.2 模型分割与单独控制有时候我们需要对组合模型中的单个部件进行独立控制这就需要掌握模型分割技术。模型部件单独控制方案// 为模型的不同部分添加独立控制 function setupModelPartsControl(model) { const parts {}; model.traverse((child) { if (child.isMesh) { // 根据名称或其他属性识别不同部件 if (child.name.includes(wheel)) { parts.wheels parts.wheels || []; parts.wheels.push(child); } else if (child.name.includes(body)) { parts.body child; } } }); return parts; } // 使用示例 loader.load(car-model.gltf, (gltf) { const model gltf.scene; const parts setupModelPartsControl(model); // 独立控制车轮旋转 function animateWheels() { if (parts.wheels) { parts.wheels.forEach(wheel { wheel.rotation.x 0.01; }); } } });4. 交互功能实现案例4.1 鼠标交互与物体选择交互是 3D 应用的核心功能之一Three.js 提供了射线检测Raycasting来实现物体选择。基础鼠标交互实现import { Raycaster } from three; const raycaster new Raycaster(); const mouse new THREE.Vector2(); // 鼠标移动事件监听 function onMouseMove(event) { // 将鼠标位置归一化为设备坐标-1 到 1 mouse.x (event.clientX / window.innerWidth) * 2 - 1; mouse.y -(event.clientY / window.innerHeight) * 2 1; } // 鼠标点击事件 function onMouseClick(event) { // 更新射线投射器 raycaster.setFromCamera(mouse, camera); // 计算与哪些物体相交 const intersects raycaster.intersectObjects(scene.children, true); if (intersects.length 0) { const selectedObject intersects[0].object; console.log(选中物体:, selectedObject.name); // 高亮显示选中的物体 highlightObject(selectedObject); } } // 高亮选中物体函数 function highlightObject(object) { // 保存原始材质 if (!object.userData.originalMaterial) { object.userData.originalMaterial object.material.clone(); } // 应用高亮材质 object.material new THREE.MeshBasicMaterial({ color: 0xffff00, transparent: true, opacity: 0.8 }); // 3秒后恢复原始材质 setTimeout(() { object.material object.userData.originalMaterial; }, 3000); } // 绑定事件 window.addEventListener(mousemove, onMouseMove, false); window.addEventListener(click, onMouseClick, false);4.2 交互式剖切功能实现交互式盒式剖切是医疗影像和工程分析中的常见需求下面是实现方案。盒式剖切器实现import { Box3, Plane, Vector3 } from three; class InteractiveClipping { constructor(scene, renderer) { this.scene scene; this.renderer renderer; this.clippingPlanes []; this.clippingBox new Box3(); this.isEnabled false; this.setupClippingPlanes(); } setupClippingPlanes() { // 创建六个剖切平面对应立方体的六个面 for (let i 0; i 6; i) { this.clippingPlanes.push(new Plane()); } } updateClippingBox(min, max) { this.clippingBox.set(min, max); this.updatePlanes(); } updatePlanes() { const center new Vector3(); const size new Vector3(); this.clippingBox.getCenter(center); this.clippingBox.getSize(size); // 更新六个剖切平面 this.clippingPlanes[0].setFromNormalAndCoplanarPoint( new Vector3(1, 0, 0), new Vector3(center.x - size.x / 2, center.y, center.z) ); this.clippingPlanes[1].setFromNormalAndCoplanarPoint( new Vector3(-1, 0, 0), new Vector3(center.x size.x / 2, center.y, center.z) ); // 类似地更新其他四个平面... } enable() { this.isEnabled true; this.renderer.localClippingEnabled true; // 为场景中所有材质应用剖切 this.scene.traverse((object) { if (object.isMesh object.material) { if (Array.isArray(object.material)) { object.material.forEach(mat { mat.clippingPlanes this.clippingPlanes; }); } else { object.material.clippingPlanes this.clippingPlanes; } } }); } disable() { this.isEnabled false; this.renderer.localClippingEnabled false; // 移除所有材质的剖切设置 this.scene.traverse((object) { if (object.isMesh object.material) { if (Array.isArray(object.material)) { object.material.forEach(mat { mat.clippingPlanes null; }); } else { object.material.clippingPlanes null; } } }); } }5. 高级特效与性能优化5.1 着色器与自定义材质对于需要特殊视觉效果的情况我们可以编写自定义着色器来实现高级特效。基础自定义着色器示例// 顶点着色器 const vertexShader varying vec2 vUv; varying vec3 vPosition; void main() { vUv uv; vPosition position; gl_Position projectionMatrix * modelViewMatrix * vec4(position, 1.0); } ; // 片段着色器 const fragmentShader uniform float time; varying vec2 vUv; varying vec3 vPosition; void main() { // 创建动态波纹效果 float wave sin(vPosition.x * 10.0 time) * 0.5 0.5; vec3 color mix(vec3(0.2, 0.3, 0.8), vec3(0.8, 0.3, 0.2), wave); gl_FragColor vec4(color, 1.0); } ; // 创建着色器材质 const shaderMaterial new THREE.ShaderMaterial({ vertexShader: vertexShader, fragmentShader: fragmentShader, uniforms: { time: { value: 0.0 } } }); // 在动画循环中更新时间uniform function animate() { requestAnimationFrame(animate); shaderMaterial.uniforms.time.value performance.now() * 0.001; renderer.render(scene, camera); }5.2 性能优化最佳实践Three.js 应用性能优化是项目成功的关键特别是在移动设备或复杂场景中。几何体优化策略// 1. 几何体合并 - 减少绘制调用 function mergeGeometries(meshes) { const mergedGeometry new THREE.BufferGeometry(); const geometries meshes.map(mesh mesh.geometry); // 使用 BufferGeometryUtils 合并几何体需要引入额外工具 // THREE.BufferGeometryUtils.mergeBufferGeometries(geometries); } // 2. 实例化渲染 - 大量相同物体的优化 function createInstancedMeshes(geometry, material, count) { const instancedMesh new THREE.InstancedMesh(geometry, material, count); const matrix new THREE.Matrix4(); for (let i 0; i count; i) { // 为每个实例设置不同的位置和旋转 matrix.setPosition( Math.random() * 100 - 50, Math.random() * 100 - 50, Math.random() * 100 - 50 ); instancedMesh.setMatrixAt(i, matrix); } return instancedMesh; } // 3. LODLevel of Detail - 根据距离使用不同精度的模型 function setupLOD(object) { const lod new THREE.LOD(); // 高精度模型近距离 const highDetail object.clone(); lod.addLevel(highDetail, 0); // 中精度模型 const mediumDetail simplifyGeometry(object.geometry, 0.5); lod.addLevel(mediumDetail, 50); // 低精度模型远距离 const lowDetail simplifyGeometry(object.geometry, 0.2); lod.addLevel(lowDetail, 100); return lod; }纹理与材质优化// 纹理压缩与缓存 function optimizeTextures(materials) { materials.forEach(material { if (material.map) { // 设置纹理过滤模式 material.map.minFilter THREE.LinearMipMapLinearFilter; material.map.magFilter THREE.LinearFilter; material.map.generateMipmaps true; // 启用纹理压缩如果支持 if (renderer.extensions.get(WEBGL_compressed_texture)) { // 使用压缩纹理格式 } } }); } // 自动内存管理 class ResourceManager { constructor() { this.geometries new Map(); this.materials new Map(); this.textures new Map(); } getGeometry(key, createCallback) { if (!this.geometries.has(key)) { const geometry createCallback(); this.geometries.set(key, geometry); } return this.geometries.get(key); } dispose() { // 清理所有资源 this.geometries.forEach(geometry geometry.dispose()); this.materials.forEach(material material.dispose()); this.textures.forEach(texture texture.dispose()); this.geometries.clear(); this.materials.clear(); this.textures.clear(); } }6. Vue3 Three.js 集成实战6.1 Vue3 组件化集成方案将 Three.js 与 Vue3 结合可以充分发挥两者的优势下面是完整的集成方案。基础 Vue3 Three.js 组件template div refcontainer classthree-container/div /template script import { ref, onMounted, onUnmounted } from vue; import * as THREE from three; export default { name: ThreeScene, setup() { const container ref(null); let scene, camera, renderer; let animationId; const initThree () { // 初始化场景 scene new THREE.Scene(); scene.background new THREE.Color(0x87CEEB); // 初始化相机 camera new THREE.PerspectiveCamera( 75, container.value.clientWidth / container.value.clientHeight, 0.1, 1000 ); camera.position.z 5; // 初始化渲染器 renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize( container.value.clientWidth, container.value.clientHeight ); renderer.setPixelRatio(window.devicePixelRatio); container.value.appendChild(renderer.domElement); // 添加基础几何体 const geometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshBasicMaterial({ color: 0x00ff00 }); const cube new THREE.Mesh(geometry, material); scene.add(cube); // 启动动画循环 animate(); }; const animate () { animationId requestAnimationFrame(animate); // 更新场景动画 scene.children.forEach(child { if (child.isMesh) { child.rotation.x 0.01; child.rotation.y 0.01; } }); renderer.render(scene, camera); }; const handleResize () { if (!camera || !renderer) return; camera.aspect container.value.clientWidth / container.value.clientHeight; camera.updateProjectionMatrix(); renderer.setSize( container.value.clientWidth, container.value.clientHeight ); }; onMounted(() { initThree(); window.addEventListener(resize, handleResize); }); onUnmounted(() { window.removeEventListener(resize, handleResize); cancelAnimationFrame(animationId); if (renderer) { renderer.dispose(); } }); return { container }; } }; /script style scoped .three-container { width: 100%; height: 100vh; } /style6.2 响应式 Three.js 场景管理在 Vue3 中管理复杂的 Three.js 场景需要良好的架构设计。可复用的 Three.js 组合式函数import { ref, reactive, onUnmounted } from vue; import * as THREE from three; export function useThreeJS(containerRef) { const scene ref(null); const camera ref(null); const renderer ref(null); const objects reactive(new Map()); const init () { // 初始化 Three.js 核心组件 scene.value new THREE.Scene(); camera.value new THREE.PerspectiveCamera(75, 1, 0.1, 1000); renderer.value new THREE.WebGLRenderer({ antialias: true }); // 设置渲染器 updateSize(); containerRef.value.appendChild(renderer.value.domElement); }; const updateSize () { if (!containerRef.value || !camera.value || !renderer.value) return; const width containerRef.value.clientWidth; const height containerRef.value.clientHeight; camera.value.aspect width / height; camera.value.updateProjectionMatrix(); renderer.value.setSize(width, height); }; const addObject (key, object) { objects.set(key, object); scene.value.add(object); }; const removeObject (key) { const object objects.get(key); if (object) { scene.value.remove(object); objects.delete(key); } }; const render () { if (renderer.value scene.value camera.value) { renderer.value.render(scene.value, camera.value); } }; const dispose () { // 清理所有资源 objects.forEach((object, key) { if (object.geometry) object.geometry.dispose(); if (object.material) { if (Array.isArray(object.material)) { object.material.forEach(mat mat.dispose()); } else { object.material.dispose(); } } scene.value.remove(object); }); objects.clear(); if (renderer.value) { renderer.value.dispose(); } }; onUnmounted(() { dispose(); }); return { scene, camera, renderer, objects, init, updateSize, addObject, removeObject, render, dispose }; }7. 图片墙与创意布局案例7.1 3D 图片墙实现3D 图片墙是展示类项目的常见需求下面实现一个可交互的图片墙效果。动态图片墙组件class PhotoWall { constructor(scene, imageUrls, config {}) { this.scene scene; this.imageUrls imageUrls; this.config Object.assign({ columns: 5, radius: 10, imageWidth: 2, imageHeight: 1.5, spacing: 0.2 }, config); this.photos []; this.currentAngle 0; this.loadTextures().then(() this.createWall()); } async loadTextures() { const textureLoader new THREE.TextureLoader(); this.textures []; for (const url of this.imageUrls) { const texture await new Promise((resolve) { textureLoader.load(url, resolve); }); this.textures.push(texture); } } createWall() { const { columns, radius, imageWidth, imageHeight, spacing } this.config; const rows Math.ceil(this.textures.length / columns); for (let i 0; i this.textures.length; i) { const row Math.floor(i / columns); const col i % columns; // 创建图片平面 const geometry new THREE.PlaneGeometry(imageWidth, imageHeight); const material new THREE.MeshBasicMaterial({ map: this.textures[i], side: THREE.DoubleSide }); const photo new THREE.Mesh(geometry, material); // 计算位置弧形排列 const angle (col / (columns - 1)) * Math.PI - Math.PI / 2; const x Math.cos(angle) * radius; const z Math.sin(angle) * radius; const y (row - rows / 2) * (imageHeight spacing); photo.position.set(x, y, z); photo.lookAt(0, y, 0); // 让图片始终面向中心 this.photos.push(photo); this.scene.add(photo); } } rotate(angle) { this.currentAngle angle; this.photos.forEach((photo, i) { const { columns, radius } this.config; const row Math.floor(i / columns); const col i % columns; const newAngle (col / (columns - 1)) * Math.PI - Math.PI / 2 angle; const x Math.cos(newAngle) * radius; const z Math.sin(newAngle) * radius; const y photo.position.y; photo.position.set(x, y, z); photo.lookAt(0, y, 0); }); } // 交互方法点击图片放大显示 setupInteractions(raycaster, camera) { this.photos.forEach(photo { photo.userData.originalScale photo.scale.clone(); photo.userData.isEnlarged false; }); return (intersects) { if (intersects.length 0) { const clickedPhoto intersects[0].object; if (clickedPhoto.userData.isEnlarged) { // 恢复原始大小 clickedPhoto.scale.copy(clickedPhoto.userData.originalScale); clickedPhoto.userData.isEnlarged false; } else { // 放大显示 clickedPhoto.scale.multiplyScalar(1.5); clickedPhoto.userData.isEnlarged true; } } }; } }7.2 响应式布局与动画效果让图片墙具有生动的动画效果可以大大提升用户体验。高级动画控制器class WallAnimator { constructor(photoWall) { this.photoWall photoWall; this.animationState idle; this.targetAngle 0; this.animationSpeed 0.05; } // 自动旋转动画 startAutoRotation() { this.animationState auto-rotate; this.animate(); } // 交互式旋转 rotateTo(angle) { this.animationState rotating; this.targetAngle angle; this.animateToTarget(); } // 波浪式入场动画 entranceAnimation() { this.photoWall.photos.forEach((photo, index) { // 保存原始位置 photo.userData.originalPosition photo.position.clone(); // 设置初始位置从屏幕外飞入 photo.position.y 10; photo.scale.set(0.1, 0.1, 0.1); photo.material.opacity 0; // 创建动画 setTimeout(() { this.animatePhotoToPosition(photo, photo.userData.originalPosition, index * 100); }, index * 50); }); } animatePhotoToPosition(photo, targetPosition, delay) { setTimeout(() { const startPosition photo.position.clone(); const startScale photo.scale.clone(); const startOpacity photo.material.opacity; const duration 1000; const startTime Date.now(); const animate () { const elapsed Date.now() - startTime; const progress Math.min(elapsed / duration, 1); // 缓动函数 const ease this.easeOutCubic(progress); // 插值计算 photo.position.lerpVectors(startPosition, targetPosition, ease); photo.scale.lerpVectors(startScale, new THREE.Vector3(1, 1, 1), ease); photo.material.opacity startOpacity (1 - startOpacity) * ease; if (progress 1) { requestAnimationFrame(animate); } }; animate(); }, delay); } easeOutCubic(t) { return 1 - Math.pow(1 - t, 3); } animate() { if (this.animationState auto-rotate) { this.photoWall.rotate(this.photoWall.currentAngle 0.005); requestAnimationFrame(() this.animate()); } } animateToTarget() { const angleDiff this.targetAngle - this.photoWall.currentAngle; if (Math.abs(angleDiff) 0.001) { this.photoWall.currentAngle angleDiff * this.animationSpeed; this.photoWall.rotate(this.photoWall.currentAngle); requestAnimationFrame(() this.animateToTarget()); } else { this.animationState idle; } } }8. 项目架构与工程化实践8.1 大型 Three.js 项目结构对于复杂的 Three.js 项目良好的项目架构是维护性的关键。推荐的项目目录结构src/ ├── components/ # Three.js 组件 │ ├── cameras/ # 相机控制器 │ ├── lights/ # 光照系统 │ ├── objects/ # 3D 对象 │ └── effects/ # 特效组件 ├── core/ # 核心功能 │ ├── SceneManager.js # 场景管理 │ ├── ResourceManager.js # 资源管理 │ └── AnimationLoop.js # 动画循环 ├── utils/ # 工具函数 │ ├── math.js # 数学工具 │ ├── geometry.js # 几何体工具 │ └── loader.js # 加载器工具 ├── shaders/ # 着色器文件 │ ├── vertex/ # 顶点着色器 │ └── fragment/ # 片段着色器 └── styles/ # 样式文件 └── main.css场景管理器实现class SceneManager { constructor(container) { this.container container; this.scenes new Map(); this.currentScene null; this.renderer this.createRenderer(); this.clock new THREE.Clock(); this.setupRenderer(); this.setupEventListeners(); } createRenderer() { const renderer new THREE.WebGLRenderer({ antialias: true, alpha: true }); renderer.shadowMap.enabled true; renderer.shadowMap.type THREE.PCFSoftShadowMap; renderer.physicallyCorrectLights true; renderer.outputEncoding THREE.sRGBEncoding; return renderer; } setupRenderer() { this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2)); this.container.appendChild(this.renderer.domElement); } addScene(name, sceneFactory) { const scene sceneFactory(); this.scenes.set(name, scene); return scene; } switchToScene(name) { if (this.scenes.has(name)) { this.currentScene this.scenes.get(name); this.currentScene.onActivate?.(); } } startAnimationLoop() { const animate () { requestAnimationFrame(animate); const deltaTime this.clock.getDelta(); if (this.currentScene) { this.currentScene.update?.(deltaTime); this.renderer.render( this.currentScene, this.currentScene.camera ); } }; animate(); } setupEventListeners() { window.addEventListener(resize, () this.handleResize()); } handleResize() { if (!this.currentScene) return; this.currentScene.camera.aspect this.container.clientWidth / this.container.clientHeight; this.currentScene.camera.updateProjectionMatrix(); this.renderer.setSize( this.container.clientWidth, this.container.clientHeight ); } dispose() { this.scenes.forEach(scene scene.dispose?.()); this.renderer.dispose(); } }8.2 资源加载与状态管理大型项目的资源加载需要良好的状态管理和错误处理机制。高级资源管理器class ResourceManager { constructor() { this.resources new Map(); this.loadingPromises new Map(); this.eventTarget new EventTarget