Vue + Three.js導(dǎo)入obj模型并實(shí)現(xiàn)爆炸效果

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

注意點(diǎn):obj文件需要放到public文件夾下的static文件夾中(路徑寫法錯(cuò)誤會(huì)導(dǎo)致模型不顯示褂痰,可以在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 = "";

    // 執(zhí)行
    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
      );
      // 設(shè)置相機(jī)位置
      this.camera.position.x = -400;
      this.camera.position.y = 400;
      this.camera.position.z = 400;
      // 設(shè)置相機(jī)指向的位置 默認(rèn)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;
      // 設(shè)置背景色
      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;

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

      const meshBox = new THREE.Box3();

      const that = this;

      // 遍歷整個(gè)模型跑慕,保存數(shù)據(jù)到userData上徒坡,以便爆炸函數(shù)使用
      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中心點(diǎn)到爆炸中心點(diǎn)的距離
          value.userData.worldDistance = new THREE.Vector3().subVectors(
            meshCenter,
            explodeCenter
          );
          // 原始坐標(biāo)
          value.userData.originPosition = value.getWorldPosition(
            new THREE.Vector3()
          );
          // mesh中心點(diǎn)
          value.userData.meshCenter = meshCenter.clone();
          value.userData.explodeCenter = explodeCenter.clone();
        }
      });
    },
    initModel() {
      // 實(shí)例化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)
      //       // 設(shè)置可以投影
      //       obj.children.forEach(item => {
      //         item.castShadow = true
      //         item.receiveShadow = true
      //       })
      //       that.jeepCar = obj
      //       // 添加到場(chǎng)景
      //       that.scene.add(that.jeepCar)
      //     })
      //   })

      // 實(shí)例化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;
        // 添加到場(chǎng)景
        that.scene.add(that.jeepCar);
        that.initExplodeModel(that.jeepCar);
      });

      // 創(chuàng)建一個(gè)幾何平面喇完,作為地板伦泥,400,400
      const planeGeometry = new THREE.PlaneGeometry(400, 400);
      // 創(chuàng)建帶顏色材質(zhì),更換為MeshLambertMaterial材質(zhì)锦溪,去掉網(wǎng)格結(jié)構(gòu)
      const planeMaterial = new THREE.MeshLambertMaterial({
        color: "rgb(110,110,110)",
      });
      this.plane = new THREE.Mesh(planeGeometry, planeMaterial);

      // 平面開啟接收陰影效果
      this.plane.receiveShadow = true;

      // 設(shè)置平面角度和位置
      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;
      //     // 添加到場(chǎng)景
      //     that.scene.add(gltf.scene);
      //     that.initExplodeModel(gltf.scene);
      //   });
    },
    onChange(val) {
      console.log(val);
      this.explodeModel(this.jeepCar, val);
    },
    initLight() {
      // 為場(chǎng)景所有物體添加基礎(chǔ)光源
      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坐標(biāo)不脯,xyz角度)
      this.controls = {
        positionX: -18,
        positionZ: -18,
        rotationX: 0,
        rotationY: 45,
        rotationZ: -15,
      };

      const gui = new Dat.GUI();
      // 設(shè)置允許操作范圍
      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;

        // 這里設(shè)置的角度,需要轉(zhuǎn)換為弧度
        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);
    },
    // 角度轉(zhuǎn)弧度(弧度 = π / 180 * 角度)
    angle2Radian(angle) {
      return (Math.PI / 180) * angle;
    },
    execute() {
      // 初始化場(chǎng)景
      this.initScene();
      // 初始化攝像頭
      this.initCamera();
      // 初始化三維坐標(biāo)系
      this.initHelper();
      // 初始化輔助UI
      this.initGui();
      // 初始化幀數(shù)顯示工具
      this.initStats();
      // 初始化時(shí)鐘工具
      this.initClock();
      // 初始化模型
      this.initModel();
      // 初始化渲染器
      this.initRender();
      // 初始化光源
      this.initLight();
      // 初始化控制器
      this.initControls();
      // 執(zhí)行渲染
      this.render();
    },
  },
};
</script>
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末刻诊,一起剝皮案震驚了整個(gè)濱河市防楷,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌则涯,老刑警劉巖复局,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件冲簿,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡亿昏,警方通過查閱死者的電腦和手機(jī)峦剔,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來角钩,“玉大人吝沫,你說我怎么就攤上這事〉堇瘢” “怎么了野舶?”我有些...
    開封第一講書人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宰衙。 經(jīng)常有香客問我平道,道長(zhǎng),這世上最難降的妖魔是什么供炼? 我笑而不...
    開封第一講書人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任一屋,我火速辦了婚禮,結(jié)果婚禮上袋哼,老公的妹妹穿的比我還像新娘冀墨。我一直安慰自己,他們只是感情好涛贯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開白布诽嘉。 她就那樣靜靜地躺著,像睡著了一般弟翘。 火紅的嫁衣襯著肌膚如雪虫腋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,816評(píng)論 1 290
  • 那天稀余,我揣著相機(jī)與錄音悦冀,去河邊找鬼。 笑死睛琳,一個(gè)胖子當(dāng)著我的面吹牛盒蟆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播师骗,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼历等,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了辟癌?” 一聲冷哼從身側(cè)響起寒屯,我...
    開封第一講書人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎愿待,沒想到半個(gè)月后浩螺,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體靴患,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年要出,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸳君。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡患蹂,死狀恐怖或颊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情传于,我是刑警寧澤囱挑,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站沼溜,受9級(jí)特大地震影響平挑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜系草,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一通熄、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧找都,春花似錦唇辨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至晓猛,卻和暖如春饿幅,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞍帝。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來泰國打工诫睬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人帕涌。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像续徽,于是被迫代替她去往敵國和親蚓曼。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容