ThreeJs加载模型及优化体验

在网上看了一段时间threejs尝试加载gltf模型并为模型添加标签,点击高亮等效果。
文中不足之处请各位大佬斧正,感谢!!!

直接上代码

<template>
  <div id="appPage">
    <main>
      <el-tree
        ref="tree"
        :props="props"
        :data="treeArr"
        node-key="id"
        show-checkbox
        @check="checked"
        highlight-current
        default-expand-all
      >
      </el-tree>
      <div id="bimContainer" ref="bimContainer">
        <el-progress
          :text-inside="true"
          :stroke-width="30"
          :percentage="loadProgress"
          color="#7fbaf1"
          text-color="#333"
          stroke-linecap="square"
          v-if="isShowProgress"
          :format="formatProgress"
        ></el-progress>
      </div>
    </main>
  </div>
</template>

<script>
import * as THREE from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import * as BufferGeometryUtils from "three/addons/utils/BufferGeometryUtils.js";

import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass";

import {
  CSS3DObject,
  CSS3DRenderer,
} from "three/examples/jsm/renderers/CSS3DRenderer";
const models = [
  {
    name: "#1号楼",
    path: "/model/xpxm/xp-1.glb",
    position: [0, 0, 0],
    type: "glb",
  },
];
export default {
  data() {
    return {
      loadProgress: 0,
      isShowProgress: true,
      props: {
        label: "name",
        children: "children",
      },
      treeArr: [],
    };
  },
  watch: {
    loadProgress(value) {
      if (value >= 100) {
        setTimeout(() => {
          this.isShowProgress = false;
        }, 1500);
      }
    },
  },
  mounted() {
    this.init();
    this.loadModel();
    window.onresize = () => {
      this.renderer.setSize(
        this.$refs.bimContainer.clientWidth,
        this.$refs.bimContainer.clientHeight
      );
      this.camera.aspect =
        this.$refs.bimContainer.clientWidth /
        this.$refs.bimContainer.clientHeight;
      this.camera.updateProjectionMatrix();
    };
  },
  methods: {
    themeChange(e) {
      this.theme = e;
    },
    init() {
      // 1. 创建场景
      this.scene = new THREE.Scene();
      // 加载天空盒
      this.scene.background = new THREE.CubeTextureLoader().load([
        // 右 左 上 下 前 后
        "/model/skyTexture/1.jpg", //右
        "/model/skyTexture/2.jpg", //左
        "/model/skyTexture/3.jpg", //上
        "/model/skyTexture/4.jpg", //下
        "/model/skyTexture/5.jpg", //前
        "/model/skyTexture/6.jpg", //后
      ]);
      // 2. 添加透视投影相机
      this.camera = new THREE.PerspectiveCamera(
        50, // 视野角度
        this.$refs.bimContainer.clientWidth /
          this.$refs.bimContainer.clientHeight, // 画布宽高比
        0.1, // 近裁截面距相机距离
        10000 // 远裁截面距相机距离
      );
      this.camera.position.set(0, 20, 100);
      this.camera.lookAt(this.scene.position);
      // this.scene.add(new THREE.AxesHelper(100));
      // 3. 添加渲染器
      this.renderer = new THREE.WebGLRenderer({
        antialias: true, // 抗锯齿
      });
      this.renderer.setPixelRatio(window.devicePixelRatio);
      this.renderer.setSize(
        this.$refs.bimContainer.clientWidth,
        this.$refs.bimContainer.clientHeight
      );
      this.renderer.render(this.scene, this.camera);
      this.$refs.bimContainer.appendChild(this.renderer.domElement);
      this.renderer.setClearAlpha(1);
      this.renderer.outputEncoding = THREE.sRGBEncoding;
      // 添加光
      const ambient = new THREE.AmbientLight(0xffffff, 100);
      this.scene.add(ambient);
      // 4. 添加相机控件
      this.control = new OrbitControls(this.camera, this.renderer.domElement);
      this.control.addEventListener("change", () => {
        this.renderer.render(this.scene, this.camera);
        this.labelRenderer.render(this.scene, this.camera);
      });
    },
    // 模型居中
    autoCenter(group) {
      // 创建包围盒包裹需要居中的模型
      let box3 = new THREE.Box3();
      box3.expandByObject(group);
      let vector3 = new THREE.Vector3();
      box3.getCenter(vector3); // 返回包围盒的中心点并拷贝到vector上
      group.position.x = group.position.x - vector3.x;
      group.position.y = group.position.y - vector3.y;
      group.position.z = group.position.z - vector3.z;
      box3.makeEmpty();
    },
    // 加载模型
    loadModel() {
      const group = new THREE.Group();
      group.name = '项目效果展示';
      const manager = new THREE.LoadingManager();
      let gltfLoader = new GLTFLoader(manager);
      const dracoLoader = new DRACOLoader();
      dracoLoader.setDecoderPath("/draco/gltf/");
      gltfLoader.setDRACOLoader(dracoLoader);
      models.forEach((modelItem) => {
        gltfLoader.load(modelItem.path, (loadedModel) => {
          const mergedModel = this.merge(loadedModel.scene);
          mergedModel.name = modelItem.name;
          group.add(mergedModel);
          this.autoCenter(group);
        });
      });
      manager.onProgress = (url, itemsLoaded, itemsTotal) => {
        this.loadProgress = parseInt((itemsLoaded / itemsTotal) * 100);
        this.treeArr.push(group);
        this.setChecked();
        group.receiveShadow = true;
        this.scene.add(group);
        this.renderer.render(this.scene, this.camera);
        group.children.forEach((item) => {
          console.log(item);
          this.scene.add(this.addLabel(item.name, item.position));
        });
        this.outline();
      };
      manager.onError = (url) => {
        console.log("There was an error loading " + url);
      };
    },
    merge(object) {
      const geometries = [];
      const matArr = [];
      var meshs = new THREE.BufferGeometry();
      object.children.forEach((child) => {
        if (child.isMesh) {
          const geo = child.geometry.clone();
          // child.frustumCulled = false;
          // child.material.side = THREE.DoubleSide;
          // //模型阴影
          // child.castShadow = true;
          // child.receiveShadow = true;
          //模型自发光
          // child.material.emissive = child.material.color;
          // child.material.emissiveMap = child.material.map;
          child.material.alphaTest = 1;
          child.renderOrder = 1; // 设置一个较高的值确保透明物体在不透明物体之后渲染
          if (Array.isArray(child.material)) {
            child.material = child.material[0];
            child.material.transparent = false;

            // 设置透明度为0.5 (例如)
            child.material.opacity = 1;
          } else {
            child.material = new THREE.MeshPhongMaterial({
              color: 0xffffff,
              transparent: false,
              opacity: 1,
            });
          }
          matArr.push(child.material);
          geo.index = null;
          child.updateWorldMatrix(true, true);
          geo.applyMatrix4(child.matrixWorld);
          geometries.push(geo);
        }
      });
      const mergeGeometries = BufferGeometryUtils.mergeGeometries(
        geometries,
        true
      );
      const singleMergeMesh = new THREE.Mesh(mergeGeometries, matArr);
      return singleMergeMesh;
    },
    // 添加光源
    addLight() {
      this.Lights = [
        //平行光(DirectionalLight)
        // color 光的颜色。 缺省值为 0xffffff (白色)。intensity - (可选参数) 光照的强度。缺省值为1。
        { name: "AmbientLight", obj: new THREE.AmbientLight(0xffffff, 1.5) },
        {
          name: "PointLight",
          obj: new THREE.PointLight(0xffffff, 1),
          position: [6, 2, 8],
        },
        {
          name: "DirectionalLight_top",
          obj: new THREE.DirectionalLight(0xefefef, 1),
          position: [-100, 100, 100],
        },
        {
          name: "DirectionalLight_bottom",
          obj: new THREE.DirectionalLight(0x1b1b1b, 1.2),
          position: [100, -100, -100],
        },
        {
          name: "DirectionalLight_right1",
          obj: new THREE.DirectionalLight(0xffffff, 1),
          position: [10, 80, 65],
        },
        {
          name: "DirectionalLight_right2",
          obj: new THREE.DirectionalLight(0xffffff, 1.2),
          position: [10, 80, 65],
        },
      ];
      this.Lights.map((item) => {
        item.obj.name = item.name;
        item.position && item.obj.position.set(...item.position);
        item.Helper = new THREE.PointLightHelper(item.obj);
        item.castShadow = true;
        this.scene.add(item.obj);
      });
    },
    formatProgress(progress) {
      return `模型加载中 ${progress}%`;
    },
    checked(checkedItem) {
      let isChecked = this.$refs.tree.getNode(checkedItem).checked;
      checkedItem.visible = isChecked;
      this.renderer.render(this.scene, this.camera);
    },
    setChecked() {
      let checkedArr = [];
      this.treeArr.forEach((item) => {
        if (item.visible) {
          checkedArr.push(item.id);
          if (item.children)
            item.children.forEach((i) => {
              if (i.visible) checkedArr.push(i.id);
            });
        }
      });
      this.$refs.tree.setCheckedKeys(checkedArr);
    },
    // 发光外边缘
    outline() {
      this.outlineComposer = new EffectComposer(this.renderer); // 轮廓渲染器
      // 新建一个场景通道  为了覆盖到原理来的场景上
      this.outlineComposer.addPass(new RenderPass(this.scene, this.camera)); //物体发光通道
      this.outlinePass = new OutlinePass(
        new THREE.Vector2(window.innerWidth, window.innerHeight),
        this.scene,
        this.camera
      );
      this.outlinePass.edgeStrength = 2; // 边框的亮度
      this.outlinePass.edgeGlow = 1; // 光晕[0,1]
      this.outlinePass.edgeThickness = 2; // 边框宽度
      this.outlinePass.pulsePeriod = 3; // 呼吸闪烁的速度
      this.outlinePass.visibleEdgeColor.set(0x00ffff);
      this.outlinePass.hiddenEdgeColor.set(0xdfff14);
      this.outlineComposer.addPass(this.outlinePass);
      this.raycaster = new THREE.Raycaster();
      this.renderer.domElement.addEventListener("click", this.handlerOutline);
    },
    handlerOutline(event) {
      this.mouse = {};
      this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
      this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
      // 设置射线起点为鼠标位置,射线的方向为相机视角方向
      this.raycaster.setFromCamera(this.mouse, this.camera);
      // 计算射线相交
      const intersects = this.raycaster.intersectObjects(this.scene.children);
      console.log(intersects);
      if (intersects.length > 0) {
        // 选中物体
        const selectedObject = intersects[0].object;
        this.outlinePass.selectedObjects = [selectedObject];
        this.outlineComposer.render();
      }
    },
    // 给模型添加标签
    addLabel(labelName, vector) {
      const textDiv = document.createElement("div");
      textDiv.className = "labelName";
      textDiv.innerHTML = `<div class="modelTag">${labelName}</div>`;

      const textLabel = new CSS3DObject(textDiv);
      textLabel.position.set(vector.x, vector.y + 40, vector.z);
      textLabel.scale.set(0.1, 0.1, 0.1);
      this.scene.add(textLabel);

      this.labelRenderer = new CSS3DRenderer();
      this.labelRenderer.setSize(
        this.$refs.bimContainer.offsetWidth,
        this.$refs.bimContainer.offsetHeight
      );
      this.labelRenderer.domElement.style.position = "absolute";
      this.labelRenderer.domElement.style.top = 0;
      this.labelRenderer.domElement.style.left = 0;
      this.labelRenderer.domElement.style.pointerEvents = "none";
      this.$refs.bimContainer.appendChild(this.labelRenderer.domElement);
      this.labelRenderer.render(this.scene, this.camera);
    },
  },
};
</script>

<style  lang="less" scoped>
#appPage{
  width: 100%;
  height: 100%;
  background: var(--app-page-bgc) repeat;
  background-size: 100% 100%;
  overflow: hidden;
}
#appPage #bimContainer {
  width: 100%;
  height: 100%;
  position: relative;
}
#appPage .el-progress {
  width: 350px;
  height: 80px;
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}
#appPage main {
  position: relative;
}
#appPage.el-tree {
  width: 320px;
  background-color: rgba(0, 0, 0, 0.1);
  color: #fff;
  position: absolute;
  left: 80px;
  top: 20px;
  z-index: 999;
  border-radius: 8px;
}

.el-tree-node.is-current > .el-tree-node__content {
  background-color: rgba(48, 61, 73, 0.2) !important;
}

#appPage .el-tree-node__content {
  &:hover {
    background-color: transparent !important;
  }
}
.modelTag {
  color: #fff !important;
  font-size: 24px;
}
</style>
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值