three.js初体验

189 阅读3分钟

20230722_131108.gif

three.js项目,学习地址

官网连接:https://threejs.org/

简介

three.js是JavaScript编写的WebGL第三方库。提供了非常多的3D显示功能

什么是threejs,很简单,你将它理解成three + js就可以了。three表示3D的意思,js表示javascript的意思。那么合起来,three.js就是使用javascript来写3D程序的意思。Javascript是运行在网页端的脚本语言,那么毫无疑问Three.js也是运行在浏览器上的。

项目中使用到CSS2DObject,TWEEN结合镜头运动,模型动画,

初始化项目

npm i three@0.124.0 需要加上版本号,官网引入某些控制器时最新引入方法 import { OrbitControls } from 'three/addons/controls/OrbitControls.js'; 旧版本引入方法 import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; 实际上不同版本的three库有差别

1690002211242.png

控件引入

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; //鼠标控制器
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'; //模型解压缩
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; //模型加载器

import { CSS2DRenderer, CSS2DObject } from 'three/examples/jsm/renderers/CSS2DRenderer' // 引入包

import Stats from 'three/examples/jsm/libs/stats.module.js'

let scene = null; //场景(在vue3中scene与mesh不能在data中定义,必须全局定义)

const TWEEN = require('@tweenjs/tween.js') //vue 中定义一个TWEEN的常量

three.js控制器

createOrbitControls(){
      this.mouseControls = new OrbitControls(
          this.camera,
          this.renderer.domElement
      ); //创建控件对象

      this.mouseControls.target = new THREE.Vector3(0, 0, 0)
      this.mouseControls.enablePan = true; //右键平移拖拽
      this.mouseControls.enableZoom = true; //鼠标缩放
      // this.mouseControls.minDistance = 1600; //相机距离原点的距离范围
      this.mouseControls.maxDistance = 20000;
      this.mouseControls.enableDamping = true; //滑动阻尼
      this.mouseControls.dampingFactor = 0.1; //(默认.25)
      this.mouseControls.maxPolarAngle = (Math.PI / 4) * 3; //y旋转角度范围
      this.mouseControls.minPolarAngle = Math.PI / 4;
    },

three.js环境光

createLight() {
      let lightColor = new THREE.Color('#ffffff');
      let ambient = new THREE.AmbientLight(lightColor); //环境光
      ambient.name = "环境光";
      scene.add(ambient);


      let directionalLight1 = new THREE.DirectionalLight(lightColor);
      directionalLight1.position.set(0, 0, 500);
      scene.add(directionalLight1); //平行光源添加到场景中
    },

模型加载

注意这边用的是.glb的模型动画。.fbx模型的动画加载方式会有出入

loadModel() {
      let that = this
      //利用Promise加载模型
      let lm = new Promise((resolve, reject) => {
        let loader = new GLTFLoader();
        const dracoLoader = new DRACOLoader();
        dracoLoader.setDecoderPath( './draco/' )  // 注意路径,确保public下面有draco文件夹
        loader.setDRACOLoader( dracoLoader );
        loader.load('./model/moxing10.glb', (gltf) => {

          resolve(gltf); //返回加载成功的模型
          reject("model加载失败!");
          this.isLoading = false; //关闭加载框
        });
      });
      //加载成功
      lm.then((res) => {
         // 将模型放在页面中央
        that.modelCenter(res.scene)
        // 模型中自带的动画
      this.mixer = new THREE.AnimationMixer(res.scene)
      for (var i = 0; i < res.animations.length; i++) {
        const clip = res.animations[i] //将第1帧动画设置为动画剪辑对象
        const action = this.mixer.clipAction(clip) //使用动画剪辑对象创建AnimationAction对象
        this.mixer.timeScale = 0.5 //默认1,可以调节播放速度
        action.setDuration(2).play() //设置单此循环的持续时间(setDuration也可以调节播放速度)并开始动画
      }

	// 模型放大会造成卡顿
        // res.scene.scale.set(modePar[3], modePar[4], modePar[5]);
        scene.add(res.scene);
        let len = 0
        setTimeout(()=>{
        // 模型中的有些模型名称需要显示,可以根据名称判断,这里只显示前面10个模型名称
          res.scene.traverse((child) => {
            if(len <= 10){
              len += 1
                that.createLableObj(child.name,child.position,child)
            }
          })
        },3000)

      });
      //加载失败
      lm.catch((err) => {
        console.log(err)
      });
    },

three.js渲染

