Bootstrap

使用three.js在网页里展示3D模型

本篇本章是在vue框架下进行的网页搭建,使用原生版本也可以进行three.js的使用。不过现在网上流行的大多是框架内的搭建方法

首先需要安装three.js。

在vue里配置three.js的命令是

npm install three

这里稍微提一嘴想要在原生html的情况下使用three.js的话需要找到较早之前的版本,并且由于vue和原生还是有一些区别的,所以这里不会过多的提及。

关于找较早的版本可以去下面链接中去获取,通过查找可以通过133的版本找到原生html可以使用的版本,像是GLTFLoader文件也可以通过在目录three.js-r133\examples\js\loaders中找到。其他文件也可以在上级目录three.js-r133\examples\js中找到,可以看需求使用

Releases · mrdoob/three.js (github.com)icon-default.png?t=N7T8https://github.com/mrdoob/three.js/releases

先展示下页面的效果,这里是在放入的3D模型中加入自己需求的设计。

首先需要把准备好的模型文件放到public文件夹中,这里准备的是gltf类型的模型文件

搭建页面需要一个位置来放置画布,其中id为container的位置被我指定为画布并为其设置一下样式表

<template>
  <div id="box">
    <div id="container"></div>
  </div>
</template>
#container {
  position: absolute;
  width: 100%;
  height: 100%;
}

然后需要绘制画布和设置摄像头和光源等各种参数

methods: {
    MapCreate() {
      // 指定场景位置
      const canvas = document.getElementById("container");

      // 创建场景
      const scene = new THREE.Scene();


      // model(这里是添加模型的位置)
      const loader = new GLTFLoader();
      loader.setCrossOrigin('anonymous');
      loader.load('./model/scene.gltf', function (gltf) {
        scene.add(gltf.scene)
      });

      // render(渲染器)
      const renderer = new THREE.WebGLRenderer();
      renderer.setSize(window.innerWidth, window.innerHeight);
      canvas.appendChild(renderer.domElement);

      // Camera(摄像头)
      const camera = new THREE.PerspectiveCamera(
          45, //视角,视角越大,看的东西越多
          window.innerWidth / window.innerHeight, //宽高比
          0.1, //近平面,相机最近看到的物体
          1000 //远平面,相机最远看到的物体
      );

      camera.position.z = 100;
      camera.lookAt(0, 0, 0);


      // Camera Control(镜头控制器)
      const control = new OrbitControls(camera, renderer.domElement);
      control.addEventListener('change', () => {
        renderer.render(scene, camera);
      });


      // Light(光源)
      const light = new THREE.AmbientLight(0xffffff);
      scene.add(light);

      // 窗口自适应
      window.addEventListener("resize", () => {
        // 更新摄像头
        camera.aspect = window.innerWidth / window.innerHeight;
        //   更新摄像机的投影矩阵
        camera.updateProjectionMatrix();
        //   更新渲染器
        renderer.setSize(window.innerWidth, window.innerHeight);
        //   设置渲染器的像素比
        renderer.setPixelRatio(window.devicePixelRatio);
      });

      // 渲染函数
      let timer = null;

      // 实时更新动画
      function animate() {
        renderer.render(scene, camera);
        timer = requestAnimationFrame(animate);
        if (TWEEN.update()) {
          cancelAnimationFrame(timer);
          timer = requestAnimationFrame(animate);
        }
      }

      animate();
      }
  }

当然要让效果显示的话不要忘了让他在开始就被调用。

mounted() {
    this.MapCreate()
  },

现在的效果是只有模型并没有图标的效果就如下图所示

想要加入图标只需要在页面加入图片的模块,这里的坐标使用其他位置的js文件来记录,Marks是我导入的模块。第一张图是模块里的数据格式

const mark_list = [
    {
        "x" : 0,
        "y" : 35,
        "z" : 0,
        "Url":"https://www.baidu.com"
    },
    {
        "x" : 0,
        "y" : 3,
        "z" : 10,
        "Url":"https://www.bilibili.com"
    }
]


