封面图

第183期:用ThreeJS创建一个小飞机_初始化

这个实现过程比较简单,主要用来练习一下ThreeJS中的基本操作,场景、灯光、相机、几何体。

实现过程

要是实现这个demo ,我们首先必须要先创建一个基本的场景,先把舞台搭建好,这些都是正常的流程,比如我这里用的vue3起的项目,那么代码如下:

<template>
  <div ref="world" class="gemo-wrapper">

  </div>
</template>
<script setup>
import { ref, unref, onMounted, watch } from 'vue'
import { Input, InputNumber } from 'ant-design-vue'
import * as THREE from 'three'
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer.js'
import { CSS2DRenderer } from 'three/addons/renderers/CSS2DRenderer.js'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader';
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader';
import * as dat from 'dat.gui'
import gsap from 'gsap'

let scene
let camera
let fov
let asp
let near
let far
let height

let width
let renderer

const world = ref();

const colors = {
  red: 0xf25346,
  white: 0xd8d0d1,
  brown: 0x59332e,
  pink: 0xF5986E,
  brownDark: 0x23190f,
  blue: 0x68c3c0,
}

const init = () => {
  
  createScene()
  createLights()
  createPlane()
  // loop
  loop()
  var controls = new OrbitControls(camera, renderer.domElement);
  controls.enablePan = false;
}

onMounted(() => {
  init()
})

</script>

然后我们需要把createScene以及createLights补全,因为是demo,所以大家不要在意变量的定义规范,自己喜欢就好,代码如下:

const createScene = () => {
  height = world.value.clientHeight
  width = world.value.clientWidth

  scene = new THREE.Scene()
  asp = width / height
  fov = 60
  near = 1
  far = 10000

  camera = new THREE.PerspectiveCamera(
    fov,
    asp,
    near,
    far
  );
  // camera.lookAt(0, 0, 0)
  camera.position.x = 0;
  camera.position.z = 200;
  camera.position.y = 100;

  renderer = new THREE.WebGLRenderer({
    alpha: true,
    antialias: true
  })
  renderer.setSize(width, height)
  renderer.shadowMap.enabled = true
  world.value.appendChild(renderer.domElement)
}

// 灯光
let hemisphereLight, shadowLight
const createLights = () => {
  // 用一个半球光来展示渐变的云朵
  // 第一个参数是天空的颜色,第二个参数是地面颜色,第三个参数是光线强度
  hemisphereLight = new THREE.HemisphereLight(0xaaaaaa, 0x000000, .9)
  // 从指定位置放置一个平行光,代表太阳
  shadowLight = new THREE.DirectionalLight(0xffffff, .9)
  // 设置光线的方向
  shadowLight.position.set(150, 350, 350)
  // 允许投影
  shadowLight.castShadow = true
  // 设置允许投影的范围
  shadowLight.shadow.camera.left = -400
  shadowLight.shadow.camera.right = 400
  shadowLight.shadow.camera.top = 400
  shadowLight.shadow.camera.bottom = -400
  shadowLight.shadow.camera.near = 1
  shadowLight.shadow.camera.far = 1000
  // 定义阴影的分辨率,越高效果越好,但是相对就消耗性能
  shadowLight.shadow.mapSize.width = 2048
  shadowLight.shadow.mapSize.height = 2048

  // 将他们添加到场景中
  scene.add(hemisphereLight)
  scene.add(shadowLight)
}

关于灯光,hemisphereLight是半球光,这个光非常有意思,一部分是从下往上的平行光,一部分是从上半球往中心点的光。如图:

第183期:用ThreeJS创建一个小飞机_JavaScript_02

它的第一个参数是天空的颜色,第二个参数是地面颜色,第三个参数是光线强度。

设置场景和灯光之后,我们就要开始思考怎么绘制这个小飞机了,其实也很简单,就是一推方块儿,将他们设置成合适的大小,放在合适的位置,就行,如图:

第183期:用ThreeJS创建一个小飞机_着色器_03

小飞机可以拆分成,机舱、发动机、机翼、机尾、螺旋桨这几个部分,那么我们就可以按照这个逻辑分别用几何体将这部分绘制出来,然后组装到一起即可。

绘制飞机

我这里定义了一个Plane类。

