vue+threejs控制相机:环绕物体

439 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情


写在前面

本文用vue+threejs控制相机环绕物体。

说明:在创建的场景中,有一棵树,一个女孩和一只狗,这时,我们想要环绕展示小狗。

以下是演示gif:

20221020_145930.gif

完整代码和说明

  1. html和css

创建一个插入渲染器dom节点的id容器,创建一个按钮,用来控制开启相机环绕小狗和停止环绕小狗

<template>
  <div class="item">
    <div id="THREE51"></div>
    <div class="btn_box">
      <el-button @click="flyToPosi">{{
        start ? "停止环绕" : "环绕小狗"
      }}</el-button>
    </div>
  </div>
</template>

<style lang="less" scoped>
.btn_box {
  position: absolute;
  top: 10px;
  left: 210px;
}
</style>
  1. 引入three.js和需要的模块

OrbitControls轨道控制器,GLTFLoader模型加载器

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
  1. data()中定义的变量

camera相机,cameraPerspective用来环绕的相机,scene场景,gltfLoader模型加载器,renderer场景渲染器,controls轨道控制器,manager模型加载进度管理,tree树模型,dog小狗模型,girl女孩模型,start是否开启相机环绕

  data() {
    return {
      camera: null,
      cameraPerspective: null,
      scene: null,
      gltfLoader: null,
      renderer: null,
      controls: null,

      manager: null,

      tree: null,
      dog: null,
      girl: null,

      start: false,
    };
  },
  1. mounted()中调用的函数

每个函数的意义在注释中都有说明

  mounted() {
    this.manager = new THREE.LoadingManager();
    this.gltfLoader = new GLTFLoader(this.manager);
    this.initScene(); // 创建场景
    this.initCamera(); // 创建相机
    this.initLight(); // 创建灯光
    this.initRenderer(); // 创建渲染器
    this.initControls(); //创建轨道控制器
    this.initModel(); // 加载模型

    this.manager.onLoad = () => {
      this.animate();
      console.log("Loading complete!");
    };
  },
  1. 创建场景
    initScene() {
      this.scene = new THREE.Scene();
    },
  1. 创建相机

创建两个透视相机,一个是camera,另一个是cameraPerspective,cameraPerspective用来环绕物体

    initCamera() {
      this.camera = new THREE.PerspectiveCamera(
        35,
        (window.innerWidth - 201) / window.innerHeight,
        1,
        600
      ); // 透视相机
      this.camera.position.x = 10.5;
      this.camera.position.y = 32.3; // 设置相机的位置
      this.camera.position.z = 104.3;

      this.cameraPerspective = new THREE.PerspectiveCamera(
        35,
        (window.innerWidth - 201) / window.innerHeight,
        1,
        30
      );
    },
  1. 创建灯光

创建两个灯光,一个是平行光DirectionalLight,另一个是环境光AmbientLight

    initLight() {
      const light = new THREE.DirectionalLight(0xffffff); // 平行光
      light.position.set(0.5, 1.0, 0.5).normalize(); // 设置平行光的方向,从(0.5, 1.0, 0.5)->target一般(0, 0, 0)
      this.scene.add(light); // 将灯光添加到场景中

      const ambLight = new THREE.AmbientLight(0xf0f0f0, 0.1); // 环境光
      this.scene.add(ambLight);
    },
  1. 创建渲染器

创建渲染器,并将渲染器节点插入到dom中,我们的场景需要靠它来渲染

    initRenderer() {
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(window.innerWidth - 201, window.innerHeight);
      document.getElementById("THREE51").appendChild(this.renderer.domElement);
    },
  1. 创建轨道控制器

有了它可以通过鼠标来控制界面的平移缩放旋转

    initControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      this.controls.addEventListener("change", this.render);

      this.controls.target.set(22, 26, -7); // 控制器的焦点
      this.controls.update();
    },
  1. 加载模型

使用gltfLoader加载器来加载三个模型,分别是树模型,小狗模型和女孩模型

    initModel() {
      this.gltfLoader.load(
        "/models/models/gltf/urban_tree/scene.gltf",
        (gltf) => {
          gltf.scene.scale.set(10, 10, 10);
          gltf.scene.position.set(40, 0, 0);
          this.tree = gltf.scene;
          this.scene.add(gltf.scene);
        }
      );
      this.gltfLoader.load("/models/models/gltf/shiba/scene.gltf", (gltf) => {
        gltf.scene.scale.set(3, 3, 3);
        gltf.scene.position.set(-5, 3, 1);
        this.dog = gltf.scene;
        this.scene.add(gltf.scene);
      });
      this.gltfLoader.load("/models/models/gltf/matilda/scene.gltf", (gltf) => {
        gltf.scene.scale.set(0.1, 0.1, 0.1);
        this.girl = gltf.scene;
        this.scene.add(gltf.scene);
      });
    },
  1. 渲染场景和相机环绕的动画

当start为true时,开启了相机环绕,这时候相机就应该开始环绕展示小狗,我们让环绕的相机cameraPerspective一直朝向(lookAt)小狗的位置(this.dog.position),然后改变相机的位置,使其能环绕小狗运动,20是环绕的半径,(pos.x,pos.y)是环绕的中心,环绕开始后同时隐藏女孩的模型this.girl.visible = false;,以防在环绕过程中女孩的脚挡住视线,环绕停止后再显示女孩的模型this.girl.visible = true,环绕中我们渲染用的相机是我们创建的cameraPerspective相机,环绕停止后用camera相机

    animate() {
      requestAnimationFrame(this.animate);
      this.render();
    },
    render() {
      if (this.start) {
        const r = Date.now() * 0.0007;
        let pos = this.dog.position;
        this.cameraPerspective.position.x = 20 * Math.cos(r) + pos.x;
        this.cameraPerspective.position.z = 20 * Math.sin(r) + pos.z;
        this.cameraPerspective.lookAt(pos);
        this.girl.visible = false;
        this.renderer.render(this.scene, this.cameraPerspective);
      } else {
        this.girl && (this.girl.visible = true);
        this.renderer.render(this.scene, this.camera);
      }
    },
  1. 按钮事件

点击按钮后切换start的值

    flyToPosi() {
      this.start = !this.start;
    },

写在最后

以上就是所有的代码和说明。