Three.js 实现飞机沿着特定路线飞行
在这个示例中,我们将通过 Three.js 来构建一个包含场景(Scene)、相机(Camera)、渲染器(Renderer)、飞机模型以及飞行路径的小型三维环境。效果类似一个初级版的“飞行模拟器”:一架飞机沿着一条预设的曲线飞行,同时伴随天空与光照效果。
模型准备
访问
下载模型
场景、相机与渲染器
- 使用
THREE.Scene()
来创建一个场景容器,并通过scene.fog
增加雾效,以提升空间层次感。 - 使用
THREE.PerspectiveCamera
来生成透视相机,并通过camera.position.set(0,5,10)
放置到略微俯视的角度。 - 使用
THREE.WebGLRenderer
进行渲染,并通过开启antialias
、shadowMap
以及合适的toneMapping
配置来提升视觉表现。
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { Sky } from "three/examples/jsm/objects/Sky";
import { FogExp2 } from "three";
import "./style.css";
// 创建场景
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0xdfe9f3, 0.02); // 添加雾效果
// 创建相机
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);
// 创建渲染器
const renderer = new THREE.WebGLRenderer({
antialias: true,
alpha: true,
});
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.5;
document.body.appendChild(renderer.domElement);
添加天空盒(Sky)
- 使用
new Sky()
来创建天空对象,并通过sky.scale.setScalar(1000)
让天空能够包围整个场景。 - 使用
turbidity
,rayleigh
,mieCoefficient
等参数来微调天空的光学特性,从而达到想要的颜色和散射效果。 - 使用
sunPosition
来指定太阳位置,通过球面坐标计算来模拟更真实的光照角度。
// 添加天空
const sky = new Sky();
sky.scale.setScalar(1000);
scene.add(sky);
// 设置天空材质参数
const sun = new THREE.Vector3();
const uniforms = sky.material.uniforms;
uniforms["turbidity"].value = 10; // 大气浑浊度
uniforms["rayleigh"].value = 2; // 瑞利散射系数
uniforms["mieCoefficient"].value = 0.005; // 米氏散射系数
uniforms["mieDirectionalG"].value = 0.8; // 光线与视角的相位函数
// 设置太阳位置
const phi = THREE.MathUtils.degToRad(90 - 2);
const theta = THREE.MathUtils.degToRad(180);
sun.setFromSphericalCoords(1, phi, theta);
uniforms["sunPosition"].value.copy(sun);
轨道控制器 (OrbitControls)
- 使用
OrbitControls
来允许用户在浏览器里通过鼠标或触摸手势自由旋转、缩放和移动相机视角。 - 使用
enableDamping
与dampingFactor
来提供更平滑的视角移动体验。 - 使用
maxDistance
和minDistance
来限制相机的最远和最近距离,避免视角拉得过远或过近。
// 添加轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxDistance = 50;
controls.minDistance = 3;
飞机模型的加载
缩放(Scale)与旋转(Rotation)
- 使用
gltf.scene.scale
来调整飞机模型的大小,让它与场景的比例更匹配。 - 使用
gltf.scene.rotation
来给模型做适当旋转,确保其朝向正确。 - 使用
gltf.scene.traverse
来遍历模型内所有网格,统一开启或配置阴影以及材质的属性。
// 创建一个组来存放飞机模型
const airplane = new THREE.Group();
scene.add(airplane);
// 使用 GLTFLoader 加载飞机模型
const loader = new GLTFLoader();
loader.load(
"/airplane/scene.gltf",
(gltf) => {
// 缩放与旋转模型
gltf.scene.scale.set(0.01, 0.01, 0.01);
gltf.scene.rotation.set(0, Math.PI, 0);
// 遍历子网格并设置阴影
gltf.scene.traverse((child) => {
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
if (child.material) {
child.material.envMapIntensity = 1;
child.material.needsUpdate = true;
}
}
});
airplane.add(gltf.scene);
},
(progress) => {
console.log("加载进度:", (progress.loaded / progress.total) * 100 + "%");
},
(error) => {
console.error("模型加载出错:", error);
}
);
如果还想改变模型在场景内的位置(例如略微抬高到地面之上),可以使用
gltf.scene.position.set(0,1,0)
。
光源设置
- 使用
AmbientLight
来保证场景不会出现完全漆黑的区域,提供柔和的整体亮度。 - 使用
DirectionalLight
(方向光)作为主要光源,并设置它的位置和阴影参数,让飞机模型产生逼真的投影。 - 我们需要添加补光
fillLight
来让相反方向也有一定亮度,避免一面过亮、一面过暗。
// 环境光
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 主光源(方向光)
const mainLight = new THREE.DirectionalLight(0xffffff, 1);
mainLight.position.set(5, 5, 5);
mainLight.castShadow = true;
mainLight.shadow.mapSize.width = 2048;
mainLight.shadow.mapSize.height = 2048;
mainLight.shadow.camera.near = 0.1;
mainLight.shadow.camera.far = 100;
mainLight.shadow.camera.left = -20;
mainLight.shadow.camera.right = 20;
mainLight.shadow.camera.top = 20;
mainLight.shadow.camera.bottom = -20;
scene.add(mainLight);
// 补光
const fillLight = new THREE.DirectionalLight(0x8088ff, 0.4);
fillLight.position.set(-5, 3, -5);
scene.add(fillLight);
飞行路径的创建
- 使用
CatmullRomCurve3
来生成一条平滑的样条曲线,它可以帮助我们设置飞机的飞行动作,让曲线在五个关键点之间自然过渡。 - 我们可以随时增减控制点,或调整它们的坐标,以改变飞机的飞行轨迹。
// 创建飞行路径
const curve = new THREE.CatmullRomCurve3([
new THREE.Vector3(-10, 0, 0),
new THREE.Vector3(-5, 4, 5),
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, 4, -5),
new THREE.Vector3(10, 0, 0),
]);
可视化路径(虚线)
- 使用
curve.getPoints(50)
来采样 50 个点,以便将曲线绘制出来。 - 使用
LineDashedMaterial
来渲染虚线效果,并调用computeLineDistances()
来计算虚线的间隔。 - 我们将生成的
pathLine
添加到场景中,以清晰显示飞机的移动路线。
// 可视化路径
const points = curve.getPoints(50);
const pathGeometry = new THREE.BufferGeometry().setFromPoints(points);
const pathMaterial = new THREE.LineDashedMaterial({
color: 0xffffff,
dashSize: 0.5,
gapSize: 0.3,
opacity: 0.5,
transparent: true,
});
const pathLine = new THREE.Line(pathGeometry, pathMaterial);
pathLine.computeLineDistances();
scene.add(pathLine);
动画循环:让飞机沿曲线飞行
- 使用
requestAnimationFrame(animate)
来在每帧更新并渲染场景,实现流畅的动画效果。 - 使用
curve.getPoint(progress)
来确定飞机在曲线上的位置,并使用curve.getTangent(progress)
来计算飞机沿曲线的方向,从而让飞机始终面朝前方。curve.getTangent(progress)
会先计算在曲线对应点处的微分值(或通过数值方式计算导数),然后将该结果标准化(单位化),得到一个长度为 1 的方向向量 - 使用
controls.update()
来确保轨道控制器在用户操作后能实时刷新视图。
// 动画参数
let progress = 0;
const speed = 0.001;
function animate() {
requestAnimationFrame(animate);
// 更新飞机位置
progress += speed;
if (progress > 1) progress = 0;
const point = curve.getPoint(progress);
airplane.position.copy(point);
// 计算飞机朝向
const tangent = curve.getTangent(progress);
const up = new THREE.Vector3(0, 1, 0);
const matrix = new THREE.Matrix4();
matrix.lookAt(new THREE.Vector3(0, 0, 0), tangent, up);
airplane.quaternion.setFromRotationMatrix(matrix);
// 更新轨道控制器
controls.update();
// 渲染场景
renderer.render(scene, camera);
}
// 处理窗口大小变化
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// 开始动画
animate();
代码
github
https://github.com/calmound/threejs-demo/tree/main/airplane (opens in a new tab)
gitee
https://gitee.com/calmound/threejs-demo/tree/main/airplane (opens in a new tab)