Vue3 + Three.js-前端-E先生的博客
Java
MySQL
大数据
Python
前端
黑科技
大语言模型
    首页 >> 互联网 >> 前端

Vue3 + Three.js

[导读]:Vue3 + Three.js...

动力:市场需要量大  Ai动效

一、threejs安装

通过npm或者yarn安装threejs库

npm install three or yarn add three

导入整个 three.js核心库

import * as THREE from 'three';

二、场景创建

<template>
    <div ref="container"></div>
</template>

const container = ref(null)
// 创建场景
const scene = new THREE.Scene();
// 创建相机
camera = new THREE.OrthographicCamera( 
    window.innerWidth / -10.5,
    window.innerWidth / 10.5,
    window.innerHeight / 10.5,
    window.innerHeight / - 10.5,
    0.1,
    1000
);// 这边设置的是OrthographicCamera解决模型近大远小的问题
// 相机位置
camera.position.z = 200; // 根据模型大小调整
camera.position.y = 0;
camera.position.x = 0;
camera.lookAt(0, 0, 0)
// 创建渲染器
renderer = new THREE.WebGLRenderer({ alpha: true }); // 设置背景为透明
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x000000, 0);
container.value.appendChild(renderer.domElement);
// 如果需要添加环境贴图(添加背景环境)可以加上以下代码
// import { RGBELoader} from "three/examples/jsm/loaders/RGBELoader"
// let rgbeLoader = new RGBELoader();
// const rgbUrl = "xxxxx"; // 环境贴图hdr文件路径
// rgbeLoader.load(rgbUrl, envMap => {
//     // 设置球形贴图
//     envMap.mapping = THREE.EquirectangularReflectionMapping;
//     // 设置环境贴图
//     scene.background = envMap;
//     scene.environment = envMap;
// })
// 渲染场景
const animate = () => {
  requestAnimationFrame(animate);
  renderer.render(scene, camera);
};
animate();
// 监听窗口缩放
window.addEventListener("resize", () => {
    renderer.setSize( window.innerWidth, window.innerHeight );
    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();
});

三、模型引入没有模型怎么办?可以去sketchfab.com 这里下载你想要的模型。下面有地址

import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
const modelContainer = new THREE.Object3D();
const loader = new GLTFLoader();
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath('./draco/')
// 加载 glb 格式的 3D 模型
loader.setDRACOLoader(dracoLoader)
const modelPath = url // url是你模型的路径
loader.load(
    modelPath, 
    (gltf) => {
      // 加载成功后的回调函数
      model = gltf.scene;
      const box = new THREE.Box3().setFromObject(model);
      // 获取包围盒中心点
      const center = box.getCenter(new THREE.Vector3());
      model.position.sub(center); // 将模型位置移到原点处
      // 将模型添加到父对象中
      modelContainer.add(model);
      scene.add(modelContainer);
      
    },
    (xhr) => {
      // 加载过程中的回调函数
      // emit("handleProgress",Math.floor((xhr.loaded / xhr.total) * 100))
      // 如果有写模型加载进度条的要求,可以在这里判断
    },
    (error) => {
      // 加载失败的回调函数
      console.error("Failed to load model", error);
    }
  );

四、交互事件

const STATE = {
    NONE: -1,
    MOVE: 0,
    ZOOM_OR_PAN: 1,
    POSITION_MOVE: 1,
}
const mouseInfo = ref({startX: 0, startY: 0, isDown: false, startPointerDistance: 0, state: STATE.NONE}) // 触控参数存储
const rotateSpeed = 0.010; // 拖动速度 可自行调整
const twoFirst = ref(true)
const lastPoint = ref({
    touchStartX1: 0,
    touchStartY1: 0,
    touchStartX2: 0,
    touchStartY2: 0,
})

旋转、缩放、平移方法