export class Plane {
  mesh;
  geomCockpit; 
  matCockpit;
  cockpit; // 机舱
  engine; // 引擎
  tail; // 机尾巴
  wing; // 鸡翅膀
  propeller; // 螺旋桨
  blade; // 螺旋桨叶片
  constructor() {
    this.mesh = new THREE.Object3D();
    this.geomCockpit = new THREE.BoxGeometry(60, 50, 50, 1, 1, 1);
    this.matCockpit = new THREE.MeshPhongMaterial({
      color: colors.red,
      shading: THREE.FlatShading,
    });
    this.cockpit = new THREE.Mesh(this.geomCockpit, this.matCockpit);
    this.mesh.add(this.cockpit);
    this.init();
  }
  
  init() {
    this.createEngine();
    this.createTail();
    this.createWing();
    this.createPropeller();
    this.createBlades();
  }
  }

构造器中对机舱做了个初始化,然后执行了init方法,init方法中又分别创建了机舱、发动机、机翼、机尾、螺旋桨,我们只需要挨个实现即可。

绘制引擎

createEngine() {
    let geomEngine = new THREE.BoxGeometry(20, 50, 50, 1, 1, 1);
    let matEngine = new THREE.MeshPhongMaterial({
      color: colors.red,
      shading: THREE.FlatShading,
    });
    this.engine = new THREE.Mesh(geomEngine, matEngine);
    this.engine.position.x = 40;
    this.engine.castShadow = true;
    this.engine.receiveShadow = true;
    console.log("engine", this.engine);
    this.mesh.add(this.engine);
  }

绘制尾巴

createTail() {
    let geomTail = new THREE.BoxGeometry(15, 20, 5, 1, 1, 1);
    let matTail = new THREE.MeshPhongMaterial({
      color: colors.red,
      shading: THREE.FlatShading,
    });
    this.tail = new THREE.Mesh(geomTail, matTail);
    this.tail.position.set(-35, 25, 0);
    this.tail.castShadow = true;
    this.tail.receiveShadow = true;
    this.mesh.add(this.tail);
  }

绘制鸡翅膀

createWing() {
    let geomWing = new THREE.BoxGeometry(40, 8, 150, 1, 1, 1);
    let matWing = new THREE.MeshPhongMaterial({
      color: colors.red,
      shading: THREE.FlatShading,
    });
    this.wing = new THREE.Mesh(geomWing, matWing);
    this.wing.castShadow = true;
    this.wing.receiveShadow = true;
    this.mesh.add(this.wing);
  }

绘制螺旋桨和叶片

createPropeller() {
    let geomPropeller = new THREE.BoxGeometry(20, 10, 10, 1, 1, 1);
    let matPropeller = new THREE.MeshPhongMaterial({
      color: colors.brownDark,
      shading: THREE.FlatShading,
    });
    this.propeller = new THREE.Mesh(geomPropeller, matPropeller);
    this.propeller.castShadow = true;
    this.propeller.receiveShadow = true;
  }

  createBlades() {
    let geomBlades = new THREE.BoxGeometry(1, 100, 20, 1, 1, 1);
    let matBlades = new THREE.MeshPhongMaterial({
      color: colors.brownDark,
      shading: THREE.FlatShading,
    });

    this.blade = new THREE.Mesh(geomBlades, matBlades);
    this.blade.position.set(8, 0, 0);
    this.blade.castShadow = true;
    this.blade.receiveShadow = true;
    this.propeller.add(this.blade);
    this.propeller.position.set(50, 0, 0);
    this.mesh.add(this.propeller);
  }

可以看到,这里没什么比较特别需要注意的地方,都是一些简单的创建几何体,然后设置位置,开启投影。

如果非要有一点需要注意的话,可以看下THREE.FlatShading相关的资料,它是threejs中默认的一个着色器,计算三角形质心并根据该向量填充所有颜色来完成。

向场景中添加飞机

完成Plane类之后,我们就可以向场景中添加飞机了,很简单:

let plane
const createPlane = () => {
  plane = new Plane()
  console.log('plane', plane)
  plane.mesh.scale.set(.25, .25, .25)
  plane.mesh.position.y = 50
  scene.add(plane.mesh)
}

然后加个动画跟轨道控制:

var controls = new OrbitControls(camera, renderer.domElement);
 controls.enablePan = false;

const loop = () => {
  plane.propeller.rotation.x += 0.3;
  sky.rotation.z += .01;
  renderer.render(scene, camera)
  requestAnimationFrame(loop)
}

就可以实现下面的效果了:

第183期:用ThreeJS创建一个小飞机_前端_04