export default mark_list
import Marks from '../data/marks.js'
      let marks_box = [];

      const textureLoader = new THREE.TextureLoader();
      const material = new THREE.MeshBasicMaterial({
        map: textureLoader.load("mark.png"),
        transparent: true, //使用背景透明的png贴图,注意开启透明计算
      });
      for (let i = 0; i < Marks.length; i++) {
        const geometry = new THREE.BoxGeometry(5, 5, 0);
        const mesh = new THREE.Mesh(geometry, material);
        const wp = Marks[i]
        mesh.position.set(wp.x, wp.y, wp.z);
        marks_box.push(mesh);
        scene.add(mesh);
      }

现在有了内容当时图片并没有任何功能,一下代码是实现功能的部分主要的效果是视图飞行移动的效果,主要是通过判断鼠标的位置和3D模型视图里的位置是否重合来判断是否发生事件,左键为移动视图,右键为跳转链接

      // 创建射线检测器
      let raycaster = new THREE.Raycaster();
      let mouse = new THREE.Vector2();
      // 添加鼠标事件监听器
      document.addEventListener('click', onDocumentMouseDown, false);

      // 鼠标事件处理函数
      function onDocumentMouseDown(event) {
        event.preventDefault();

        // 计算鼠标在canvas中的坐标
        mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
        mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

        // 把鼠标坐标转换为three.js中的坐标
        raycaster.setFromCamera(mouse, camera);
        // 计算射线和立方体的交点

        for (let i = 0; i < marks_box.length; i++) {
          let cube = marks_box[i]
          let intersects = raycaster.intersectObjects([cube]);

          // 处理点击事件
          if (intersects.length > 0) {

            const pos = new THREE.Vector3();
            cube.getWorldPosition(pos); //获取三维场景中某个对象世界坐标
            // 相机飞行到的位置和观察目标拉开一定的距离
            const pos2 = pos.clone().addScalar(30);//向量的x、y、z坐标分别在pos基础上增加30
            // 相机从当前位置camera.position飞行三维场景中某个世界坐标附近
            new TWEEN.Tween({
              // 相机开始坐标
              x: camera.position.x,
              y: camera.position.y,
              z: camera.position.z,
              // 相机开始指向的目标观察点
              tx: 0,
              ty: 0,
              tz: 0,
            })
                .to({
                  // 相机结束坐标
                  x: pos2.x,
                  y: pos2.y,
                  z: pos2.z,
                  // 相机结束指向的目标观察点
                  tx: pos.x,
                  ty: pos.y,
                  tz: pos.z,
                }, 500)
                .onUpdate(function (obj) {
                  // 动态改变相机位置
                  camera.position.set(obj.x, obj.y, obj.z);
                  // 动态计算相机视线
                  camera.lookAt(obj.tx, obj.ty, obj.tz);
                })
                .start().update();
          }
        }
      }

      document.addEventListener('mousedown', onDocumentLclick, false);

      function onDocumentLclick(event) {
        if(event.button == 2){
          event.preventDefault();

          // 计算鼠标在canvas中的坐标
          mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
          mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

          // 把鼠标坐标转换为three.js中的坐标
          raycaster.setFromCamera(mouse, camera);
          // 计算射线和立方体的交点

          for (let i = 0; i < marks_box.length; i++) {
            let cube = marks_box[i]
            let intersects = raycaster.intersectObjects([cube]);

            // 处理点击事件
            if (intersects.length > 0) {
              // 跳转到指定的 URL 地址
              location.assign(Marks[i].Url);
            }
          }
        }

      }

其中有一部分让图标和摄像头移动的动画在摄影机处单独绘制

      // Camera Control
      const control = new OrbitControls(camera, renderer.domElement);
      // edge鼠标指示功能兼容
      // control.mouseButtons = {
      //   LEFT: THREE.MOUSE.PAN,
      //   MIDDLE: THREE.MOUSE.ROTATE,
      // }
      control.addEventListener('change', () => {
        for (let i = 0; i < marks_box.length; i++) {
          marks_box[i].rotation.y = camera.rotation.y;
        }
        // ;
        renderer.render(scene, camera);
      });

;