// 开始触碰
renderer.domElement.addEventListener('touchstart', (event) => {
  handleTouchStart(event)
});
const handleTouchStart = (event) => {
  mouseInfo.value.isDown = true
  // 双指
  const touch0 = event.touches[0]
  const touch1 = event.touches[1]
  // 单点触控
  if (event.touches.length === 1) {
      mouseInfo.value.startX = touch0.pageX
      mouseInfo.value.startY = touch0.pageY
      mouseInfo.value.state = STATE.MOVE 
  } else if (event.touches.length === 2) {
      // 双指,计算两指距离,一般是平移或者缩放
      const dx = (touch0.pageX - touch1.pageX)
      const dy = (touch0.pageY - touch1.pageY)
    
      mouseInfo.value.startPointerDistance = Math.sqrt(dx * dx + dy * dy)
      mouseInfo.value.startX = (touch0.pageX + touch1.pageX) / 2
      mouseInfo.value.startY = (touch0.pageY + touch1.pageY) / 2
      mouseInfo.value.state = STATE.ZOOM_OR_PAN
  }
}
// 监听缩放\旋转操作
renderer.domElement.addEventListener('touchmove', (event) => {
  handleTouchMove(event)
});
const handleTouchMove = (event) => {
  if (!mouseInfo.value.isDown) {
      return
  }
  switch (mouseInfo.value.state) {
      case STATE.MOVE:
          if (event.touches.length === 1) {
              handleRotate(event)
          } else if (event.touches.length === 2) {
              // 兼容处理,如果是移动过程中出现双指时,需要取消单指,然后再重新计算
              renderer.domElement.removeEventListener("touchmove",handleTouchMove, false)
              renderer.domElement.removeEventListener("touchend",handleTouchEnd, false)
              handleTouchStart(event)
          }
          break
      case STATE.ZOOM_OR_PAN:
          if (event.touches.length === 1) {
          } else if (event.touches.length === 2) {
              handleZoomOrPan(event)
          }
          break
      default:
          break
  }
}
// 模型旋转
const handleRotate = (event) => {
  const x = event.touches[0].pageX
  const y = event.touches[0].pageY
  
  const {startX, startY} = mouseInfo.value
  const deltaX = x - startX;
  const deltaY = y - startY;
  modelContainer.rotation.y += deltaX * rotateSpeed;
  modelContainer.rotation.x += deltaY * rotateSpeed;
  mouseInfo.value.startX = x
  mouseInfo.value.startY = y
}
// 模型缩放
const handleZoomOrPan = (event) => {
  let {touchStartX1,touchStartY1, touchStartX2, touchStartY2} = lastPoint.value;
  let initialScale = modelContainer.scale.x;
  const touch0 = event.touches[0]
  const touch1 = event.touches[1]
  const dx = (touch0.pageX - touch1.pageX)
  const dy = (touch0.pageY - touch1.pageY)
  const distance = Math.sqrt(dx * dx + dy * dy)
  let obj = {
    touchStartX1: touch0.pageX,
    touchStartY1: touch0.pageY,
    touchStartX2: touch1.pageX,
    touchStartY2: touch1.pageY,
  }
  if (twoFirst.value) {
      twoFirst.value = false;
      lastPoint.value = {...obj}
  } else {
      let deltaScale = initialScale * (distance / mouseInfo.value.startPointerDistance);
      // 限制缩放距离
      if (deltaScale < -2) {
          deltaScale = -2
      } else if (deltaScale > 2) {
          deltaScale = 2
      }
      mouseInfo.value.startPointerDistance = distance;
      modelContainer.scale.set(deltaScale, deltaScale, deltaScale);
      const avgX = (touch0.pageX + touch1.pageX) / 2;
      const avgY = (touch0.pageY + touch1.pageY) / 2;
      const deltaX = avgX - (touchStartX1 + touchStartX2) / 2;
      const deltaY = avgY - (touchStartY1 + touchStartY2) / 2;
      modelContainer.position.x += deltaX * 0.3;
      modelContainer.position.y -= deltaY * 0.3; 
      
      lastPoint.value = {
        touchStartX1: touch0.pageX,
        touchStartY1: touch0.pageY,
        touchStartX2: touch1.pageX,
        touchStartY2: touch1.pageY,
      }
  }
}
// 监听触控结束
renderer.domElement.addEventListener('touchend', (event) => {
  handleTouchEnd()
}); 
const handleTouchEnd = () => {
  mouseInfo.value.isDown = false
  mouseInfo.value.state = STATE.NONE
  twoFirst.value = true;
  lastPoint.value.touchStartX1 = 0;
  lastPoint.value.touchStartY1 = 0;
  lastPoint.value.touchStartX2 = 0;
  lastPoint.value.touchStartY2 = 0;
  // 取消移动事件监听
  renderer.domElement.removeEventListener("touchmove",handleTouchMove, false)
  renderer.domElement.removeEventListener("touchstart",handleTouchStart, false)
}

自动旋转、重置位置方法

// 开启自动旋转
const rotateTimer = ref(null)
const setTimer = () => {
    rotateTimer.value = setInterval(() => {
        modelContainer.rotation.y -= 0.1
    }, 100)
}
// 重置模型位置
const ResetPosition = () => {
  modelContainer.position.z = 0;
  modelContainer.position.y = 0;
  modelContainer.position.x = 0;
  modelContainer.scale.z = 1;
  modelContainer.scale.y = 1;
  modelContainer.scale.x = 1;
  modelContainer.rotation.z = 0;
  modelContainer.rotation.y = 0;
  modelContainer.rotation.x = 0;
}

五、优化调整

打光

// 环境光 (这是一定要的)
const ambientLight = new THREE.AmbientLight(0xffffff, 2);
scene.add(ambientLight);
// 白色平行光(模型更明亮)
const directionalLight = new THREE.DirectionalLight( 0xffffff, 2 ); // 参数自行调整
directionalLight.position.x = 1;
directionalLight.position.y = 1;
directionalLight.position.z = 80;
directionalLight.target = modelContainer; // target指向模型
scene.add( directionalLight );
// *创建点光源(这个看情况给)
var pointLight = new THREE.PointLight(0xffffff, 500); // 设置点光源的颜色和强度
pointLight.position.set(0, 0, 100); // 设置点光源的位置
scene.add(pointLight);
「清晰度」// 在创建渲染器时,添加antiallias:true抗锯齿,让模型看起来更加平滑
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
// 设置画布分辨率 提高画质
renderer.setPixelRatio(window.devicePixelRatio);
「ios上safari网页缩放问题」在safari上打开缩放模型会和网页缩放冲突,添加以下方法可以解决。// 在main.ts中添加
window.onload = function() {
  var lastTouchEnd = 0;
  document.addEventListener('touchstart', function(event) {
      if (event.touches.length > 1) {
          event.preventDefault();
      }
  });
  document.addEventListener('touchend', function(event) {
      var now = (new Date()).getTime();
      if (now - lastTouchEnd <= 300) {
          event.preventDefault();
      }
      lastTouchEnd = now;
  }, false);
  document.addEventListener('gesturestart', function(event) {
      event.preventDefault();
  });
  document.addEventListener('dblclick', function (event) {
      event.preventDefault();
})
}

六、参考

模型下载:https://sketchfab.com/ or https://market.pmnd.rs/

环境贴图hdr下载:https://hdrmaps.com/threejs

文档参考:http://www.yanhuangxueyuan.com/threejs/docs/index.html#manual/zh/introduction/Creating-a-scene

1726912600505.jpg


本文来自E先生的博客,如若转载,请注明出处:https://javajz.cn

留言区

联系人:
手   机:
内   容:
验证码:

历史留言

欢迎加Easy的QQ