vue+threejs写界面:文字/图片标注

1,101 阅读3分钟

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


写在前面

本文用vue+threejs写界面标注。

下面是效果图:

image.png

代码说明

1.html+css

一个存放标注的id,里面是一张图片和文字“兔子”,我们要将包含这些信息的html节点插入到界面中

<template>
  <div class="item">
    <div id="THREE54">
      <div id="Rabbit">
        <img src="../../assets/imgs/tag.png" class="img" />
        <div class="name">兔子</div>
      </div>
    </div>
  </div>
</template>

css用的是less的语法,对图片和文字的样式进行了调整

<style lang="less" scoped>
#Rabbit {
  position: relative;
  .img,
  .name {
    position: absolute;
    width: 80px;
    left: -40px;
  }
  .name {
    top: 12px;
    text-align: center;
    font-size: 18px;
    font-weight: bold;
    color: #0b49b0;
  }
}
</style>

标注的图片用的是以下图片:

tag.png

  1. 引入threejs和需要的模块

这次主要用到的是CSS 2D渲染器:CSS2DRendererCSS2DObject

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/examples/jsm/renderers/CSS2DRenderer.js"; // CSS 2D渲染器
  1. data中定义的变量

camera相机,scene场景,dracoLoader模型加载器,renderer场景渲染器,controls轨道控制器,labelRendererCSS 2D渲染器

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

      labelRenderer: null,
    };
  },

4.mounted()中调用的方法

注释中说明了各方法的用途

  mounted() {
    this.dracoLoader = new DRACOLoader();
    this.dracoLoader.setDecoderPath("js/libs/draco/");
    this.dracoLoader.setDecoderConfig({ type: "js" });
    this.initScene(); // 创建场景
    this.initCamera(); // 创建相机
    this.initLight(); // 创建灯光
    this.initRenderer(); // 创建渲染器
    this.initLabelRenderer(); // 创建 CSS 2D渲染器
    this.initControls(); //创建轨道控制器
    this.initGround(); // 创建地面
    this.initModel(); // 加载模型
  },
  1. initScene()

创建场景,并且设置场景背景颜色

    initScene() {
      this.scene = new THREE.Scene();
      this.scene.background = new THREE.Color(0x000000); // 设置场景背景颜色
    },
  1. initCamera()

创建一个透视相机,并设置相机的位置,然后将相机添加到场景中

    initCamera() {
      this.camera = new THREE.PerspectiveCamera(
        35,
        (window.innerWidth - 201) / window.innerHeight,
        1,
        500
      ); // 透视相机
      this.camera.position.x = 0.5;
      this.camera.position.y = 0.5; // 设置相机的位置
      this.camera.position.z = 1.8;
      this.scene.add(this.camera); // 将相机添加到场景中
    },
  1. initLight()

创建一个平行光light,并设置平行光的方向,然后将平行光添加到场景中

再创建一个环境光ambLight,然后将环境光添加到场景中

    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. initRenderer()

创建渲染器,并将渲染器节点插入到html中

    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("THREE54").appendChild(this.renderer.domElement);
    },
  1. initLabelRenderer()

创建CSS 2D渲染器,并将渲染器节点插入到html中

    initLabelRenderer() {
      this.labelRenderer = new CSS2DRenderer();
      this.labelRenderer.setSize(window.innerWidth - 201, window.innerHeight);
      this.labelRenderer.domElement.style.position = "absolute";
      this.labelRenderer.domElement.style.top = "0px";
      document
        .getElementById("THREE54")
        .appendChild(this.labelRenderer.domElement);
    },
  1. initControls()

创建轨道控制器,用于鼠标控制界面缩放旋转移动,这里控制器的第二个参数传的是CSS 2D渲染器的节点this.labelRenderer.domElement

    initControls() {
      this.controls = new OrbitControls(
        this.camera,
        this.labelRenderer.domElement
      );
      this.controls.addEventListener("change", this.render);
      this.controls.enableDamping = true; // 开启惯性
    },
  1. initGround()

创建地面,创建一个1x1的地面

    initGround() {
      const ground = new THREE.Mesh(
        new THREE.BoxGeometry(1, 0.0015, 1),
        new THREE.MeshPhongMaterial({
          color: 0x999999,
          depthWrite: false,
          transparent: true,
          opacity: 1,
        })
      );
      ground.receiveShadow = true;
      this.scene.add(ground);
    },
  1. initModel()

加载模型,并将写好的图片和文字添加到模型中mesh.add(RabbitTag),并设置标注的位置RabbitTag.position.set(mesh.position.x, 0.25, mesh.position.z);,模型加载成功后调用animate()方法

    initModel() {
      this.dracoLoader.load("/models/models/draco/bunny.drc", (geometry) => {
        geometry.computeVertexNormals();

        const material = new THREE.MeshStandardMaterial({
          color: 0xffffff,
        });
        let mesh = new THREE.Mesh(geometry, material);
        mesh.castShadow = true;
        mesh.receiveShadow = true;
        mesh.position.y = -0.035;

        // x左右 z前后
        this.Rabbit = document.getElementById("Rabbit");
        const RabbitTag = new CSS2DObject(this.Rabbit);
        RabbitTag.position.set(mesh.position.x, 0.25, mesh.position.z);
        mesh.add(RabbitTag);
        RabbitTag.layers.set(0);

        this.scene.add(mesh);

        this.dracoLoader.dispose();

        this.animate();
      });
    },
  1. animate()和render()

因为轨道控制器开启了惯性this.controls.enableDamping = true;,所以需要在动画中加入this.controls.update();

render()方法就是渲染场景、相机和渲染文字图片标注

    animate() {
      requestAnimationFrame(this.animate);

      this.controls.update();

      this.render();
    },
    render() {
      this.renderer.render(this.scene, this.camera);
      this.labelRenderer.render(this.scene, this.camera);
    },

写在最后

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