three.js实现模型渲染及简单交互(源码+注释)

531 阅读1分钟

一、前言

  • 在three.js中,展示的一切内容都是在canvas中绘制的,所以点击事件点击到物体上是无法获取点击对象的,要获取点击的对象要使用RayCaster,用于在三维空间中进行鼠标拾取,原理是:相机与鼠标所在的设备坐标之间的连线经过哪些物体。

二 、代码注释详解

<template>
  <div id="box"></div>
</template>

<script>
// 引入three.js
import * as THREE from 'three';
// 引入轨道控制器扩展库OrbitControls.js
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// 引入dat.gui.js的一个类GUI
import { GUI } from 'three/addons/libs/lil-gui.module.min.js'
export default {
  name: 'threeBox',
  mounted() {
    // 创建3D场景对象Scene
    const scene = new THREE.Scene();
    //创建几何对象Geometry
    const geometry = new THREE.BoxGeometry(20, 20, 20);
    //创建材质对象Material
    const material = new THREE.MeshBasicMaterial({
      color: 0xff0000, //设置材质颜色
      transparent: true,//开启透明
      opacity: 0.5,//设置透明度
    });
    // 改变颜色
    material.color.setRGB(0, 112, 217)
    //网格模型对象Mesh
    const mesh = new THREE.Mesh(geometry, material);
    // 创建矩阵
    // let mesh
    // for (let i = 0; i < 10; i++) {
    //   for (let j = 0; j < 10; j++) {
    //     mesh = new THREE.Mesh(geometry, material); //网格模型对象Mesh
    //     // 在XOZ平面上分布
    //     mesh.position.set(i * 200, 0, j * 200);
    //     scene.add(mesh); //网格模型添加到场景中  
    //   }
    // }
    //设置网格模型在三维空间中的位置坐标,默认是坐标原点
    mesh.position.set(0, 10, 0);
    // 网格模型mesh添加到三维场景scene
    scene.add(mesh);
    // 定义相机输出画布的尺寸
    const width = window.screen.availWidth; //宽度
    const height = window.screen.availHeight; //高度
    //(视场角度, width / height:Canvas画布宽高比, 1:近裁截面, 3000:远裁截面)
    const camera = new THREE.PerspectiveCamera(30, width / height, 1, 3000);
    // 相机位置xyz坐标:200, 200, 200
    camera.position.set(200, 200, 200);
    //指向mesh对应的位置
    camera.lookAt(mesh.position);
    // 创建渲染器对象
    const renderer = new THREE.WebGLRenderer();
    //设置three.js渲染区域的尺寸(像素px)
    renderer.setSize(width, height);
    //执行渲染操作
    renderer.render(scene, camera);
    document.getElementById('box').appendChild(renderer.domElement);
    // 旋转相关
    // 设置相机控件轨道控制器OrbitControls
    const controls = new OrbitControls(camera, renderer.domElement);
    // 如果OrbitControls改变了相机参数,重新调用渲染器渲染三维场景
    controls.addEventListener('change', function () {
      renderer.render(scene, camera); //执行渲染操作
    });//监听鼠标、键盘事件
    // 动画相关
    renderer.setSize(width, height);
    // renderer.render(scene, camera); //执行渲染操作
    document.body.appendChild(renderer.domElement);
    // 渲染函数
    //创建一个对象,对象属性的值可以被GUI库创建的交互界面改变
    const obj = {
      x: 30,
      y: 60,
      z: 300,
      color: 0x00ffff,
      scale: 0,
      bool: true,
      specular: 0x00ffff
    };
    function render() {
      if (!obj.bool) {
        return
      } else {
        renderer.render(scene, camera); //执行渲染操作
        mesh.rotateY(0.01);//每次绕y轴旋转0.01弧度
        requestAnimationFrame(render);//请求再次执行渲染函数render,渲染下一帧
      }
    }
    renderer.antialias = true
    // 获取你屏幕对应的设备像素比.devicePixelRatio告诉threejs,以免渲染模糊问题
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setClearColor(0x444444, 1); //设置背景颜色

    // 实例化一个gui对象
    const gui = new GUI();
    // gui界面上增加交互界面,改变obj对应属性
    gui.add(mesh.position, 'x', 0, 180);
    gui.add(mesh.position, 'y', 0, 180);
    gui.add(mesh.position, 'z', 0, 180);
    // .addColor()生成颜色值改变的交互界面
    gui.addColor(obj, 'color').onChange(function (value) {
      console.log(value);
      mesh.material.color.set(value);
    });
    // 下拉菜单
    gui.add(obj, 'scale', [10, 50, 80]).name('y坐标').onChange(function (value) {
      mesh.position.y = value;
    });
    render();
    // 改变的obj属性数据类型是布尔值,交互界面是单选框
    gui.add(obj, 'bool').name('是否旋转').onChange(function (value) {
      render();
    });;
    // 创建材质子菜单
    const matFolder = gui.addFolder('材质');
    matFolder.close();
    let flag = true
    renderer.domElement.addEventListener('click', function (event) {
      flag = !flag
      // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px
      const px = event.offsetX;
      const py = event.offsetY;
      //屏幕坐标px、py转WebGL标准设备坐标x、y
      //width、height表示canvas画布宽高度
      const x = (px / width) * 2 - 1;
      const y = -(py / height) * 2 + 1;
      //创建一个射线投射器`Raycaster`
      const raycaster = new THREE.Raycaster();
      //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray
      // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中
      raycaster.setFromCamera(new THREE.Vector2(x, y), camera);
      //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算
      // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素
      const intersects = raycaster.intersectObjects([mesh]);
      console.log("射线器返回的对象", intersects);
      // intersects.length大于0说明,说明选中了模型
      if (intersects.length > 0) {
        if (flag) {
          mesh.material.color.set(65531);
        } else {
          mesh.material.color.set(61235);
        }
      }
    })
  }
}
</script>
<style lang="scss" scoped>
.box {
  width: 100vw;
  height: 100vh;
}
</style>