1. 初探3D世界的魅力
第一次接触three.js时,我被它构建的3D世界深深震撼。作为一个前端开发者,过去我们只能在2D平面上绘制图形,而three.js让我们能够在浏览器中创建和展示3D内容,这简直像是打开了新世界的大门。three.js是一个基于WebGL的JavaScript 3D库,它封装了WebGL的复杂API,让开发者能够更轻松地创建3D场景。
提示:three.js不是唯一的WebGL库,但它是最流行、文档最完善的一个,特别适合初学者入门3D开发。
在开始之前,我们需要了解几个核心概念:场景(Scene)、相机(Camera)和渲染器(Renderer)。这三个元素构成了three.js应用的基础框架。场景是所有3D对象的容器,相机决定了我们观看场景的视角,而渲染器则负责将场景和相机结合起来,最终呈现在网页上。
2. 搭建基础three.js环境
2.1 项目初始化
首先创建一个基本的HTML文件,引入three.js库。最简单的方式是通过CDN引入:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>My first three.js app</title> <style> body { margin: 0; } canvas { display: block; } </style> </head> <body> <script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script> <script src="js/main.js"></script> </body> </html>2.2 创建基础场景
在main.js中,我们开始构建第一个3D场景:
// 创建场景 const scene = new THREE.Scene(); // 创建相机(透视相机) const camera = new THREE.PerspectiveCamera( 75, // 视野角度 window.innerWidth / window.innerHeight, // 宽高比 0.1, // 近截面 1000 // 远截面 ); // 创建渲染器 const renderer = new THREE.WebGLRenderer(); renderer.setSize(window.innerWidth, window.innerHeight); document.body.appendChild(renderer.domElement);2.3 添加3D对象
现在场景还是空的,让我们添加一个立方体:
// 创建立方体几何体 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); // 调整相机位置(否则我们会在立方体内部) camera.position.z = 5;2.4 渲染场景
最后,我们需要创建一个动画循环来持续渲染场景:
function animate() { requestAnimationFrame(animate); // 旋转立方体 cube.rotation.x += 0.01; cube.rotation.y += 0.01; renderer.render(scene, camera); } animate();3. 深入理解three.js核心概念
3.1 场景图结构
three.js使用场景图(Scene Graph)结构来组织3D对象。场景是一个树形结构,可以包含多个对象,这些对象又可以包含子对象。这种结构使得我们可以轻松地创建复杂的3D场景。
// 创建组 const group = new THREE.Group(); scene.add(group); // 向组中添加多个对象 const cube1 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0xff0000 }) ); cube1.position.x = -2; const cube2 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x00ff00 }) ); const cube3 = new THREE.Mesh( new THREE.BoxGeometry(1, 1, 1), new THREE.MeshBasicMaterial({ color: 0x0000ff }) ); cube3.position.x = 2; group.add(cube1, cube2, cube3); // 旋转整个组 group.rotation.y = Math.PI / 4;3.2 相机类型与选择
three.js提供了几种不同的相机类型,最常用的是透视相机(PerspectiveCamera)和正交相机(OrthographicCamera)。
透视相机模拟人眼看到的景象,远处的物体看起来更小:
const camera = new THREE.PerspectiveCamera( 45, // 视野角度(FOV) window.innerWidth / window.innerHeight, // 宽高比 1, // 近截面 1000 // 远截面 );正交相机则保持物体大小不变,无论距离远近:
const aspect = window.innerWidth / window.innerHeight; const camera = new THREE.OrthographicCamera( -5 * aspect, // 左 5 * aspect, // 右 5, // 上 -5, // 下 1, // 近 1000 // 远 );3.3 材质与光照
three.js提供了多种材质类型,从简单的MeshBasicMaterial(不受光照影响)到复杂的PBR材质(基于物理的渲染)。要看到光照效果,我们需要使用受光照影响的材质,如MeshPhongMaterial或MeshStandardMaterial。
// 添加光源 const light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(1, 1, 1); scene.add(light); // 使用受光照影响的材质 const material = new THREE.MeshPhongMaterial({ color: 0x00ff00, shininess: 100 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube);4. 高级特性与性能优化
4.1 纹理与贴图
为3D对象添加纹理可以大幅提升真实感。three.js支持多种纹理类型,包括基础颜色贴图、法线贴图、粗糙度贴图等。
// 加载纹理 const textureLoader = new THREE.TextureLoader(); const texture = textureLoader.load('textures/brick.jpg'); // 使用纹理材质 const material = new THREE.MeshStandardMaterial({ map: texture, // 颜色贴图 roughness: 0.8, metalness: 0.2 }); const cube = new THREE.Mesh(geometry, material); scene.add(cube);4.2 3D模型加载
three.js支持加载多种3D模型格式,如glTF、OBJ、FBX等。glTF是官方推荐的格式,因为它专为Web设计,文件小且加载快。
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; const loader = new GLTFLoader(); loader.load( 'models/scene.gltf', function (gltf) { scene.add(gltf.scene); }, undefined, function (error) { console.error('加载模型出错:', error); } );4.3 性能优化技巧
随着场景复杂度增加,性能优化变得尤为重要:
- 几何体合并:使用BufferGeometry和合并几何体减少绘制调用
- 细节层次(LOD):根据距离显示不同细节级别的模型
- 视锥体剔除:只渲染相机视野内的物体
- 使用实例化渲染:大量相同物体时使用InstancedMesh
- 合理使用阴影:阴影计算很耗性能,只对必要物体启用
// 实例化渲染示例 const geometry = new THREE.BoxGeometry(1, 1, 1); const material = new THREE.MeshPhongMaterial({ color: 0x00ff00 }); const count = 1000; const mesh = 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 ); mesh.setMatrixAt(i, matrix); } scene.add(mesh);5. 常见问题与解决方案
5.1 场景不显示或黑屏
- 检查相机位置是否合适(可能在物体内部)
- 确认物体是否添加到场景中
- 检查渲染循环是否正常运行
- 确认材质是否需要光源(MeshBasicMaterial不需要光)
5.2 性能问题
- 使用Chrome开发者工具的Performance面板分析性能瓶颈
- 减少实时阴影的使用
- 降低几何体复杂度或使用LOD
- 避免在动画循环中创建新对象
5.3 纹理加载问题
- 确保纹理路径正确
- 检查服务器是否允许跨域请求(本地文件可能需要启动本地服务器)
- 对于大纹理,考虑压缩或使用更小的分辨率
5.4 响应式设计
当窗口大小变化时,需要更新相机和渲染器:
window.addEventListener('resize', () => { camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); renderer.setSize(window.innerWidth, window.innerHeight); });6. 实战项目:创建一个3D产品展示
让我们把这些知识应用到一个实际项目中:创建一个可交互的3D产品展示。
6.1 场景设置
// 初始化场景、相机和渲染器 const scene = new THREE.Scene(); scene.background = new THREE.Color(0xf0f0f0); const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000); camera.position.set(0, 2, 5); const renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(window.innerWidth, window.innerHeight); renderer.shadowMap.enabled = true; document.body.appendChild(renderer.domElement);6.2 添加光照
// 环境光 const ambientLight = new THREE.AmbientLight(0x404040); scene.add(ambientLight); // 平行光(带阴影) const directionalLight = new THREE.DirectionalLight(0xffffff, 1); directionalLight.position.set(5, 10, 7); directionalLight.castShadow = true; directionalLight.shadow.mapSize.width = 2048; directionalLight.shadow.mapSize.height = 2048; scene.add(directionalLight);6.3 加载产品模型
const loader = new GLTFLoader(); let product; loader.load( 'models/product.gltf', function (gltf) { product = gltf.scene; product.traverse(function (node) { if (node.isMesh) { node.castShadow = true; node.receiveShadow = true; } }); scene.add(product); }, undefined, function (error) { console.error('加载模型出错:', error); } );6.4 添加交互控制
// 轨道控制器 const controls = new OrbitControls(camera, renderer.domElement); controls.enableDamping = true; controls.dampingFactor = 0.05; // 鼠标悬停高亮效果 const raycaster = new THREE.Raycaster(); const mouse = new THREE.Vector2(); window.addEventListener('mousemove', (event) => { mouse.x = (event.clientX / window.innerWidth) * 2 - 1; mouse.y = -(event.clientY / window.innerHeight) * 2 + 1; raycaster.setFromCamera(mouse, camera); const intersects = raycaster.intersectObjects(scene.children, true); if (intersects.length > 0) { // 高亮处理 } });6.5 动画循环
function animate() { requestAnimationFrame(animate); controls.update(); renderer.render(scene, camera); } animate();7. 进阶学习路径
掌握了three.js基础后,你可以继续深入学习以下方向:
- 着色器编程:学习GLSL编写自定义着色器
- 物理引擎:集成Cannon.js或Ammo.js实现物理效果
- 后期处理:使用EffectComposer添加各种后期效果
- VR/AR:探索WebXR API创建虚拟/增强现实体验
- 性能优化:深入WebGL底层原理进行高级优化
three.js的官方文档是很好的学习资源,同时GitHub上有大量优秀的开源项目可以参考。在实际项目中,建议从简单功能开始,逐步增加复杂度,这样更容易定位和解决问题。