在Web 3D开发中Three.js作为最流行的JavaScript 3D库为开发者提供了丰富的功能和案例参考。本文整理了第九十四期Three.js实用案例合集涵盖从基础几何体到高级特效的完整实现方案每个案例都包含可运行的代码示例和详细的技术解析。无论你是刚接触Three.js的新手还是有一定经验的开发者都能从这些案例中找到实用的技术点和解决方案。本文将重点介绍UV坐标贴图渲染原理、不规则平面贴图处理、经纬度坐标回显、Vue3集成GLB模型等热门技术问题并提供完整的代码实现。1. Three.js核心概念与环境准备1.1 Three.js简介与应用场景Three.js是基于WebGL的JavaScript 3D图形库它封装了底层的WebGL API让开发者能够更轻松地创建和展示3D内容。主要应用场景包括数据可视化3D图表、地理信息系统、网络拓扑图游戏开发网页3D游戏、交互式体验产品展示电商商品3D展示、房地产漫游教育模拟物理实验、化学分子结构展示1.2 开发环境搭建首先创建基本的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 srchttps://cdnjs.cloudflare.com/ajax/libs/three.js/r185/three.min.js/script script src./main.js/script /body /html创建主要的JavaScript文件// main.js - Three.js基础初始化 let scene, camera, renderer; function init() { // 创建场景 scene new THREE.Scene(); scene.background new THREE.Color(0xf0f0f0); // 创建相机 camera new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.z 5; // 创建渲染器 renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement); // 添加光源 const ambientLight new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); scene.add(directionalLight); // 窗口大小变化响应 window.addEventListener(resize, onWindowResize); } function onWindowResize() { camera.aspect window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); } function animate() { requestAnimationFrame(animate); renderer.render(scene, camera); } // 初始化并启动动画 init(); animate();2. UV坐标贴图原理与不规则平面渲染2.1 UV坐标基础概念UV坐标是2D纹理坐标系统用于将2D图像映射到3D模型表面。U代表水平方向V代表垂直方向取值范围都是0到1。// 创建带UV坐标的平面几何体 const geometry new THREE.PlaneGeometry(4, 3); const textureLoader new THREE.TextureLoader(); // 加载纹理图片 const texture textureLoader.load(texture.jpg); const material new THREE.MeshBasicMaterial({ map: texture }); const plane new THREE.Mesh(geometry, material); scene.add(plane);2.2 不规则平面的UV映射处理对于不规则平面Three.js通过顶点UV坐标来控制纹理的映射方式// 创建自定义形状的平面几何体 const shape new THREE.Shape(); shape.moveTo(0, 0); shape.lineTo(2, 1); shape.lineTo(1, 3); shape.lineTo(-1, 2); shape.lineTo(0, 0); const geometry new THREE.ShapeGeometry(shape); // 手动设置UV坐标 const uvs []; const vertices geometry.attributes.position.array; for (let i 0; i vertices.length; i 3) { const x vertices[i]; const y vertices[i 1]; // 将顶点坐标归一化到UV空间 const u (x 2) / 4; // 假设x范围[-2, 2] const v (y 2) / 5; // 假设y范围[-2, 3] uvs.push(u, v); } geometry.setAttribute(uv, new THREE.Float32BufferAttribute(uvs, 2)); const material new THREE.MeshBasicMaterial({ map: textureLoader.load(texture.jpg), side: THREE.DoubleSide }); const irregularPlane new THREE.Mesh(geometry, material); scene.add(irregularPlane);2.3 纹理环绕模式与重复设置// 纹理重复设置 texture.wrapS THREE.RepeatWrapping; texture.wrapT THREE.RepeatWrapping; texture.repeat.set(2, 2); // 水平重复2次垂直重复2次 // 纹理偏移 texture.offset.set(0.1, 0.1); // 纹理旋转 texture.center.set(0.5, 0.5); texture.rotation Math.PI / 4;3. 经纬度坐标回显与3D地球实现3.1 球体几何与经纬度转换// 创建地球球体 const earthGeometry new THREE.SphereGeometry(2, 64, 64); const earthTexture textureLoader.load(earth.jpg); const earthMaterial new THREE.MeshPhongMaterial({ map: earthTexture }); const earth new THREE.Mesh(earthGeometry, earthMaterial); scene.add(earth); // 经纬度转3D坐标函数 function latLongToVector3(lat, lon, radius) { const phi (90 - lat) * Math.PI / 180; const theta (lon 180) * Math.PI / 180; const x -radius * Math.sin(phi) * Math.cos(theta); const y radius * Math.cos(phi); const z radius * Math.sin(phi) * Math.sin(theta); return new THREE.Vector3(x, y, z); } // 在地球上标记特定经纬度位置 function addLocationMarker(lat, lon, color 0xff0000) { const position latLongToVector3(lat, lon, 2.02); // 略大于地球半径 const markerGeometry new THREE.SphereGeometry(0.05, 16, 16); const markerMaterial new THREE.MeshBasicMaterial({ color: color }); const marker new THREE.Mesh(markerGeometry, markerMaterial); marker.position.copy(position); scene.add(marker); return marker; } // 示例在北京位置添加标记 addLocationMarker(39.9042, 116.4074, 0xff0000);3.2 交互式经纬度显示// 添加射线检测用于交互 const raycaster new THREE.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 checkIntersection() { // 更新射线 raycaster.setFromCamera(mouse, camera); // 计算物体与射线的交点 const intersects raycaster.intersectObject(earth); if (intersects.length 0) { const point intersects[0].point; // 3D坐标转经纬度 const lat 90 - (Math.acos(point.y / 2) * 180 / Math.PI); const lon ((Math.atan2(point.z, point.x) * 180 / Math.PI) 180) % 360 - 180; // 显示经纬度信息 showCoordinates(lat, lon); } } function showCoordinates(lat, lon) { // 创建或更新坐标显示 let coordDisplay document.getElementById(coord-display); if (!coordDisplay) { coordDisplay document.createElement(div); coordDisplay.id coord-display; coordDisplay.style.cssText position: absolute; top: 10px; left: 10px; background: rgba(0,0,0,0.7); color: white; padding: 10px; border-radius: 5px; ; document.body.appendChild(coordDisplay); } coordDisplay.innerHTML 纬度: ${lat.toFixed(2)}°br经度: ${lon.toFixed(2)}°; } // 添加事件监听 window.addEventListener(mousemove, onMouseMove); // 在动画循环中检测交互 function animate() { requestAnimationFrame(animate); checkIntersection(); renderer.render(scene, camera); }4. Vue3 Three.js GLB模型集成4.1 Vue3项目配置首先创建Vue3项目并安装依赖npm create vuelatest threejs-project cd threejs-project npm install three types/three npm run dev4.2 Three.js Vue组件封装创建Three.js组件template div refcontainer classthree-container/div /template script setup import { ref, onMounted, onUnmounted } from vue; import * as THREE from three; import { GLTFLoader } from three/examples/jsm/loaders/GLTFLoader.js; const container ref(null); let scene, camera, renderer, mixer; const init () { // 初始化场景 scene new THREE.Scene(); scene.background new THREE.Color(0xeeeeee); // 初始化相机 camera new THREE.PerspectiveCamera(75, container.value.clientWidth / container.value.clientHeight, 0.1, 1000); camera.position.set(5, 5, 5); // 初始化渲染器 renderer new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(container.value.clientWidth, container.value.clientHeight); renderer.shadowMap.enabled true; renderer.shadowMap.type THREE.PCFSoftShadowMap; container.value.appendChild(renderer.domElement); // 添加光源 const ambientLight new THREE.AmbientLight(0xffffff, 0.6); scene.add(ambientLight); const directionalLight new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(10, 10, 5); directionalLight.castShadow true; scene.add(directionalLight); // 加载GLB模型 loadGLBModel(); // 添加轨道控制器 const controls new OrbitControls(camera, renderer.domElement); controls.enableDamping true; // 启动动画循环 animate(); }; const loadGLBModel () { const loader new GLTFLoader(); loader.load(/models/example.glb, (gltf) { const model gltf.scene; // 设置模型位置和缩放 model.position.set(0, 0, 0); model.scale.set(1, 1, 1); // 启用阴影 model.traverse((child) { if (child.isMesh) { child.castShadow true; child.receiveShadow true; } }); scene.add(model); // 如果有动画设置动画混合器 if (gltf.animations.length 0) { mixer new THREE.AnimationMixer(model); gltf.animations.forEach((clip) { mixer.clipAction(clip).play(); }); } }, undefined, (error) { console.error(加载GLB模型失败:, error); }); }; const animate () { requestAnimationFrame(animate); if (mixer) { mixer.update(0.016); // 更新动画 } renderer.render(scene, camera); }; const handleResize () { if (container.value camera renderer) { camera.aspect container.value.clientWidth / container.value.clientHeight; camera.updateProjectionMatrix(); renderer.setSize(container.value.clientWidth, container.value.clientHeight); } }; onMounted(() { init(); window.addEventListener(resize, handleResize); }); onUnmounted(() { window.removeEventListener(resize, handleResize); if (renderer) { renderer.dispose(); } }); /script style scoped .three-container { width: 100%; height: 100vh; } /style4.3 GLB模型加载优化// 模型加载进度显示 const loadGLBModelWithProgress () { const loader new GLTFLoader(); const manager new THREE.LoadingManager(); manager.onProgress (url, itemsLoaded, itemsTotal) { const progress (itemsLoaded / itemsTotal) * 100; console.log(加载进度: ${progress.toFixed(2)}%); }; loader.load(/models/example.glb, (gltf) { // 模型加载完成 console.log(模型加载完成); scene.add(gltf.scene); }, (progress) { // 加载进度 const percent (progress.loaded / progress.total) * 100; console.log(加载进度: ${percent.toFixed(2)}%); }, (error) { console.error(加载失败:, error); } ); };5. 水面效果实现与FlowDirection问题解决5.1 基础水面效果// 创建简单的水面效果 function createWaterSurface() { const waterGeometry new THREE.PlaneGeometry(10, 10, 256, 256); const waterMaterial new THREE.MeshPhongMaterial({ color: 0x0077be, transparent: true, opacity: 0.8, shininess: 100 }); const water new THREE.Mesh(waterGeometry, waterMaterial); water.rotation.x -Math.PI / 2; water.position.y -1; return water; } const waterSurface createWaterSurface(); scene.add(waterSurface);5.2 高级水面着色器效果对于更复杂的水面效果可以使用自定义着色器// 水面着色器材质 const waterVertexShader varying vec2 vUv; varying vec3 vPosition; void main() { vUv uv; vPosition position; gl_Position projectionMatrix * modelViewMatrix * vec4(position, 1.0); } ; const waterFragmentShader uniform float time; varying vec2 vUv; varying vec3 vPosition; void main() { // 创建波纹效果 float wave sin(vPosition.x * 10.0 time) * cos(vPosition.z * 10.0 time) * 0.1; // 基础颜色 vec3 baseColor vec3(0.0, 0.3, 0.6); // 添加高光 vec3 highlight vec3(0.2, 0.4, 0.8) * abs(wave); gl_FragColor vec4(baseColor highlight, 0.8); } ; function createShaderWater() { const geometry new THREE.PlaneGeometry(10, 10, 256, 256); const material new THREE.ShaderMaterial({ vertexShader: waterVertexShader, fragmentShader: waterFragmentShader, transparent: true, uniforms: { time: { value: 0 } } }); const water new THREE.Mesh(geometry, material); water.rotation.x -Math.PI / 2; return { water, material }; } const { water, material } createShaderWater(); scene.add(water); // 在动画循环中更新时间uniform function animate() { requestAnimationFrame(animate); material.uniforms.time.value performance.now() * 0.001; renderer.render(scene, camera); }5.3 FlowDirection属性问题解决方案当遇到Property flowDirection does not exist on type Water错误时这通常是因为版本兼容性问题// 解决方案1检查Three.js版本兼容性 console.log(Three.js版本:, THREE.REVISION); // 解决方案2使用标准的水面材质配置 function createCompatibleWater() { const waterGeometry new THREE.PlaneGeometry(10, 10); // 使用标准材质替代特定的Water类 const material new THREE.MeshStandardMaterial({ color: 0x0077be, roughness: 0.1, metalness: 0.9, transparent: true, opacity: 0.8 }); const water new THREE.Mesh(waterGeometry, material); water.rotation.x -Math.PI / 2; // 添加动态效果 let time 0; function updateWater() { time 0.01; water.position.y Math.sin(time) * 0.05; requestAnimationFrame(updateWater); } updateWater(); return water; } const compatibleWater createCompatibleWater(); scene.add(compatibleWater);6. 性能优化与最佳实践6.1 几何体复用与实例化// 使用InstancedMesh进行大量相同物体的渲染优化 function createInstancedGeometry() { const geometry new THREE.BoxGeometry(1, 1, 1); const material new THREE.MeshPhongMaterial({ color: 0x00ff00 }); const count 1000; const instancedMesh new THREE.InstancedMesh(geometry, material, count); const matrix new THREE.Matrix4(); const position new THREE.Vector3(); for (let i 0; i count; i) { position.x Math.random() * 100 - 50; position.y Math.random() * 100 - 50; position.z Math.random() * 100 - 50; matrix.setPosition(position); instancedMesh.setMatrixAt(i, matrix); } scene.add(instancedMesh); return instancedMesh; }6.2 纹理加载优化// 纹理压缩与缓存 const textureCache new Map(); function loadOptimizedTexture(url) { if (textureCache.has(url)) { return textureCache.get(url); } const texture textureLoader.load(url, (tex) { // 设置纹理优化参数 tex.generateMipmaps true; tex.minFilter THREE.LinearMipMapLinearFilter; tex.magFilter THREE.LinearFilter; tex.anisotropy renderer.capabilities.getMaxAnisotropy(); }); textureCache.set(url, texture); return texture; }6.3 内存管理与资源释放// 正确的资源释放方法 function disposeSceneResources() { scene.traverse((object) { if (object.isMesh) { if (object.geometry) { object.geometry.dispose(); } if (object.material) { if (Array.isArray(object.material)) { object.material.forEach(material material.dispose()); } else { object.material.dispose(); } } } }); renderer.dispose(); }7. 常见问题排查与解决方案7.1 纹理加载失败处理// 纹理加载错误处理 textureLoader.load( texture.jpg, (texture) { console.log(纹理加载成功); }, undefined, (error) { console.error(纹理加载失败:, error); // 使用默认颜色替代 material.color.set(0xff0000); } );7.2 性能问题诊断// 帧率监控 const stats new Stats(); stats.showPanel(0); // 0: fps, 1: ms, 2: mb document.body.appendChild(stats.dom); function animate() { stats.begin(); // 渲染逻辑 renderer.render(scene, camera); stats.end(); requestAnimationFrame(animate); }7.3 跨浏览器兼容性// 检测WebGL支持 if (!window.WebGLRenderingContext) { // 浏览器不支持WebGL const warning document.createElement(div); warning.innerHTML 您的浏览器不支持WebGL请使用现代浏览器访问; warning.style.cssText position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); color: red;; document.body.appendChild(warning); } else { // 正常初始化Three.js init(); }本文涵盖了Three.js开发中的核心技术和常见问题解决方案从基础的几何体创建到复杂的水面效果实现每个案例都提供了完整的代码示例。在实际项目开发中建议根据具体需求选择合适的实现方案并注意性能优化和资源管理。通过掌握这些技术点你将能够构建出更加丰富和流畅的3D Web应用。Three.js生态系统不断更新建议定期查阅官方文档和示例保持对最新特性的了解。