美文网首页
Vue + Three.js导入obj模型并实现爆炸效果

Vue + Three.js导入obj模型并实现爆炸效果

作者: Vivian_0430 | 来源:发表于2022-08-31 13:52 被阅读0次

Three.js官方文档:https://threejs.org/manual/#zh/load-obj

注意点:obj文件需要放到public文件夹下的static文件夹中(路径写法错误会导致模型不显示,可以在network里查看文件是否被加载)
// 完整代码
<template>
  <div id="container"></div>
  <el-slider
    v-model="value"
    show-input
    :min="1"
    :max="100"
    style="
      width: 50%;
      position: absolute;
      bottom: 20%;
      left: 50%;
      transform: translateX(-50%);
    "
    @change="onChange"
  />
</template>

<script>
import * as THREE from "three";
import Stats from "stats-js";
import * as Dat from "dat-gui";
import TrackballControls from "three-trackballcontrols";
// import { OBJLoader } from "three-obj-mtl-loader";
// import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";

export default {
  name: "LoadObjMtl",
  data() {
    return {
      step: 0,
      value: 0,
    };
  },
  mounted() {
    this.renderer = "";
    this.camera = "";
    this.scene = "";
    this.light = "";
    this.gui = "";
    this.axesHelper = "";
    this.stats = "";
    this.trackballControls = "";
    this.clock = "";
    this.jeepCar = "";
    this.controls = "";
    this.objLoader = "";
    this.mtlLoader = "";
    this.gltfLoader = "";
    this.plane = "";

    // 执行
    this.execute();
    // 窗口大小变化
    window.onresize = this.onWindowResize;
  },
  methods: {
    initScene() {
      this.scene = new THREE.Scene();
    },
    initCamera() {
      this.camera = new THREE.PerspectiveCamera(
        45,
        window.innerWidth / window.innerHeight,
        0.1,
        5000
      );
      // 设置相机位置
      this.camera.position.x = -400;
      this.camera.position.y = 400;
      this.camera.position.z = 400;
      // 设置相机指向的位置 默认0,0,0
      this.camera.lookAt(this.scene.position);
    },
    initHelper() {
      //   this.axesHelper = new THREE.AxesHelper(100);
      //   this.scene.add(this.axesHelper);
    },
    initRender() {
      this.renderer = new THREE.WebGLRenderer({ antialias: true });
      this.renderer.setSize(window.innerWidth, window.innerHeight);
      // 告诉渲染器需要阴影效果
      // this.renderer.shadowMap.enabled = true;
      // this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      // 设置背景色
      this.renderer.setClearColor(new THREE.Color("rgb(61,61,61)"));
      document
        .querySelector("#container")
        .appendChild(this.renderer.domElement);
    },
    initStats() {
      this.stats = new Stats();
      document.body.appendChild(this.stats.dom);
    },
    getWorldCenterPosition(box, scalar = 0.5) {
      return new THREE.Vector3()
        .addVectors(box.max, box.min)
        .multiplyScalar(scalar);
    },
    explodeModel(model, scalar) {
      model.traverse(function (value) {
        // @ts-ignore
        if (!value.isMesh || !value.userData.originPosition) return;
        const distance = value.userData.worldDir
          .clone()
          .multiplyScalar(value.userData.worldDistance.length() * scalar);
        const offset = new THREE.Vector3().subVectors(
          value.userData.meshCenter,
          value.userData.originPosition
        );
        const center = value.userData.explodeCenter;
        const newPos = new THREE.Vector3()
          .copy(center)
          .add(distance)
          .sub(offset);
        const localPosition = value.parent?.worldToLocal(newPos.clone());
        localPosition && value.position.copy(localPosition);
      });
    },
    initExplodeModel(modelObject) {
      if (!modelObject) return;

      // 计算模型中心
      const explodeBox = new THREE.Box3();
      explodeBox.setFromObject(modelObject);
      const explodeCenter = this.getWorldCenterPosition(explodeBox);

      const meshBox = new THREE.Box3();

      const that = this;

      // 遍历整个模型,保存数据到userData上,以便爆炸函数使用
      modelObject.traverse(function (value) {
        if (
          value.isMark ||
          value.isMarkChild ||
          value.isLine ||
          value.isSprite
        ) {
          return;
        }
        if (value.isMesh) {
          meshBox.setFromObject(value);

          const meshCenter = that.getWorldCenterPosition(meshBox);
          // 爆炸方向
          value.userData.worldDir = new THREE.Vector3()
            .subVectors(meshCenter, explodeCenter)
            .normalize();
          // 爆炸距离 mesh中心点到爆炸中心点的距离
          value.userData.worldDistance = new THREE.Vector3().subVectors(
            meshCenter,
            explodeCenter
          );
          // 原始坐标
          value.userData.originPosition = value.getWorldPosition(
            new THREE.Vector3()
          );
          // mesh中心点
          value.userData.meshCenter = meshCenter.clone();
          value.userData.explodeCenter = explodeCenter.clone();
        }
      });
    },
    initModel() {
      // 实例化mtl loader
      // this.mtlLoader = new MTLLoader();
      const that = this;

      // 加载mtl
      //   this.mtlLoader.load("objs/jeep/jeepCar.mtl", function(materials) {
      //     materials.preload()
      //     that.objLoader.setMaterials(materials)
      //     // 加载obj
      //     that.objLoader.load("objs/jeep/jeepCar.obj", function(obj) {
      //       // 模型文件太大,缩小一下比例,方便显示
      //       obj.scale.set(0.1, 0.1, 0.1)
      //       // 设置可以投影
      //       obj.children.forEach(item => {
      //         item.castShadow = true
      //         item.receiveShadow = true
      //       })
      //       that.jeepCar = obj
      //       // 添加到场景
      //       that.scene.add(that.jeepCar)
      //     })
      //   })

      // 实例化obj loader
      this.objLoader = new OBJLoader();

      this.objLoader.load("static/f019d04068.obj", function (obj) {
        console.log(obj);
        obj.traverse((node) => {
          node.material = new THREE.MeshLambertMaterial({
            color: "#72757A",
            emissive: "#000000",
            shininess: 150
          });
        });
        obj.scale.set(1, 1, 1);
        obj.children[0].material.color.set(0xccd072);
        // obj.children[4].material.color.set(0xe1a07f);
        obj.children.forEach((item) => {
          item.castShadow = true;
          item.receiveShadow = true;
        });
        obj.castShadow = true;
        obj.receiveShadow = true;
        that.jeepCar = obj;
        // 添加到场景
        that.scene.add(that.jeepCar);
        that.initExplodeModel(that.jeepCar);
      });

      // 创建一个几何平面,作为地板,400,400
      const planeGeometry = new THREE.PlaneGeometry(400, 400);
      // 创建带颜色材质,更换为MeshLambertMaterial材质,去掉网格结构
      const planeMaterial = new THREE.MeshLambertMaterial({
        color: "rgb(110,110,110)",
      });
      this.plane = new THREE.Mesh(planeGeometry, planeMaterial);

      // 平面开启接收阴影效果
      this.plane.receiveShadow = true;

      // 设置平面角度和位置
      this.plane.rotation.x = -0.5 * Math.PI;
      this.plane.position.x = 0;
      this.plane.position.y = 0;
      this.plane.position.z = 0;
      // 添加平面
      //   this.scene.add(this.plane);

      //   this.gltfLoader = new GLTFLoader();
      //   this.gltfLoader.load("static/gltf/scene.gltf", function (gltf) {
      //     console.log(gltf);
      //     gltf.scene.scale.set(80, 80, 80);
      //     that.jeepCar = gltf.scene;
      //     // 添加到场景
      //     that.scene.add(gltf.scene);
      //     that.initExplodeModel(gltf.scene);
      //   });
    },
    onChange(val) {
      console.log(val);
      this.explodeModel(this.jeepCar, val);
    },
    initLight() {
      // 为场景所有物体添加基础光源
      const ambientLight = new THREE.AmbientLight(0x72757a, 2);
      this.scene.add(ambientLight);

      // 添加聚光灯光源
      const spotLight = new THREE.SpotLight(0xffffff, 2, 1000);
      spotLight.shadow.mapSize.set(2048, 2048);
      spotLight.position.set(-300, 0, -200);
      // 开启投影
      // spotLight.castShadow = true;
      this.scene.add(spotLight);
      const anotherSpotLight = new THREE.SpotLight(0xffffff, 2, 1000);
      anotherSpotLight.shadow.mapSize.set(2048, 2048);
      anotherSpotLight.position.set(300, 0, 300);
      this.scene.add(anotherSpotLight);

      const spotLightHelper = new THREE.SpotLightHelper(spotLight);
      this.scene.add(spotLightHelper);

      //   const light = new THREE.DirectionalLight(0xe8b73b, 2, 1000); // 光源颜色
      //   light.shadow.mapSize.set(2048, 2048);
      //   light.position.set(100, 100, 0);
      //   this.scene.add(light);

      //   const directionalLightHelper = new THREE.SpotLightHelper(light);
      //   this.scene.add(directionalLightHelper);
    },
    initGui() {
      // 为带贴图MTL的OBJ模型添加UI控制(xy坐标,xyz角度)
      this.controls = {
        positionX: -18,
        positionZ: -18,
        rotationX: 0,
        rotationY: 45,
        rotationZ: -15,
      };

      const gui = new Dat.GUI();
      // 设置允许操作范围
      gui.add(this.controls, "positionX", -200, 200);
      gui.add(this.controls, "positionZ", -200, 200);
      gui.add(this.controls, "rotationX", -360, 360);
      gui.add(this.controls, "rotationY", -360, 360);
      gui.add(this.controls, "rotationZ", -360, 360);
    },
    initControls() {
      this.trackballControls = new TrackballControls(
        this.camera,
        this.renderer.domElement
      );
      this.trackballControls.rotateSpeed = 1.0;
      this.trackballControls.zoomSpeed = 1;
      this.trackballControls.panSpeed = 1;
      this.trackballControls.noZoom = false;
      this.trackballControls.noPan = false;
      this.trackballControls.staticMoving = true;
      this.trackballControls.dynamicDampingFactor = 0.3;
      this.trackballControls.keys = [65, 83, 68];
    },
    initClock() {
      this.clock = new THREE.Clock();
    },
    render() {
      this.trackballControls.update(this.clock.getDelta());
      this.stats.update();

      // UI事件
      if (this.jeepCar) {
        this.jeepCar.position.x = this.controls.positionX;
        this.jeepCar.position.z = this.controls.positionZ;

        // 这里设置的角度,需要转换为弧度
        this.jeepCar.rotation.x = this.angle2Radian(this.controls.rotationX);
        this.jeepCar.rotation.y = this.angle2Radian(this.controls.rotationY);
        this.jeepCar.rotation.z = this.angle2Radian(this.controls.rotationZ);
      }

      // render using requestAnimationFrame
      requestAnimationFrame(this.render);
      this.renderer.render(this.scene, this.camera);
    },
    onWindowResize() {
      this.camera.aspect = window.innerWidth / window.innerHeight;
      this.camera.updateProjectionMatrix();
      this.renderer.setSize(window.innerWidth, window.innerHeight);
    },
    // 角度转弧度(弧度 = π / 180 * 角度)
    angle2Radian(angle) {
      return (Math.PI / 180) * angle;
    },
    execute() {
      // 初始化场景
      this.initScene();
      // 初始化摄像头
      this.initCamera();
      // 初始化三维坐标系
      this.initHelper();
      // 初始化辅助UI
      this.initGui();
      // 初始化帧数显示工具
      this.initStats();
      // 初始化时钟工具
      this.initClock();
      // 初始化模型
      this.initModel();
      // 初始化渲染器
      this.initRender();
      // 初始化光源
      this.initLight();
      // 初始化控制器
      this.initControls();
      // 执行渲染
      this.render();
    },
  },
};
</script>

相关文章

网友评论

      本文标题:Vue + Three.js导入obj模型并实现爆炸效果

      本文链接:https://www.haomeiwen.com/subject/pqcsnrtx.html