threejs+vue3+vite加载模型及点击事件

437 阅读2分钟

样例

image.png

点击之后的效果

image.png

直接上代码

<script setup>
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
// 导入轨道模型控制器
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";

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

// 创建相机
const camera = new THREE.PerspectiveCamera(
  100,
  window.innerWidth / window.innerHeight,
  0.1,
  1000
);
camera.position.set(15, 10, -5);

// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

// 添加控制器
let control = new OrbitControls(camera, renderer.domElement);

//添加直线光
let light1 = new THREE.DirectionalLight(0xffffff, 1);
light1.position.set(0, 50, 50);
let light2 = new THREE.DirectionalLight(0xffffff, 1);
light2.position.set(0, 50, -50);
let light3 = new THREE.DirectionalLight(0xffffff, 1);
light3.position.set(50, 50, 50);
let light4 = new THREE.DirectionalLight(0xffffff, 1);
light4.position.set(-50, -10, 0);
let light5 = new THREE.DirectionalLight(0xffffff, 1);
light5.position.set(0, 0, 50);
let light6 = new THREE.DirectionalLight(0xffffff, 1);
light6.position.set(0, 0, -50);
let light7 = new THREE.DirectionalLight(0xffffff, 1);
light7.position.set(50, 0, 0);
let light8 = new THREE.DirectionalLight(0xffffff, 1);
light8.position.set(-50, 0, 0);
scene.add(light1, light2, light3, light4, light5, light6, light7, light8);

// 创建天空盒
// 相应面对应相应图片
const imgUrl = [
  "bak5/right.jpg",
  "bak5/left.jpg",
  "bak5/top.jpg",
  "bak5/down.jpg",
  "bak5/back.jpg",
  "bak5/front.jpg",
];
// 调用getTexturesFromAtlasFile() 给每个材质加上相应的图片
const textures = getTexturesFromAtlasFile(imgUrl, 6);
const materials = [];

for (let i = 0; i < 6; i++) {
  // 创造六个面的材质
  materials.push(new THREE.MeshBasicMaterial({ map: textures[i] }));
}
//创造包围盒
const skyBox = new THREE.Mesh(
  new THREE.BoxGeometry(1024, 1024, 1024),
  materials
);
// skyBox.position.set(0, 0, 0);
skyBox.geometry.scale(1, 1, -1);
scene.add(skyBox);
// 六个面添加图片
function getTexturesFromAtlasFile(atlasImgUrl, tilesNum) {
  const textures = [];

  for (let i = 0; i < tilesNum; i++) {
    textures[i] = new THREE.Texture();
  }

  for (let i = 0; i < textures.length; i++) {
    const imageObj = new Image();
    imageObj.src = atlasImgUrl[i];
    imageObj.onload = () => {
      let context = "";
      // let tileWidth = imageObj.height;
      // let tileWidth = 5000;

      const canvas = document.createElement("canvas");
      // const canvas: HTMLCanvasElement = this.canvasRef.nativeElement;  // 得到canvas 元素
      context = canvas.getContext("2d");
      const canvasHeight = 720;
      canvas.height = canvasHeight;
      canvas.width = canvasHeight;
      // context.drawImage( imageObj, canvasHeight * i, 0, canvasHeight, canvasHeight, 0, 0, canvasHeight, canvasHeight );
      context.drawImage(imageObj, 0, 0, canvasHeight, canvasHeight);
      textures[i].image = canvas;
      textures[i].needsUpdate = true;
    };
  }
  return textures;
}

// 加载模型
const loader = new GLTFLoader();

loader.load(
  "/model/scene.gltf",
  function (gltf) {
    scene.add(gltf.scene);
  },
  undefined,
  function (error) {
    console.error(error);
  }
);

// 添加点击事件(官方)
let intersects = []; //几何体合集
const pointer = new THREE.Vector2();
document.addEventListener("click", meshOnClick);
let raycaster = new THREE.Raycaster();
function meshOnClick(event) {
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = -(event.clientY / window.innerHeight) * 2 + 1;
  raycaster.setFromCamera(pointer, camera);
  //geometrys为需要监听的Mesh合集,可以通过这个集合来过滤掉不需要监听的元素例如地面天空
  //true为不拾取子对象
  intersects = raycaster.intersectObjects(scene.children, true);
  //被射线穿过的几何体为一个集合,越排在前面说明其位置离端点越近,所以直接取[0]
  if (intersects.length > 0) {
    //alert(intersects[0].object.name);
    console.log(intersects[0].object);
    if (intersects[0].object.name == "nature_Nature_0") {
      intersects[0].object.material.color.set(0xff0000);
    }
    if (intersects[0].object.name == "water_Water_0") {
      intersects[0].object.material.color.set(0x0000ff);
    }
  } else {
    //若没有几何体被监听到,可以做一些取消操作
  }
}

// 渲染函数
function animate() {
  requestAnimationFrame(animate);

  // 渲染
  renderer.render(scene, camera);
}

animate();
</script>

<template></template>

<style>
* {
  margin: 0;
  padding: 0;
}

canvas {
  display: block;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
}
</style>

代码是在创建了vue项目之后直接在App里写的,但是不建议这么写,一般App里都是加载主代码,可以另外创建一个文件写。

模型是在 sketchfab.com/3d-models?f… 里免费下载的,大家有需要可以自己去下载