repeatRender() {

      const delta = this.clock.getDelta();
      //请求动画帧,屏幕每刷新一次调用一次,绑定屏幕刷新频率
      this.anId = requestAnimationFrame(this.repeatRender);
      // 动画
       if (this.mixer) {
        this.mixer.update(delta)
      }
      this.renderer.render(scene, this.camera); //将场景和相机进行渲染
      // label标题
      if(this.labelRenderer){
        this.labelRenderer.render(scene, this.camera);
      }

    },

初始化渲染器

initScene(){
      scene = new THREE.Scene(); //新建场景
      let dom = document.getElementById("container");
      document.body.clientWidth || document.documentElement.clientWidth;
      let width = document.body.clientWidth || document.documentElement.clientWidth; //场景宽度
      let height = document.body.clientHeight || document.documentElement.clientHeight; //场景高度
      let k = width / height; //场景宽高比

      this.mouse = new THREE.Vector2()

      let cameraPar = [65, 0.01, 20000, -500, 2300, 4300]
      // 相机
      this.camera = new THREE.PerspectiveCamera(
          cameraPar[0],
          k,
          cameraPar[1],
          cameraPar[2]
      ); //透视相机

      // 渲染器
      this.renderer = new THREE.WebGLRenderer({
        logarithmicDepthBuffer: true,
        antialias: true, //抗锯齿
        alpha: true,
      });

      this.renderer.logarithmicDepthBuffer = true;
      this.renderer.setSize(width, height); //设置渲染区域尺寸

      document.getElementById("container").appendChild(this.renderer.domElement); //将画布添加到container中
      this.clock = new THREE.Clock();//用于更新轨道控制器

      // 控制器
      this.createOrbitControls();
      // 光
      this.createLight();
      // 模型加载
      this.loadModel();
      // 渲染
      this.repeatRender();

      document.addEventListener('dblclick', this.onMouseMove2, false);


      // 模型标题的CSS2DRenderer初始化
      this.labelRenderer = new CSS2DRenderer()
      this.labelRenderer.setSize(window.innerWidth, window.innerHeight)
      this.labelRenderer.domElement.style.position = 'absolute'
      this.labelRenderer.domElement.style.top = '0px';
      document.getElementById("container").appendChild(this.labelRenderer.domElement)
    },

创建label标题

createLableObj(text,positions,clickedObject) {
        let laberDiv = document.createElement('div');//创建div容器
        laberDiv.className = 'laber_name';
        laberDiv.textContent = text;
        laberDiv.style.height = '20px'
      laberDiv.style.background = 'red'
      laberDiv.style.color = '#ffffff'
        let pointLabel = new CSS2DObject(laberDiv);
        
      clickedObject.add(pointLabel)
      this.mouseControls = new OrbitControls(
          this.camera,
          this.labelRenderer.domElement
      ); //创建控件对象
      this.labelRenderer.render(scene, this.camera)
    },

双击模型事件

onMouseMove2(e){
		const raycaster = new THREE.Raycaster();
		
		const mouse = new THREE.Vector2();
		// 计算鼠标或触摸点的位置
		mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
		mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
		
		// 更新射线   注意——> this.camera 是相机   定义到data里的
		raycaster.setFromCamera(mouse, this.camera);
		// 计算与所有对象的交点
		const intersects = raycaster.intersectObjects(scene.children, true);
		console.log(intersects)
		let target1
		let obj
		if (intersects.length > 0) {
                // 获取到点击的模型
		  const clickedObject = intersects[0].object;
		  console.log(clickedObject.position)
		  target1 = clickedObject.position
		  obj = clickedObject
		  
		  
		  let boxMaxY = new THREE.Box3().setFromObject(intersects[0].object).max.y
		  
		  let distance = boxMaxY + 10
		  let angel = Math.PI / 5
                   // 运动方向
		  let position = {
			  x: intersects[0].object.position.x + Math.cos(angel) * distance,
			  y: intersects[0].object.position.y,
			  z: intersects[0].object.position.z + Math.sin(angel) * distance
		  }

		  let tween = new TWEEN.Tween(this.camera.position).to(position, 3000)
		  // let tween1 = new TWEEN.Tween(this.mouseControls.target).to(intersects[0].object.position, 3000)

		  this.mouseControls.enabled = false;
                  
		  tween.start()
		  // tween1.start()
		}
	},

总结

诸君共勉

2023年7月22日 纸上谈赖总觉浅,绝知此事要躬行