嗨,我是小路。今天主要和大家分享的主题是“vue+three.js 加载模型,并让模型随航线飞行”。
这两天在gitee上找到一个three.js项目,主要是用来加载gltf模型,并让模型随着移动。今天主要是对这个学习过程的梳理。
项目示例图
一、主要知识点
1.setScalar
定义:主要是将THREE.Vector3中的x、y、z设置为相同的标量。
属性列表 | 列表说明 |
2.turbidity
定义:浑浊度,模糊不清的程度。在计算机图形学中,turbidity 常用于模拟大气散射效果,比如天空和阳光的真实感渲染。
数值 | 意义 |
0 - 1 | 非常清晰 |
1-5 | 一般清晰 |
5-50 | 浑浊 |
>50 | 非常浑浊 |
3.rayleigh
定义:光照的强度。是一个常用于物理、光学、图形渲染等领域的术语,散射的意思。主要是天空的颜色。数值在0-4之间,数值越高,蓝天越鲜艳。
属性列表 | 列表说明 |
0 | 无瑞利散射,天空呈灰色或黑色 |
1-2 | 正常蓝天效果(适合晴天) |
>3 | 蓝天非常强烈,可能显得不自然 |
4.mieCoefficient
定义:米氏系数是控制 Mie 散射强度 的参数。用于模拟空气中较大颗粒(如灰尘、水滴、气溶胶)对光线的散射效果,主要影响日出/日落时的橙红色调。
属性列表 | 列表说明 |
0 | 没有米氏散射,天空更偏向瑞利散射效果(蓝色为主) |
0.005-0.02 | 正常大气下的柔和日光或黄昏效果 |
0.05 ~ 0.08 | 天空变得更“雾蒙蒙”,阳光方向更明显,适合雾霾、沙尘天气;明显的阳光散射,适合黄昏、雾霾天 |
0.1+ | 强烈的“光污染”效果,适合极端天气或风格化场景 |
5.mieDirectionalGlar
定义:米氏散射(Mie Scattering)的方向性,介于 -1
和 1
之间的数值。用于模拟阳光穿过大气层时,由于空气中大颗粒(如尘埃、水滴、气溶胶)导致的 前向或后向散射效果。
属性列表 | 列表说明 |
0 | 各向同性散射(光线均匀地向所有方向散射) |
>0 | 前向散射增强(光线更倾向于沿原方向传播) |
<0 | 后向散射增强(光线更倾向于反方向散射) |
6.setFromSphericalCoords
定义:米氏散射(Mie Scattering)的方向性,介于 -1
和 1
之间的数值。用于模拟阳光穿过大气层时,由于空气中大颗粒(如尘埃、水滴、气溶胶)导致的 前向或后向散射效果。
注意:单位是 弧度(radians),如果你使用的是角度(degrees),记得先用 THREE.MathUtils.degToRad()
转换。
参数 | 含义 |
radius | 半径 |
phi | 极角,从正 Y 轴向下旋转的角度,单位是弧度,需要转换 |
theta | 方位角,绕 Y 轴旋转的角度,单位是弧度,需要将角度转换为弧度 |
7.sunPosition
定义:太阳的位置。用于控制光线方向和散射效果,在使用 THREE.Sky
模拟大气时尤为重要
极角 | 意义 |
phi = 0 | 太阳在正上方(头顶) |
phi = Math.PI / 2 | 太阳在地平线上 |
phi > Math.PI / 2 | 太阳在地平面以下(夜晚) |
8.sunPosition
定义:太阳的位置。用于控制光线方向和散射效果,在使用 THREE.Sky
模拟大气时尤为重要
极角 | 意义 |
phi = 0 | 太阳在正上方(头顶) |
phi = Math.PI / 2 | 太阳在地平线上 |
phi > Math.PI / 2 | 太阳在地平面以下(夜晚) |
9.getPoints
定义:用于获取路径上的一系列点;点通常用于绘制线段、生成几何体轮廓、创建沿路径移动的动画。参数主要是要返回的点的数量(即路径被分成多少段)默认值:12,数值越大,点越密集,路径越平滑
10.setFromPoints
定义:它可以根据一组三维点(THREE.Vector3
)创建一个几何体,常用于绘制线段、点云或自定义形状。
注意:数据量有限。太多的点容易影响性能。
11.computeLineDistances
定义:用于计算线段中每个顶点到起点的累计距离。这个功能在你需要根据线段长度进行着色、动画或渐变效果时非常有用。
应用:
1、遍历线段中的所有顶点;
2、计算从第一个顶点开始,每一段线段的累计长度;
3、将结果存储在几何体的 lineDistance 属性中(作为顶点属性);
4、可用于后续的着色器处理(如颜色渐变、粒子沿路径运动等);
12.getTangent
定义:用于获取曲线在某个参数位置上的切线方向向量。
二、实例代码
<template>
<div class="pageBox">
</div>
</template>
<script setup>
import { onMounted, reactive, ref } from 'vue';
import * as THREE from "three";
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
import { Sky } from "three/examples/jsm/objects/Sky";
import { FogExp2 } from "three";
let scene, camera, renderer, controls, fillLight, ambientLight, mainLight;
// 加载飞机模型
const loader = new GLTFLoader();
const initThree = () => {
// 创建场景
scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0xdfe9f3, 0.02); // 添加雾效果
// 创建相机
camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
);
camera.position.set(0, 5, 10);
// 创建渲染器
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);
// 添加轨道控制器
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.dampingFactor = 0.05;
controls.maxDistance = 50;
controls.minDistance = 3;
// 添加光源
ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 主光源
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);
// 添加补光
fillLight = new THREE.DirectionalLight(0x8088ff, 0.4);
fillLight.position.set(-5, 3, -5);
scene.add(fillLight);
}
let sky, sun, uniforms, phi, theta;
const initOther = () => {
// 添加天空
sky = new Sky();
//将向量的 x、y 和 z 分量全部设置为指定的标量值 1000
sky.scale.setScalar(1000);
scene.add(sky);
//阳光
sun = new THREE.Vector3();
uniforms = sky.material.uniforms;
//清晰度
uniforms["turbidity"].value = 10;
//用于模拟地球天空的颜色变化和阳光的散射效果 散射颜色
uniforms["rayleigh"].value = 2;
//用于模拟空气中较大颗粒(如灰尘、水滴、气溶胶)对光线的散射效果 散射强度
uniforms["mieCoefficient"].value = 0.005;
//于模拟阳光穿过大气层时,由于空气中大颗粒(如尘埃、水滴、气溶胶)导致的 前向或后向散射效果。 散射的方向
uniforms["mieDirectionalG"].value = 0.8;
//将88度转化成弧度
phi = THREE.MathUtils.degToRad(90 - 2);
//将180度转化成弧度
theta = THREE.MathUtils.degToRad(180);
//设置阳光三维向量的值 半径 极角(从Y轴向下偏转) 方位角(绕Y轴旋转的角度)
sun.setFromSphericalCoords(1, phi, theta);
uniforms["sunPosition"].value.copy(sun);
}
onMounted(() => {
initThree();
initOther();
initModel();
initLine();
animate();
// 处理窗口大小变化 只需要设置相机 和重新渲染
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
})
let airplane;
const initModel = () => {
// 创建一个组来存放飞机模型
airplane = new THREE.Group();
scene.add(airplane);
loader.load(
"./models/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);
}
);
}
let curve, points, pathGeometry, pathMaterial, pathLine;
const initLine = () => {
// 创建飞行路径
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),
]);
// 可视化路径(使用渐变材质)
//于获取路径上的一系列点 这些点通常用于绘制线段、生成几何体轮廓、创建沿路径移动的动画
points = curve.getPoints(50);
//可以根据一组三维点(THREE.Vector3)创建一个几何体,常用于绘制线段、点云或自定义形状
pathGeometry = new THREE.BufferGeometry().setFromPoints(points);
pathMaterial = new THREE.LineDashedMaterial({
color: 0xffffff,
dashSize: 0.5,
gapSize: 0.3,
opacity: 0.5,
transparent: true,
});
pathLine = new THREE.Line(pathGeometry, pathMaterial);
pathLine.computeLineDistances(); // 计算虚线
scene.add(pathLine);
}
// 动画参数
let progress = 0;
let 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);
}
</script>
<style scoped lang="less">
.pageBox {
width: 100%;
//height: 100vh;
padding: 0;
margin: 0;
display: flex;
justify-content: space-between;
align-items: center;
.rightBox {
width: 100%;
height: 100%;
background: yellow;
}
}
</style>
三、复盘总结
1、总共分为多个部分:天空、飞行路线、加载模型、模型随着线的方向移动、最后是屏幕缩放。
2、在屏幕尺寸调整时,不需要重新加载模型,只需要调整相机的视图和角度,以及重新渲染就可以了,没必要再加载模型。
都看到这里了,记得【点赞】+【关注】哟。
参考文件: