vue+three.js 加载模型,并让模型随航线飞行

发布于:2025-07-03 ⋅ 阅读:(21) ⋅ 点赞:(0)

        嗨,我是小路。今天主要和大家分享的主题是“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)的方向性,介于 -11 之间的数值。用于模拟阳光穿过大气层时,由于空气中大颗粒(如尘埃、水滴、气溶胶)导致的 前向或后向散射效果。

属性列表 列表说明
0 各向同性散射(光线均匀地向所有方向散射)
>0 前向散射增强(光线更倾向于沿原方向传播)
<0 后向散射增强(光线更倾向于反方向散射)

6.setFromSphericalCoords

定义:米氏散射(Mie Scattering)的方向性,介于 -11 之间的数值。用于模拟阳光穿过大气层时,由于空气中大颗粒(如尘埃、水滴、气溶胶)导致的 前向或后向散射效果。

注意:单位是 弧度(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、在屏幕尺寸调整时,不需要重新加载模型,只需要调整相机的视图和角度,以及重新渲染就可以了,没必要再加载模型。

都看到这里了,记得【点赞】+【关注】哟。

参考文件: