Three.js 汽车动画教程
简介
本教程将指导你使用 Three.js 创建一个简单的汽车在公路上行驶的动画。通过这个示例,你将学习到如何创建场景、相机、渲染器,以及如何创建和动画化 3D 对象。
环境准备
在开始之前,你需要一个文本编辑器和一个浏览器。可以使用任何文本编辑器,如 Visual Studio Code、Sublime Text 等。
代码实现步骤
1. 创建 HTML 基础结构
首先,创建一个基本的 HTML 文件,设置文档类型、语言和页面标题。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Car Animation</title>
<style>
body {
margin: 0;
}
canvas {
display: block;
}
</style>
</head>
<body>
</body>
</html>
在 标签中,我们将 body 的外边距设置为 0,并将 canvas 元素设置为块级元素,以确保 Three.js 渲染的画布占据整个屏幕。
2. 引入 Three.js 库
使用 CDN 引入 Three.js 库。在 标签内添加以下脚本标签:
<script src="https://cdn.jsdelivr.net/npm/three/build/three.min.js"></script>
3. 创建场景、相机和渲染器
在 <script> 标签中,我们将创建场景、相机和渲染器。
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75, // 视野角度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近裁剪面
1000 // 远裁剪面
);
camera.position.set(0, 1.5, 10); // 将相机放置在汽车后面,稍微高一点
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
- 场景(Scene) :是 Three.js 中所有对象的容器。
- 相机(Camera) :定义了观察者的视角。这里我们使用 PerspectiveCamera 创建一个透视相机。
- 渲染器(Renderer) :负责将场景和相机的内容渲染到屏幕上。我们使用 WebGLRenderer 进行渲染,并将其添加到 body 元素中。
4. 创建公路
我们将创建多个公路段,每个公路段的宽度逐渐变窄,以模拟透视效果。
const numRoadSegments = 5; // 公路段数量
const roadLength = 20; // 每段公路长度
const maxRoadWidth = 10; // 公路最大宽度
const minRoadWidth = 2; // 公路最小宽度
const roadSegments = [];
// 创建多个公路段
for (let i = 0; i < numRoadSegments; i++) {
const width = maxRoadWidth - (maxRoadWidth - minRoadWidth) * (i / numRoadSegments);
const roadGeometry = new THREE.PlaneGeometry(width, roadLength);
const roadMaterial = new THREE.MeshBasicMaterial({ color: 0x666666 });
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.z = i * roadLength;
scene.add(road);
roadSegments.push(road);
}
- numRoadSegments 定义了公路段的数量。
- roadLength 定义了每段公路的长度。
- maxRoadWidth 和 minRoadWidth 分别定义了公路的最大和最小宽度。
- 在循环中,我们根据当前段的索引计算公路的宽度,并创建一个平面几何体和材质,然后将它们组合成一个网格对象添加到场景中。
5. 创建汽车
创建一个简单的汽车几何体,并将其添加到场景中。
// 创建汽车几何体
const carGeometry = new THREE.BoxGeometry(1, 0.5, 2);
// 创建汽车材质
const carMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// 创建汽车网格
const car = new THREE.Mesh(carGeometry, carMaterial);
car.position.z = 0; // 将汽车放置在公路的起始位置
car.position.y = 0.25; // 将汽车稍微抬高一点
scene.add(car);
- carGeometry 是一个立方体几何体,代表汽车的形状。
- carMaterial 是汽车的材质,这里我们使用红色。
- car 是一个网格对象,将几何体和材质组合在一起,并添加到场景中。
6. 动画循环
我们将创建一个动画循环,在每一帧中更新汽车的位置、相机的位置和公路的位置。
let carPosition = 0; // 汽车的初始位置
const carSpeed = 0.1; // 汽车的移动速度
// 检查公路是否即将移出视野,提前进行重置
function checkAndResetRoad(road, index) {
if (road.position.z + roadLength < camera.position.z) {
const lastRoad = roadSegments[(index - 1 + numRoadSegments) % numRoadSegments];
road.position.z = lastRoad.position.z + roadLength;
const width = maxRoadWidth - (maxRoadWidth - minRoadWidth) * (index / numRoadSegments);
road.geometry = new THREE.PlaneGeometry(width, roadLength);
}
}
function animate() {
requestAnimationFrame(animate);
// 更新汽车位置
carPosition += carSpeed;
car.position.z = carPosition;
// 更新相机位置和方向
camera.position.z = carPosition - 5; // 相机始终在汽车后面 5 个单位
camera.lookAt(car.position); // 相机始终看向汽车
// 更新公路位置
roadSegments.forEach(road => {
road.position.z -= carSpeed;
});
// 检查并重置公路位置
roadSegments.forEach((road, index) => {
checkAndResetRoad(road, index);
});
// 渲染场景
renderer.render(scene, camera);
}
animate();
- carPosition 记录汽车的当前位置,carSpeed 定义了汽车的移动速度。
- checkAndResetRoad 函数用于检查公路是否即将移出视野,如果是,则将其重置到公路的末尾。
- animate 函数是动画循环的核心,它使用 requestAnimationFrame 递归调用自身,在每一帧中更新汽车、相机和公路的位置,并调用 renderer.render 方法渲染场景。
完整代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Three.js Car Animation</title>
<style>
body {
margin: 0;
}
canvas {
display: block;
}
</style>
</head>
<body>
<!-- 使用替代 CDN -->
<script src="https://cdn.jsdelivr.net/npm/three/build/three.min.js"></script>
<script>
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
75, // 视野角度
window.innerWidth / window.innerHeight, // 宽高比
0.1, // 近裁剪面
1000 // 远裁剪面
);
camera.position.set(0, 1.5, 10); // 将相机放置在汽车后面,稍微高一点
// 创建渲染器
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const numRoadSegments = 5; // 公路段数量
const roadLength = 20; // 每段公路长度
const maxRoadWidth = 10; // 公路最大宽度
const minRoadWidth = 2; // 公路最小宽度
const roadSegments = [];
// 创建多个公路段
for (let i = 0; i < numRoadSegments; i++) {
const width = maxRoadWidth - (maxRoadWidth - minRoadWidth) * (i / numRoadSegments);
const roadGeometry = new THREE.PlaneGeometry(width, roadLength);
const roadMaterial = new THREE.MeshBasicMaterial({ color: 0x666666 });
const road = new THREE.Mesh(roadGeometry, roadMaterial);
road.rotation.x = -Math.PI / 2;
road.position.z = i * roadLength;
scene.add(road);
roadSegments.push(road);
}
// 创建汽车几何体
const carGeometry = new THREE.BoxGeometry(1, 0.5, 2);
// 创建汽车材质
const carMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
// 创建汽车网格
const car = new THREE.Mesh(carGeometry, carMaterial);
car.position.z = 0; // 将汽车放置在公路的起始位置
car.position.y = 0.25; // 将汽车稍微抬高一点
scene.add(car);
let carPosition = 0; // 汽车的初始位置
const carSpeed = 0.1; // 汽车的移动速度
// 检查公路是否即将移出视野,提前进行重置
function checkAndResetRoad(road, index) {
if (road.position.z + roadLength < camera.position.z) {
const lastRoad = roadSegments[(index - 1 + numRoadSegments) % numRoadSegments];
road.position.z = lastRoad.position.z + roadLength;
const width = maxRoadWidth - (maxRoadWidth - minRoadWidth) * (index / numRoadSegments);
road.geometry = new THREE.PlaneGeometry(width, roadLength);
}
}
function animate() {
requestAnimationFrame(animate);
// 更新汽车位置
carPosition += carSpeed;
car.position.z = carPosition;
// 更新相机位置和方向
camera.position.z = carPosition - 5; // 相机始终在汽车后面 5 个单位
camera.lookAt(car.position); // 相机始终看向汽车
// 更新公路位置
roadSegments.forEach(road => {
road.position.z -= carSpeed;
});
// 检查并重置公路位置
roadSegments.forEach((road, index) => {
checkAndResetRoad(road, index);
});
// 渲染场景
renderer.render(scene, camera);
}
animate();
</script>
</body>
</html>
运行代码
将上述代码保存为一个 HTML 文件,例如 car_animation.html,然后在浏览器中打开该文件,你将看到一个汽车在公路上行驶的动画。
扩展和优化
- 可以尝试使用更复杂的几何体和材质来创建更逼真的汽车和公路。
- 添加光照效果,使场景更加真实。
- 实现用户交互,例如通过键盘控制汽车的速度和方向。