在三维可视化项目开发中,很多开发者都会遇到一个共同的问题:网上 Three.js 的案例虽然很多,但要么过于简单无法满足业务需求,要么代码不完整难以直接复用。特别是当需要实现复杂交互、模型处理或性能优化时,往往需要花费大量时间拼凑各种零散方案。
本文整理了 110 个经过实战检验的 Three.js 案例,涵盖从基础渲染到高级特效的完整解决方案。每个案例都提供可运行的源码和详细实现思路,无论是初学者想要系统学习,还是有经验的开发者需要快速解决特定问题,都能在这里找到可直接复用的参考方案。
1. Three.js 核心概念与基础环境搭建
1.1 Three.js 是什么及其应用场景
Three.js 是一个基于 WebGL 的 JavaScript 3D 图形库,它封装了底层的 WebGL API,让开发者能够用更简单的方式创建三维场景、相机、灯光、材质和几何体。相比于直接使用 WebGL,Three.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 lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Three.js 基础模板</title> <style> body { margin: 0; overflow: hidden; } canvas { display: block; } </style> </head> <body> <script type="module" src="./main.js"></script> </body> </html>1.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. LOD(Level 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 ref="container" class="three-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; } </style>6.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