ThreeJS 是一个强大的 JavaScript 库,用于在浏览器中创建和展示 3D 图形。它基于 WebGL 技术,提供了简单易用的 API,使得 3D Web 开发变得更加容易。本文将详细介绍 ThreeJS 3D Web 开发的核心概念、基本用法和高级技巧,包括场景搭建、模型加载、材质应用、光照设置、动画实现等,帮助你快速掌握 ThreeJS 开发技能。
1. ThreeJS 简介
1.1 什么是 ThreeJS?
ThreeJS 是一个用于在浏览器中创建和展示 3D 图形的 JavaScript 库。它封装了 WebGL 的复杂性,提供了简单易用的 API,使得开发者可以更专注于 3D 场景的创建和交互逻辑的实现。
1.2 ThreeJS 的核心概念
- 场景(Scene):3D 世界的容器,包含所有的 3D 对象
- 相机(Camera):决定了场景中哪些部分会被渲染到屏幕上
- 渲染器(Renderer):将 3D 场景渲染到 DOM 元素中
- 网格(Mesh):由几何体和材质组成的 3D 对象
- 几何体(Geometry):定义了 3D 对象的形状
- 材质(Material):定义了 3D 对象的外观
- 光源(Light):影响 3D 对象的颜色和阴影
- 动画(Animation):使 3D 对象运动起来
1.3 ThreeJS 的应用场景
- 产品展示:360 度展示产品
- 游戏开发:浏览器中的 3D 游戏
- 数据可视化:3D 数据图表
- 虚拟实境:WebVR 应用
- 建筑可视化:3D 建筑模型
- 教育应用:交互式 3D 教学内容
2. 环境搭建
2.1 安装 ThreeJS
使用 npm:
npm install three
使用 CDN:
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
2.2 基本项目结构
├── index.html
├── package.json
├── src/
│ ├── main.js
│ ├── components/
│ ├── scenes/
│ └── utils/
└── public/
└── models/
2.3 基本示例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ThreeJS Basic Example</title>
<style>
body {
margin: 0;
overflow: hidden;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 创建几何体
const geometry = new THREE.BoxGeometry();
// 创建材质
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
// 创建网格
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 旋转立方体
cube.rotation.x += 0.01;
cube.rotation.y += 0.01;
// 渲染场景
renderer.render(scene, camera);
}
// 响应窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 启动动画
animate();
</script>
</body>
</html>
3. 核心组件
3.1 场景(Scene)
创建场景:
const scene = new THREE.Scene();
// 设置场景背景
scene.background = new THREE.Color(0xf0f0f0);
// 添加雾化效果
scene.fog = new THREE.Fog(0xf0f0f0, 1, 10);
// 添加环境贴图
const loader = new THREE.CubeTextureLoader();
loader.load([
'textures/posx.jpg',
'textures/negx.jpg',
'textures/posy.jpg',
'textures/negy.jpg',
'textures/posz.jpg',
'textures/negz.jpg'
], (texture) => {
scene.environment = texture;
});
3.2 相机(Camera)
透视相机:
// 创建透视相机
const camera = new THREE.PerspectiveCamera(
75, // 视野角度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近裁剪面
1000 // 远裁剪面
);
// 设置相机位置
camera.position.set(0, 0, 5);
// 看向场景中心
camera.lookAt(0, 0, 0);
正交相机:
// 创建正交相机
const camera = new THREE.OrthographicCamera(
window.innerWidth / -2, // 左
window.innerWidth / 2, // 右
window.innerHeight / 2, // 上
window.innerHeight / -2, // 下
0.1, // 近裁剪面
1000 // 远裁剪面
);
// 设置相机位置
camera.position.set(0, 0, 5);
// 看向场景中心
camera.lookAt(0, 0, 0);
3.3 渲染器(Renderer)
创建渲染器:
// 创建渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true, // 启用抗锯齿
alpha: true // 启用透明背景
});
// 设置渲染器大小
renderer.setSize(window.innerWidth, window.innerHeight);
// 设置像素比
renderer.setPixelRatio(window.devicePixelRatio);
// 启用阴影
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
// 添加到 DOM
document.body.appendChild(renderer.domElement);
3.4 几何体(Geometry)
内置几何体:
// 立方体
const boxGeometry = new THREE.BoxGeometry(1, 1, 1);
// 球体
const sphereGeometry = new THREE.SphereGeometry(1, 32, 32);
// 圆柱体
const cylinderGeometry = new THREE.CylinderGeometry(1, 1, 2, 32);
// 平面
const planeGeometry = new THREE.PlaneGeometry(2, 2);
// torus
const torusGeometry = new THREE.TorusGeometry(1, 0.3, 16, 100);
自定义几何体:
// 创建自定义几何体
const geometry = new THREE.BufferGeometry();
// 顶点数据
const vertices = new Float32Array([
-1.0, -1.0, 1.0,
1.0, -1.0, 1.0,
1.0, 1.0, 1.0,
1.0, 1.0, 1.0,
-1.0, 1.0, 1.0,
-1.0, -1.0, 1.0
]);
// 颜色数据
const colors = new Float32Array([
1.0, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
0.0, 0.0, 1.0,
1.0, 0.0, 0.0,
0.0, 1.0, 0.0
]);
// 设置顶点属性
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
3.5 材质(Material)
基础材质:
// 基础材质(不响应光照)
const basicMaterial = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: false,
transparent: false,
opacity: 1
});
// Lambert 材质(响应漫反射光照)
const lambertMaterial = new THREE.MeshLambertMaterial({
color: 0x00ff00,
emissive: 0x000000,
transparent: false,
opacity: 1
});
// Phong 材质(响应漫反射和高光)
const phongMaterial = new THREE.MeshPhongMaterial({
color: 0x00ff00,
emissive: 0x000000,
specular: 0xffffff,
shininess: 30,
transparent: false,
opacity: 1
});
// Standard 材质(基于物理的渲染)
const standardMaterial = new THREE.MeshStandardMaterial({
color: 0x00ff00,
emissive: 0x000000,
metalness: 0,
roughness: 0.5,
transparent: false,
opacity: 1
});
纹理材质:
// 加载纹理
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load('textures/earth.jpg');
// 创建纹理材质
const textureMaterial = new THREE.MeshStandardMaterial({
map: texture,
metalness: 0,
roughness: 1
});
3.6 光源(Light)
环境光:
// 创建环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
平行光:
// 创建平行光
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 7.5);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 50;
scene.add(directionalLight);
点光源:
// 创建点光源
const pointLight = new THREE.PointLight(0xffffff, 1, 100);
pointLight.position.set(0, 10, 0);
pointLight.castShadow = true;
pointLight.shadow.mapSize.width = 2048;
pointLight.shadow.mapSize.height = 2048;
pointLight.shadow.camera.near = 0.5;
pointLight.shadow.camera.far = 50;
scene.add(pointLight);
聚光灯:
// 创建聚光灯
const spotLight = new THREE.SpotLight(0xffffff, 1);
spotLight.position.set(0, 10, 0);
spotLight.castShadow = true;
spotLight.shadow.mapSize.width = 2048;
spotLight.shadow.mapSize.height = 2048;
spotLight.shadow.camera.near = 0.5;
spotLight.shadow.camera.far = 50;
spotLight.shadow.camera.fov = 30;
scene.add(spotLight);
3.7 网格(Mesh)
创建网格:
// 创建几何体
const geometry = new THREE.BoxGeometry(1, 1, 1);
// 创建材质
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
// 创建网格
const mesh = new THREE.Mesh(geometry, material);
// 设置位置
mesh.position.set(0, 0, 0);
// 设置旋转
mesh.rotation.set(0, 0, 0);
// 设置缩放
mesh.scale.set(1, 1, 1);
// 启用阴影
mesh.castShadow = true;
mesh.receiveShadow = true;
// 添加到场景
scene.add(mesh);
4. 模型加载
4.1 OBJ 模型加载
依赖:
npm install three-obj-loader
使用示例:
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
// 创建加载器
const loader = new OBJLoader();
// 加载模型
loader.load('models/model.obj', (object) => {
// 设置材质
object.traverse((child) => {
if (child.isMesh) {
child.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
}
});
// 设置位置和缩放
object.position.set(0, 0, 0);
object.scale.set(1, 1, 1);
// 添加到场景
scene.add(object);
}, (xhr) => {
// 加载进度
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
// 加载错误
console.error('An error happened', error);
});
4.2 GLTF 模型加载
使用示例:
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
// 创建加载器
const loader = new GLTFLoader();
// 加载模型
loader.load('models/model.gltf', (gltf) => {
// 获取模型
const model = gltf.scene;
// 设置位置和缩放
model.position.set(0, 0, 0);
model.scale.set(1, 1, 1);
// 添加到场景
scene.add(model);
// 播放动画
if (gltf.animations && gltf.animations.length > 0) {
const mixer = new THREE.AnimationMixer(model);
const action = mixer.clipAction(gltf.animations[0]);
action.play();
// 在动画循环中更新
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
}
animate();
}
}, (xhr) => {
// 加载进度
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
// 加载错误
console.error('An error happened', error);
});
4.3 FBX 模型加载
使用示例:
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader.js';
// 创建加载器
const loader = new FBXLoader();
// 加载模型
loader.load('models/model.fbx', (object) => {
// 设置材质
object.traverse((child) => {
if (child.isMesh) {
child.material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
}
});
// 设置位置和缩放
object.position.set(0, 0, 0);
object.scale.set(0.01, 0.01, 0.01); // FBX 模型通常很大
// 添加到场景
scene.add(object);
// 播放动画
const mixer = new THREE.AnimationMixer(object);
const action = mixer.clipAction(object.animations[0]);
action.play();
// 在动画循环中更新
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
}
animate();
}, (xhr) => {
// 加载进度
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
// 加载错误
console.error('An error happened', error);
});
5. 动画实现
5.1 基础动画
使用 requestAnimationFrame:
// 创建时钟
const clock = new THREE.Clock();
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 获取时间增量
const delta = clock.getDelta();
// 更新动画
mesh.rotation.x += 0.01;
mesh.rotation.y += 0.01;
// 渲染场景
renderer.render(scene, camera);
}
// 启动动画
animate();
5.2 关键帧动画
使用 AnimationMixer:
// 创建动画混合器
const mixer = new THREE.AnimationMixer(mesh);
// 创建关键帧轨道
const positionTrack = new THREE.KeyframeTrack(
'.position',
[0, 1, 2], // 时间
[0, 0, 0, 1, 0, 0, 0, 0, 0] // 位置数据
);
const rotationTrack = new THREE.KeyframeTrack(
'.rotation',
[0, 1, 2], // 时间
[0, 0, 0, 0, Math.PI * 2, 0, 0, 0, 0] // 旋转数据
);
// 创建动画剪辑
const clip = new THREE.AnimationClip('Action', 2, [positionTrack, rotationTrack]);
// 创建动画动作
const action = mixer.clipAction(clip);
// 播放动画
action.play();
// 在动画循环中更新
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
mixer.update(delta);
renderer.render(scene, camera);
}
animate();
5.3 路径动画
使用 CatmullRomCurve3:
// 创建曲线
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-10, 0, 0),
new THREE.Vector3(-5, 5, 0),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, -5, 0),
new THREE.Vector3(10, 0, 0)
]);
// 创建曲线的可视化
const points = curve.getPoints(50);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color: 0xff0000 });
const curveObject = new THREE.Line(geometry, material);
scene.add(curveObject);
// 动画循环
let time = 0;
function animate() {
requestAnimationFrame(animate);
// 更新时间
time += 0.001;
if (time > 1) time = 0;
// 获取曲线上的点
const point = curve.getPoint(time);
mesh.position.copy(point);
// 使网格看向曲线的下一个点
const nextPoint = curve.getPoint((time + 0.01) % 1);
mesh.lookAt(nextPoint);
// 渲染场景
renderer.render(scene, camera);
}
animate();
6. 交互实现
6.1 鼠标交互
使用 Raycaster:
// 创建射线投射器
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;
});
// 鼠标点击事件
window.addEventListener('click', () => {
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 计算与射线相交的对象
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
// 获取第一个相交对象
const object = intersects[0].object;
// 处理点击事件
console.log('Clicked on:', object.name);
// 例如,改变颜色
if (object.isMesh) {
object.material.color.set(Math.random() * 0xffffff);
}
}
});
6.2 触摸交互
使用 TouchEvent:
// 触摸开始事件
window.addEventListener('touchstart', (event) => {
// 获取第一个触摸点
const touch = event.touches[0];
// 计算触摸点在归一化设备坐标中的位置
mouse.x = (touch.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1;
// 更新射线
raycaster.setFromCamera(mouse, camera);
// 计算与射线相交的对象
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
// 处理触摸事件
const object = intersects[0].object;
console.log('Touched on:', object.name);
}
});
// 触摸移动事件
window.addEventListener('touchmove', (event) => {
// 获取第一个触摸点
const touch = event.touches[0];
// 计算触摸点在归一化设备坐标中的位置
mouse.x = (touch.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(touch.clientY / window.innerHeight) * 2 + 1;
});
6.3 控制器
使用 OrbitControls:
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// 创建控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 设置控制器属性
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.screenSpacePanning = false;
controls.minDistance = 1;
controls.maxDistance = 100;
controls.maxPolarAngle = Math.PI / 2;
// 在动画循环中更新控制器
function animate() {
requestAnimationFrame(animate);
controls.update();
renderer.render(scene, camera);
}
animate();
使用 FirstPersonControls:
import { FirstPersonControls } from 'three/examples/jsm/controls/FirstPersonControls.js';
// 创建控制器
const controls = new FirstPersonControls(camera, renderer.domElement);
// 设置控制器属性
controls.movementSpeed = 10;
controls.lookSpeed = 0.05;
controls.lookVertical = true;
// 在动画循环中更新控制器
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
controls.update(delta);
renderer.render(scene, camera);
}
animate();
7. 性能优化
7.1 几何体优化
- 使用 BufferGeometry:比 Geometry 更高效
- 合并几何体:减少绘制调用
- 使用实例化渲染:对于多个相同的对象
// 使用 BufferGeometry
const geometry = new THREE.BufferGeometry();
// 合并几何体
const geometries = [geometry1, geometry2, geometry3];
const mergedGeometry = BufferGeometryUtils.mergeBufferGeometries(geometries);
// 使用实例化渲染
const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshBasicMaterial({ 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.makeTranslation(
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10,
(Math.random() - 0.5) * 10
);
mesh.setMatrixAt(i, matrix);
}
scene.add(mesh);
7.2 材质优化
- 使用材质缓存:对于相同的材质
- 减少材质数量:合并相似的材质
- 使用纹理压缩:减少纹理大小
7.3 渲染优化
- 使用视锥体剔除:只渲染相机可见的对象
- 使用LOD(Level of Detail):根据距离使用不同细节的模型
- 使用阴影映射优化:调整阴影映射大小和距离
- 使用 WebGLRenderer 优化:启用 antialias 时要谨慎
7.4 内存管理
- 释放未使用的对象:使用 dispose() 方法
- 清理事件监听器:避免内存泄漏
- 使用对象池:重用对象而不是创建新对象
// 释放几何体
geometry.dispose();
// 释放材质
material.dispose();
// 释放纹理
texture.dispose();
// 清理事件监听器
window.removeEventListener('resize', onResize);
8. 实战案例
8.1 3D 地球仪
功能需求:
- 创建一个 3D 地球仪
- 显示地球纹理
- 实现地球自转
- 添加交互控制
实现:
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x000033);
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 2;
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 加载纹理
const textureLoader = new THREE.TextureLoader();
const earthTexture = textureLoader.load('textures/earth.jpg');
const normalTexture = textureLoader.load('textures/earth-normal.jpg');
// 创建地球几何体
const geometry = new THREE.SphereGeometry(1, 64, 64);
// 创建地球材质
const material = new THREE.MeshStandardMaterial({
map: earthTexture,
normalMap: normalTexture,
metalness: 0,
roughness: 1
});
// 创建地球网格
const earth = new THREE.Mesh(geometry, material);
scene.add(earth);
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 3, 5);
scene.add(directionalLight);
// 添加控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 响应窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 地球自转
earth.rotation.y += 0.001;
// 更新控制器
controls.update();
// 渲染场景
renderer.render(scene, camera);
}
// 启动动画
animate();
8.2 3D 产品展示
功能需求:
- 加载 3D 产品模型
- 实现模型旋转和缩放
- 添加材质和光照
- 实现鼠标交互
实现:
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 5;
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 5, 5);
directionalLight.castShadow = true;
scene.add(directionalLight);
const pointLight = new THREE.PointLight(0xffffff, 0.5);
pointLight.position.set(-5, -5, -5);
scene.add(pointLight);
// 加载模型
const loader = new GLTFLoader();
let model;
loader.load('models/product.gltf', (gltf) => {
model = gltf.scene;
model.position.set(0, 0, 0);
model.scale.set(1, 1, 1);
scene.add(model);
}, (xhr) => {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
}, (error) => {
console.error('An error happened', error);
});
// 添加控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
// 响应窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 鼠标交互
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventListener('click', (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) {
const object = intersects[0].object;
console.log('Clicked on:', object.name);
// 改变材质颜色
if (object.isMesh) {
object.material.color.set(Math.random() * 0xffffff);
}
}
});
// 动画循环
function animate() {
requestAnimationFrame(animate);
// 更新模型旋转
if (model) {
model.rotation.y += 0.005;
}
// 更新控制器
controls.update();
// 渲染场景
renderer.render(scene, camera);
}
// 启动动画
animate();
8.3 3D 游戏场景
功能需求:
- 创建一个简单的 3D 游戏场景
- 添加地面和墙壁
- 实现第一人称控制
- 添加碰撞检测
实现:
// 创建场景
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x87CEEB);
// 创建相机
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 1, 0);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// 添加光源
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(5, 10, 5);
scene.add(directionalLight);
// 创建地面
const groundGeometry = new THREE.PlaneGeometry(100, 100);
const groundMaterial = new THREE.MeshStandardMaterial({ color: 0x228B22 });
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.5;
ground.receiveShadow = true;
scene.add(ground);
// 创建墙壁
const wallGeometry = new THREE.BoxGeometry(10, 2, 0.1);
const wallMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513 });
const wall1 = new THREE.Mesh(wallGeometry, wallMaterial);
wall1.position.set(0, 0.5, 5);
scene.add(wall1);
const wall2 = new THREE.Mesh(wallGeometry, wallMaterial);
wall2.position.set(0, 0.5, -5);
scene.add(wall2);
const wall3 = new THREE.Mesh(wallGeometry, wallMaterial);
wall3.rotation.y = Math.PI / 2;
wall3.position.set(5, 0.5, 0);
scene.add(wall3);
const wall4 = new THREE.Mesh(wallGeometry, wallMaterial);
wall4.rotation.y = Math.PI / 2;
wall4.position.set(-5, 0.5, 0);
scene.add(wall4);
// 添加控制器
const controls = new FirstPersonControls(camera, renderer.domElement);
controls.movementSpeed = 5;
controls.lookSpeed = 0.05;
controls.lookVertical = true;
// 响应窗口大小变化
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 动画循环
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const delta = clock.getDelta();
controls.update(delta);
// 碰撞检测
if (camera.position.y < 0) {
camera.position.y = 0;
}
if (camera.position.x > 4.5) {
camera.position.x = 4.5;
}
if (camera.position.x < -4.5) {
camera.position.x = -4.5;
}
if (camera.position.z > 4.5) {
camera.position.z = 4.5;
}
if (camera.position.z < -4.5) {
camera.position.z = -4.5;
}
// 渲染场景
renderer.render(scene, camera);
}
// 启动动画
animate();
9. 最佳实践总结
9.1 代码组织
- 模块化:将代码分为场景、相机、渲染器、模型等模块
- 使用 ES6+:使用现代 JavaScript 特性
- 使用 ThreeJS 示例:参考官方示例学习最佳实践
- 文档:为复杂代码添加注释
9.2 性能优化
- 使用 BufferGeometry:比 Geometry 更高效
- 使用实例化渲染:对于多个相同的对象
- 合并几何体:减少绘制调用
- 优化纹理:使用适当大小的纹理,考虑压缩
- 使用 LOD:根据距离使用不同细节的模型
- 释放资源:使用 dispose() 方法释放未使用的资源
9.3 交互设计
- 使用 OrbitControls:提供直观的相机控制
- 使用 Raycaster:实现鼠标/触摸交互
- 响应式设计:适应不同屏幕尺寸
- 反馈:为用户交互提供视觉反馈
9.4 部署
- 使用 CDN:对于生产环境,使用 CDN 加载 ThreeJS
- 压缩代码:使用 webpack 等工具压缩代码
- 预加载资源:预加载模型和纹理
- 错误处理:添加适当的错误处理
10. 总结
ThreeJS 是一个强大的 JavaScript 库,用于在浏览器中创建和展示 3D 图形。通过本文的学习,你应该已经掌握了 ThreeJS 的核心概念、基本用法和高级技巧,包括场景搭建、模型加载、材质应用、光照设置、动画实现等。
ThreeJS 的应用场景非常广泛,从产品展示到游戏开发,从数据可视化到虚拟实境,都可以使用 ThreeJS 来实现。随着 WebGL 技术的不断发展,ThreeJS 也在不断更新和改进,为开发者提供了更强大、更易用的 API。
要成为一名优秀的 ThreeJS 开发者,你需要不断学习和实践,掌握 3D 图形学的基本原理,了解 WebGL 的工作机制,熟悉 ThreeJS 的 API 和最佳实践。通过不断地尝试和创新,你可以创建出令人惊艳的 3D Web 应用。
希望本文能够帮助你快速入门 ThreeJS 开发,开启你的 3D Web 开发之旅!