Three.js针对.gltf类型建模文件封装记录

记录Three.js代码组件封装片段 , 支持定制旋转位置大小配置

three.js官方连接:Three.js中文网

3D模型文件下载地址:3D模型可视化编辑器

完整效果图片

封装文件位置 : utils文件夹下 

依赖安装:

"dependencies": {
    "three": "^0.165.0",
    "three-obj-mtl-loader": "^1.0.3",
    "three-orbit-controls": "^82.1.0",
}

封装文件:

<!--THREEJS组件-->
<template>
  <div class="d3-container">
    <div
      class="camera-icon"
      v-show="
        this.camera &&
        (Math.round(this.camera.position.x) !== 1 ||
          Math.round(this.camera.position.z) !== 20) &&
        ishome
      "
    >
      <el-tooltip effect="light" content="复位" placement="top">
        <i class="el-icon-refresh" @click="onResetModelCamera"></i>
      </el-tooltip>
    </div>
    
    <div
      element-loading-text="模型加载中"
      element-loading-spinner="el-icon-loading"
      element-loading-background="rgba(0, 0, 0, 0)"
      id="d3Container"
      v-loading="loading"
      ref="mainContent"
    ></div>
  </div>
</template>
<script>
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader";
import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader";

export default {
  name: "ThreePage",
  props: {
    /*模型资源地址*/
    ossPath: {
      type: String,
      default() {
        return "/scene.gltf";
      },
    },
    /*是否主页*/
    ishome: {
      type: Boolean,
      default() {
        return true;
      },
    },
    // 是否缩放
    isEnableZoom: {
      type: Boolean,
      default() {
        return true;
      },
    },
    // 是否拖拽
    isEnablePan: {
      type: Boolean,
      default() {
        return true;
      },
    },
    /*d3物件的初始位置*/
    d3Position: {
      type: Array,
      default() {
        return [0, -6, 0];
      },
    },
    /*d3物件的初始缩放*/
    d3Scale: {
      type: Array,
      default() {
        return [2, 2, 2];
      },
    },
    /*d3相机的位置*/
    cameraPosition: {
      type: Array,
      default() {
        return [1, 1, 20];
      },
    },
    /*d3相机的观测点*/
    cameraLookAt: {
      type: Array,
      default() {
        return [1, 10, 0];
      },
    },
    /*是否开启模型动画*/
    startModelAnimaion: {
      type: Boolean,
      default() {
        return true;
      },
    },
    /*模型动画运转速度*/
    mixerTimeScale: {
      type: Number,
      default() {
        return 1;
      },
    },
    /*文件类型*/
    suffix: {
      type: String,
      default() {
        return "gltf";
      },
    },
    AutoFresh: {
      type: Boolean,
      default() {
        return true;
      },
    },
    /*是否开启自动旋转*/
    autoAnimate: {
      type: Boolean,
      default() {
        return true;
      },
    },
    /*当前模型的颜色*/
    currentColor: {
      type: String,
      default() {
        return "";
      },
    },
    /*配准后的颜色*/
    matchedColor: {
      type: String,
      default() {
        return "";
      },
    },
    /*配准后的地址*/
    matchedOssPatch: {
      type: String,
      default() {
        return "";
      },
    },
    showMatchWatch: {
      type: Boolean,
      default() {
        return false;
      },
    },
  },
  data() {
    return {
      OricameraUp: null,
      OricamerPosition: null,
      OricameraQuaternion: null,
      OricontrolTarget: null,
      composer: null,
      renderPass: null,
      outlinePass: null,
      loading: false,
      publicPath: process.env.BASE_URL,
      mesh: null,
      camera: null,
      scene: null,
      originX: 10,
      originY: 20,
      originZ: 10,
      renderer: null,
      controls: null,
      fileLoaderMap: {
        glb: new GLTFLoader(),
        fbx: new FBXLoader(),
        gltf: new GLTFLoader(),
        obj: new OBJLoader(),
      },
      clock: new THREE.Clock(),
      mixer: null,
    };
  },
  mounted() {
    this.init();
  },
  watch: {
    //监听地址变化时需要更新地址,防止多次点击同一个渲染多次;
    ossPath(val, oldVal) {
      if (val != oldVal) {
        this.init();
      }
    },
    //监测是否更新整个场景
    AutoFresh(val, oldVal) {
      if (val) {
        this.init();
      } else {
        //自我清理
        this.destroyed();
      }
    },
    //监测是否展示配准,更新场景,该属性的变化只负责更新场景,具体业务交给按钮的最终展现结果,按钮勾中就展示配准,没有勾中就不展示配准,属性没变就是原来的状态。
    showMatchWatch(val, oldVal) {
      this.init();
    },
    //由于上传标签时,CAD会绕过。
    currentColor(val, oldVal) {
      if (val != oldVal) {
        this.init();
      }
    },
  },
  //组件被销毁时,干掉所有3D资源;
  methods: {
    // 重置相机位置
    onResetModelCamera() {
      // // 设置相机位置
      this.camera.position.set(1, 1, 20);
      // 初始化位置
      this.scene.up.copy(this.OricameraUp);
      this.scene.position.copy(this.OricamerPosition);
      this.scene.quaternion.copy(this.OricameraQuaternion);
      this.controls.target.copy(this.OricontrolTarget);
    },
    destroyed() {
      this.clear();
    },
    // 初始化
    init() {
      /*利用vue单项数据流的特性做最后的守卫,在最底层监听是否需要展示配准图,只影响该组件的内部数据而不影响外部的matchedOssPatch*/
      if (!this.showMatchWatch) {
        this.matchedOssPatch = "";
      }
      this.createScene(); // 创建场景
      this.loadLoader(); // 加载P模型
      this.createLight(); // 创建光源
      this.createCamera(); // 创建相机
      this.createRender(); // 创建渲染器
      this.createControls(); // 创建控件对象
      this.render(); // 渲染
      // 获取初始模型位置
      this.OricameraUp = this.scene.up.clone();
      this.OricamerPosition = this.scene.position.clone();
      this.OricameraQuaternion = this.scene.quaternion.clone();
      this.OricontrolTarget = this.controls.target.clone();
      // console.log( this.OricontrolTarget);
      // console.log(
      //   this.OricameraUp,
      //   this.OricamerPosition,
      //   this.OricameraQuaternion,
      //   this.OricontrolTarget
      // );
    },
    //清除当前所有场景
    clear() {
      this.mesh = null;
      this.camera = null;
      this.scene = null;
      this.renderer = null;
      this.controls = null;
      this.clock = null;
      this.mixer = null;
      cancelAnimationFrame(this.animationId);
      console.log("我要清除啦");
    },
    // 创建场景
    createScene() {
      this.loading = true;
      this.scene = new THREE.Scene();
      //开启 底部网格
      var grid = new THREE.GridHelper(24, 24, 0xff0000, 0x444444);
      grid.material.opacity = 0.4;
      grid.material.transparent = true;
      grid.rotation.x = Math.PI / 2.0;
      grid.rotation.z = Math.PI / 4;
      // makeRotationAxis(axis, angle)
      // this.scene.add()
      this.scene = new THREE.Scene();
      const texture = new THREE.TextureLoader().load(
        require("@/assets/images/view-4.png")
      );
      texture.mapping = THREE.EquirectangularReflectionMapping;
      texture.colorSpace = THREE.SRGBColorSpace;
      this.scene.background = texture;
      this.scene.environment = texture;
    },
    // 加载PLY模型
    loadLoader() {
      const THIS = this;
      let fileType = THIS.ossPath.substring(THIS.ossPath.lastIndexOf(".") + 1);
      const loader = this.fileLoaderMap[fileType];
      return new Promise((resolve, reject) => {
        loader.load(
          THIS.ossPath,
          (result) => {
            //加载不同类型的文件
            switch (fileType) {
              case "glb":
                this.mesh = result.scene;
                break;
              case "fbx":
                this.mesh = result;
                break;
              case "gltf":
                this.mesh = result.scene;

                break;
              case "obj":
                this.mesh = result;
                break;
              default:
                break;
            }
            this.mesh.traverse(() => {
            });

            // 新建一个AnimationMixer
            this.mixer = new THREE.AnimationMixer(this.mesh);
            this.mixer.clipAction(result.animations[0]).play();
            this.mixer.timeScale = this.mixerTimeScale; //默认1,可以调节播放速度
            this.loading = false; //关闭载入中效果

            // 设置模型大小
            this.mesh.scale.set(
              this.d3Scale[0],
              this.d3Scale[1],
              this.d3Scale[2]
            );
            // 设置模型位置
            this.mesh.position.set(
              this.d3Position[0],
              this.d3Position[1],
              this.d3Position[2]
            ); //设置3d物品的位置
            // 将模型添加到场景中去
            this.scene.add(this.mesh);
            resolve(true);
          },
          (xhr) => {
            const percent = Math.floor((xhr.loaded / xhr.total) * 100);
          },
          (err) => {
            console.log(err);
            reject(new Error());
          }
        );
      });
      //如果有配准结果,加载配准结果,配准结果未ply格式;
    },
    // 创建光源
    createLight() {
      // 环境光
      let pointColor = " #000";
      const ambientLight = new THREE.AmbientLight(pointColor, 0.35); // 创建环境光
      this.scene.add(ambientLight); // 将环境光添加到场景

      const spotLight = new THREE.SpotLight(0xffffff); // 创建聚光灯
      spotLight.position.set(1, 1, 1);
      // spotLight.castShadow = true //平行光开启阴影
      // spotLight.receiveShadow = true
      this.scene.add(spotLight);
    },

    // 创建相机
    createCamera() {
      const element = this.$refs.mainContent;
      const width = element.clientWidth; // 窗口宽度
      const height = element.clientHeight; //
      this.cWidth = width;
      this.cHeight = height;
      const k = width / height; // 窗口宽高比
      this.aspect = k;
      // PerspectiveCamera( fov, aspect, near, far )
      this.camera = new THREE.PerspectiveCamera(35, k, 1, 20000);
      this.camera.position.set(
        this.cameraPosition[0],
        this.cameraPosition[1],
        this.cameraPosition[2]
      ); // 设置相机位置
      this.camera.up.set(0, 1, 0);
      this.camera.lookAt(
        new THREE.Vector3(
          this.cameraLookAt[0],
          this.cameraLookAt[1],
          this.cameraLookAt[2]
        )
      ); // 设置相机方向
      this.scene.add(this.camera);
    },
    // 创建渲染器
    createRender() {
      const element = this.$refs.mainContent;
      this.renderer = new THREE.WebGLRenderer({
        antialias: true,
        alpha: true,
        preserveDrawingBuffer: true,
      });
      this.renderer.setSize(element.clientWidth, element.clientHeight); // 设置渲染区域尺寸
      this.renderer.shadowMap.enabled = true; // 显示阴影
      this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
      this.scene.background = null; //设置背景颜色,null为透明
      // this.renderer.setClearColor(new THREE.Color(0xeeeeee)) // 设置背景颜色
      // this.renderer.setClearColor(new THREE.Color(0x111111)) // 设置背景颜色
      this.renderer.toneMappingExposure = 1.5; //曝光
      this.renderer.toneMapping = THREE.ACESFilmicToneMapping; //色调映射
      element.innerHTML = "";
      element.appendChild(this.renderer.domElement);
    },
    render() {
      //TODO:更新模型动画
      if (this.mixer && this.startModelAnimaion) {
        const delta = this.clock.getDelta();
        this.mixer.update(delta);
      }

      this.animationId = requestAnimationFrame(this.render); //旋转动画;
      this.renderer.render(this.scene, this.camera);
      this.controls.update();
      // 点击事件影像处理 有问题
      // if (this.composer) {
      //   this.composer.render()
      // }
    },
    // 创建控件对象
    createControls() {
      this.controls = new OrbitControls(this.camera, this.renderer.domElement);
      // const newMesh = this.mesh.clone();
      // console.log(this.mesh.position);
      // console.log(this.mesh);
      // this.controls.minDistance = 1 //设置相机距离原点的最远距离
      // this.controls.maxDistance = 1000 //设置相机距离原点的最远距离
      // this.controls.maxPolarAngle = Math.PI / 1 //设置可旋转的范围
      this.controls.enableZoom = true; //是否可以缩放
      this.controls.enablePan = true; //是否可拖拽
      this.controls.enableDamping = true; // 开启阻尼效果
      this.controls.dampingFactor = 1; // 阻尼系数
      this.controls.autoRotate = this.autoAnimate; // 自动旋转
      this.controls.autoRotateSpeed = 5; // 自动旋转速度
      this.controls.minPolarAngle = 0; // 设置最小旋转角度
      this.controls.maxPolarAngle = Math.PI; // 设置最大旋转角度
    },
    onWindowResize() {
      this.camera.aspect = this.aspect;
      this.camera.updateProjectionMatrix();
    },
  },
  beforeDestroy() {
    this.destroyed();
  },
};
</script>

<style scoped lang="less">
.d3-container {
  width: 100%;
  height: 100%;
  .camera-icon {
    position: absolute;
    top: 20px;
    left: 50%;
    cursor: pointer;
  }
}
#d3Container {
  width: 100%;
  height: 100%;
}
.el-icon-refresh {
  font-size: 28px;
  color: #ccc;
  z-index: 99;
}
</style>

使用方法

<template> 
 <d3
            :cameraPosition="[1, 3, 20]"
            :d3Position="[0, 0, 0]"
            :d3Scale="[.85, .85, .85]"
            :isEnableZoom="true"
            :isEnableRotate="true"
            :autoAnimate="true"
            ossPath="HN.gltf"
            class="three-canvas"
          ></d3>
</template>
<script>
import d3 from "../../utils/index.vue";
</script>

  • 6
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值