vue写three.js例子-控制-飞行控制器

288 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第3天,点击查看活动详情


回顾了一下昨天写的文章 vue写three.js例子-控制-物品拖动,代码编写过程写的过于简略了,一些解释和想法也没有表达出来(理科生真的文笔有限呀!所以今天的例子打算再写的详细一点。

今天要分享的是用vue写three.js例子-飞行漫游。先说一下大致的思路:先绘制一个场景,包括地球,地球云层,月球,和星星,然后通过飞行控制器,我们可以模拟自己在太空中飞行。以下是演示gif(有点大:从地球飞行到月球再飞回来,降临到表面的时候速度会变慢

20220812_143708.gif

依旧省略three.js的安装、引用过程,因为第一篇文章已经说过了。

昨天的文章少了一个步骤,在这里补一下:

贴图文件的加载

上一篇文章引入了一个木纹的贴图文件,其实是three.js官方例子中的文件(作为一个前端咱也不会设计,只能用用现成的,就是下面的的textures文件,放到自己vue项目下的public目录下即可,建议models和js文件也一起拷贝过来,后面的例子可能会用到

image.png

下面贴上我的代码(并附上我认为比较详细的讲解。

同样是先写一个容器

<div class="item">
  <div id="THREE12"></div>
</div>

然后是mounted方法

因为下面要创建地球模型、地球云层模型、月球模型、星星模型,所以一开始定义了一堆变量来存,我先一一说明一下:

radius:地球的半径

rotationSpeed:地球自转的速度

cloudsScale:云层球体相较于地球球体的缩放比例

moonScale:月球球体相较于地球球体的缩放比例

geometry:存球体的变量

meshPlanet:存地球网格模型的变量

meshClouds:存云层网格模型的变量

meshMoon:存月球网格模型的变量

dirLight:存平行光的变量

d, dPlanet, dMoon:用来算速度,作出靠近球体表面减速的效果

dMoonVec:一个三维坐标

new THREE.Clock():相当于Date().now()

mounted() {
 this.initThreejs();
},
methods: {
  initThreejs() {
    const radius = 6371;
    const rotationSpeed = 0.02;

    const cloudsScale = 1.005;
    const moonScale = 0.23;

    let SCREEN_HEIGHT = window.innerHeight;
    let SCREEN_WIDTH = window.innerWidth - 201;

    let camera, controls, scene, renderer;
    let geometry, meshPlanet, meshClouds, meshMoon;
    let dirLight;

    const textureLoader = new THREE.TextureLoader();

    let d, dPlanet, dMoon;
    const dMoonVec = new THREE.Vector3();

    const clock = new THREE.Clock();

    init();
    animate();
  }
}

下面是init()的代码:

创建相机

new THREE.PerspectiveCamera():使用该方法创建一个透视相机,相机的垂直锥面的角度是25,相机视图锥体的长宽比是SCREEN_WIDTH / SCREEN_HEIGHT,相机视图锥体近端面的距离是50,相机视图锥体远端面的距离是1千万

1e7:1后面7个0,10000000,1千万

camera.position.z:相机的z轴位置

camera = new THREE.PerspectiveCamera(
  25,
  SCREEN_WIDTH / SCREEN_HEIGHT,
  50,
  1e7
);
camera.position.z = radius * 10;

创建场景

new THREE.Scene():使用该方法创建一个场景

scene = new THREE.Scene();

创建灯光

new THREE.DirectionalLight(0xffffff):使用该方法创建平行光,光的颜色为0xffffff,光照强度缺省,默认为1

dirLight.position.set(-1, 0, 1).normalize():设置光线的照射方向,我理解为从(0,0,0)-(-1,0,1)的方向

dirLight = new THREE.DirectionalLight(0xffffff);
dirLight.position.set(-1, 0, 1).normalize();
scene.add(dirLight);

创建地球

new THREE.MeshPhongMaterial():一种用于具有镜面高光的光泽表面的材质

  • specular:用于设置材质的高光颜色
  • shininess:用于设置高亮的程度,越高的值越闪亮
  • map:颜色贴图,这里贴了一张世界地图

new THREE.SphereGeometry(radius, 100, 50):创建一个半径为radius的球体

new THREE.Mesh(geometry, materialNormalMap):创建一个形状为球体,材质为上面定义的镜面高光材质的网格模型

const materialNormalMap = new THREE.MeshPhongMaterial({
  specular: 0x333333,
  shininess: 15,
  map: textureLoader.load("textures/planets/earth_atmos_2048.jpg"),
});

geometry = new THREE.SphereGeometry(radius, 100, 50);

meshPlanet = new THREE.Mesh(geometry, materialNormalMap);

scene.add(meshPlanet);

创建云层

new THREE.MeshLambertMaterial():一种非光泽表面的材质,没有镜面高光

  • map:颜色贴图,这里贴了云

new THREE.Mesh(geometry, materialClouds):创建一个形状为球体,材质为云的网格模型

meshClouds.scale.set(cloudsScale, cloudsScale, cloudsScale):云层缩放cloudsScale倍数

const materialClouds = new THREE.MeshLambertMaterial({
  map: textureLoader.load("textures/planets/earth_clouds_1024.png"),
});

meshClouds = new THREE.Mesh(geometry, materialClouds);
meshClouds.scale.set(cloudsScale, cloudsScale, cloudsScale);

scene.add(meshClouds);

创建月球

这里贴了月亮表面的材质,创建了一个形状为球体,材质为月亮的网格模型,并且进行了moonScale倍的缩放,位置在离地球radius * 5距离的地方。

const materialMoon = new THREE.MeshPhongMaterial({
  map: textureLoader.load("textures/planets/moon_1024.jpg"),
});

meshMoon = new THREE.Mesh(geometry, materialMoon);
meshMoon.position.set(radius * 5, 0, 0);
meshMoon.scale.set(moonScale, moonScale, moonScale);
scene.add(meshMoon);

渲染器

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(SCREEN_WIDTH, SCREEN_HEIGHT);
document.getElementById("THREE12").appendChild(renderer.domElement);

飞行控制器

new FlyControls():创建一个飞行控制器

  • movementSpeed:移动速度,默认为1
  • rollSpeed:旋转速度,默认为0.005
  • autoForward:若该值设为true,初始变换后,相机将自动向前移动(且不会停止)。默认为false。
  • dragToLook:若该值设为true,你将只能通过执行拖拽交互来环视四周。默认为false。

以上为官方说明,暂无更多理解

controls = new FlyControls(camera, renderer.domElement);

controls.movementSpeed = 10;
controls.rollSpeed = Math.PI / 32;
controls.autoForward = false;
controls.dragToLook = false;

渲染方法

dMoonVec.subVectors(camera.position, meshMoon.position); // 将向量dMoonVec设置为camera.position - meshMoon.position

  function animate() {
    requestAnimationFrame(animate);
    render();
  }

  function render() {
    // 地球和云层自转

    const delta = clock.getDelta();

    meshPlanet.rotation.y += rotationSpeed * delta;
    meshClouds.rotation.y += 1.25 * rotationSpeed * delta;

    // 缓慢降落至表面

    dPlanet = camera.position.length();

    dMoonVec.subVectors(camera.position, meshMoon.position);
    dMoon = dMoonVec.length();

    if (dMoon < dPlanet) {
      d = dMoon - radius * moonScale * 1.01;
    } else {
      d = dPlanet - radius * 1.01;
    }

    controls.movementSpeed = 0.33 * d;
    controls.update(delta);

    renderer.render(scene, camera);
  }