threejs引入glb格式模型展示

这篇具有很好参考价值的文章主要介绍了threejs引入glb格式模型展示。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

本文介绍threejs引入glb格式模型展示
1.鼠标事件交互
2.局部放大效果
3.端口状态渲染
4.点击鼠标改变端口状态文章来源地址https://www.toymoban.com/news/detail-528177.html

<template>
  <!-- 三维画布 -->
  <div style="width:100%;height:100%;position:relative;">
    <div id="three_div" ref="draw" class="draw" />
    <div v-if="loadSuccess" class="loadingBox">
      <div class="progress">
        <div class="progress-bar progress-bar-danger progress-bar-striped active" :style="{width:progress}">
          <div class="progress-value">{{ progress }}</div>
        </div>
      </div>
    </div>
  </div>
</template>

js代码

<script>
  import * as THREE from 'three' // 三维
  import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js' // 控制器
  import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js' // 控制器
  import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js' // 控制器
  export default {
    props: {
      faceList: {
        type: Array,
        default: () => []
      },
      capacity: {
        type: [Number, String],
        default: 0
      }
    },
    data() {
      return {
        // 声明渲染器
        renderer: '',
        // 声明相机
        camera: '',
        // 声明场景
        scene: '',
        // 声明几何体
        geometry: '',
        // 声明材质
        material: '',
        // 声明网格
        mesh: '',
        // 声明相机控制器
        controls: '',
        // 画布大小
        clientWidth: '',
        clientHeight: '',
        // 模型组
        faceGroup: [],
        // 记住选中的端口
        selectedPort: null,
        progress: 0,
        loadSuccess: true
      }
    },
    computed: {
      portStatus() {
        let img = new THREE.TextureLoader().load('/res/glb/glbstatus/white.jpg')
        if (this.capacity === 96 || this.capacity === 24) {
          img = new THREE.TextureLoader().load('/res/glb/glbstatus/blue48.jpg')
        }
        return {
          IDLE: {
            img: img,
            name: this.$t('PORT_STATUS_IDLE'),
            typeName: 'IDLE'
          },
          PROCESS: {
            img: new THREE.TextureLoader().load('/res/glb/glbstatus/process.jpg'),
            name: this.$t('SERVICE_AVAILABLE'),
            typeName: 'PROCESS'
          },
          MAIN: {
            img: new THREE.TextureLoader().load('/res/glb/glbstatus/occupy.jpg'),
            name: this.$t('SERVICE_UNAVAILABLE'),
            typeName: 'MAIN'
          },
          BACKUP: {
            img: new THREE.TextureLoader().load('/res/glb/glbstatus/backup.jpg'),
            name: this.$t('PORT_STATUS_BACKUP'),
            typeName: 'BACKUP'
          },
          DAMAGE: {
            img: new THREE.TextureLoader().load('/res/glb/glbstatus/damage.jpg'),
            name: this.$t('PORT_STATUS_DAMAGE'),
            typeName: 'DAMAGE'
          },
          OCCUPY: {
            img: new THREE.TextureLoader().load('/res/glb/glbstatus/damage1.jpg'),
            name: this.$t('LINK_FAILURE'),
            typeName: 'OCCUPY'
          },
          CONN: {
            img: new THREE.TextureLoader().load('/res/glb/glbstatus/occupy.jpg'),
            name: this.$t('LINK_FAILURE'),
            typeName: 'CONN'
          }
        }
      }
    },
    watch: {
      faceList(val) {
        console.log(val)
      }
    },
    mounted() {
      this.init()
    },
    beforeDestroy() {
      window.removeEventListener('resize', this.changeSize)
      window.removeEventListener('click', this.portClick)
      window.removeEventListener('wheel', this.handleMouseWheel)
      this.removeObj(this.scene)
      this.renderer && this.renderer.dispose()
      this.renderer.forceContextLoss()
      this.renderer.domElement = null
      this.renderer.content = null
      this.renderer = null
      cancelAnimationFrame(this._animate)
      THREE.Cache.clear()
    },
    methods: {
      init() {
        // 初始化渲染器
        this.initRenderer()
        // 初始化场景
        this.initScene()
        // 初始化相机
        this.initCamera()
        // 引入模型
        this.initgltfLoader()
        // 初始化光源
        this.initLight()
        // 初始化动画
        this.animate()
        // 添加事件
        this.addmeth()
      },
      // 初始化渲染器
      initRenderer() {
        // 实例化渲染器
        this.renderer = new THREE.WebGLRenderer({
          antialias: true, // 是否开启抗锯齿
          alpha: true // 是否可以将背景色设置为透明
        })
        // 设置渲染区域尺寸
        this.renderer.setSize(
          this.$refs.draw.offsetWidth,
          this.$refs.draw.offsetHeight
        )
        // 告诉渲染器需要阴影效果
        this.renderer.shadowMap.enabled = true
        this.renderer.shadowMap.type = THREE.PCFSoftShadowMap
        // 设置背景色
        this.renderer.setClearColor(0x000000, 0) // 设置背景颜色
        this.$refs.draw.appendChild(this.renderer.domElement)
      },
      // 初始化场景
      initScene() {
        // 实例化场景
        this.scene = new THREE.Scene()
        // 红线是X轴,绿线是Y轴,蓝线是Z轴
        var axesHelper = new THREE.AxesHelper(0)
        this.scene.add(axesHelper)
      },
      // 初始化相机
      initCamera() {
        this.clientWidth = this.$refs.draw.clientWidth
        this.clientHeight = this.$refs.draw.clientHeight
        const k = this.clientWidth / this.clientHeight // 窗口宽高比
        // 参数:PerspectiveCamera
        // fov — 垂直视野角度(从底部到顶部,以度为单位。 默认值为50。)
        // aspect — 长宽比(一般为渲染器、画布长宽比,默认为1)
        // near — 近距离(默认值为0.1)
        // far — 远距离(默认为2000,必须大于近距离的值。)
        this.camera = new THREE.PerspectiveCamera(45, k, 0.1, 1000)
        this.camera.position.set(30, 30, 100)
        // 创建相机控制器
        this.controls = new OrbitControls(this.camera, this.renderer.domElement)
        this.controls.enableZoom = false
      },
      // 引入外部模型 gltf
      initgltfLoader() {
        const gltfLoader = new GLTFLoader()
        const dracoLoader = new DRACOLoader()
        dracoLoader.setDecoderPath('/res/glb/draco/gltf/')// 这个是加载draco算法,这样才能解析压缩后的gltf模型格式.
        gltfLoader.setDRACOLoader(dracoLoader)
        // 引入默认纹理
        gltfLoader.load('/res/glb/device3d/AFS' + this.capacity + '.glb', (gltf) => {
          const model = gltf.scene
          console.log(model)
          if (this.capacity === 24) {
            model.scale.set(0.3, 0.3, 0.3) // 缩放
            model.position.set(0, -5, 0)
          } else if (this.capacity === 96) {
            model.scale.set(0.7, 0.7, 0.7) // 缩放
            model.position.set(0, -30, 0)
          } else {
            model.scale.set(0.7, 0.7, 0.7) // 缩放
            model.position.set(0, -20, 0)
          }

          const portGroup = model.children && model.children[0].children.find(item => item.name === 'PORT_GROUP')
          if (!portGroup) return
          const portList = portGroup.children
          portList.forEach(item => {
            item.nameIndex = item.name.replace(/[^\d]/g, '')
          })
          portList.sort((a, b) => { return a.nameIndex - b.nameIndex })

          this.faceGroup[0] = portList.filter(item => item.name.includes('Port_A'))
          this.faceGroup[1] = portList.filter(item => item.name.includes('Port_B'))

          this.faceList.forEach((item, index) => {
            item.portList.forEach((port, k) => {
              this.faceGroup[index][k].portData = port
              this.faceGroup[index][k].portId = port.portInfo.portId
            })
          })
          setTimeout(() => {
            // 给端口重新赋纹理
            this.faceGroup.forEach(item => {
              item.forEach(port => {
                const texture = this.portStatus[port.portData.portInfo.status]
                port.children.forEach(mesh => {
                  mesh.material = new THREE.MeshPhongMaterial({ color: 0xFFFFFF })
                  mesh.material.map = texture.img
                  mesh.textureName = texture.typeName
                })
              })
            })
          }, 0)
          this.scene.add(model)
        }, (xhr) => {
          const percentage = Math.floor(xhr.loaded / xhr.total * 100)
          this.progress = percentage + '%'
          if (percentage >= 99) {
            setTimeout(() => {
              this.loadSuccess = false
            }, 2000)
          }
        })
      },
      // 添加光源
      initLight() {
        // 全局环境光
        const ambientLight = new THREE.AmbientLight('#ffffff', 0.1)
        this.scene.add(ambientLight)
        // 点光源
        const pointLight = new THREE.PointLight('#ffffff', 1)
        pointLight.position.set(100, 500, 500)
        this.camera.add(pointLight)

        this.scene.add(this.camera)
      },
      addmeth() {
        // 监听窗口尺寸变化
        window.addEventListener('resize', this.changeSize, false)
        window.addEventListener('click', this.portClick, false)
        window.addEventListener('wheel', this.handleMouseWheel, false)
      },
      handleMouseWheel(event) {
        // 设置相机缩放比数值越大缩放越明显
        const factor = 2
        // 从鼠标位置转化为webgl屏幕坐标位置
        const glScreenX = (event.clientX / this.controls.domElement.width) * 2 - 1
        const glScreenY = -(event.clientY / this.controls.domElement.height) * 2 + 1
        const vector = new THREE.Vector3(glScreenX, glScreenY, 0)
        // 从屏幕向量转为3d空间向量
        vector.unproject(this.controls.object)
        // 相机偏移量
        vector.sub(this.controls.object.position).setLength(factor)
        if (event.deltaY < 0) {
          this.controls.object.position.add(vector)
          this.controls.target.add(vector)
        } else {
          this.controls.object.position.sub(vector)
          this.controls.target.sub(vector)
        }
        this.controls.update()
      },
      // 运行动画
      animate() {
        this._animate = requestAnimationFrame(this.animate.bind(this)) // 循环调用函数
        // 刷新相机控制器
        this.controls.update()
        this.renderer.render(this.scene, this.camera)
      },
      // 点击端口
      portClick(event) {
        // 保持原事件
        event.preventDefault()
        this.getIntersects(event.layerX, event.layerY)
      },
      getIntersects(layerX, layerY) {
        // 建立射线
        const raycaster = new THREE.Raycaster()
        // // 建立一个空物体
        const mouseVector = new THREE.Vector3()
        const x = (layerX / this.clientWidth) * 2 - 1
        const y = -(layerY / this.clientHeight) * 2 + 1
        mouseVector.set(x, y, 1)
        raycaster.setFromCamera(mouseVector, this.camera)
        raycaster.params.Line.threshold = 0.01
        const intersections = raycaster.intersectObjects(this.scene.children, true)
        let selectedObject = null // 被选中的模型

        if (intersections.length > 0) {
          for (var i = 0; i < intersections.length; i++) {
            // 遍历线相交模型
            if (intersections[i].object instanceof THREE.Mesh) {
              // 取第一个(距离最近)的相交Mesh类型模型
              // 如果要排除地面等参照模型,也可在此处添加判断条件
              selectedObject = intersections[i].object
              break
            }
          }
        }
        if (selectedObject) {
          const selected = new THREE.TextureLoader().load('/res/glb/glbstatus/selected.jpg')
          if (selectedObject.parent.name.includes('Port_')) {
            if (this.selectedPort && this.selectedPort.parent.portData.connPortId !== selectedObject.parent.portData.connPortId) {
              this.faceGroup.forEach(item => {
                item.forEach(port => {
                  const texture = this.portStatus[port.portData.portInfo.status]
                  if (this.selectedPort.parent.portData.connPortId === port.portData.connPortId ||
                    this.selectedPort.parent.portData.connPortId === port.portData.portInfo.portId) {
                    port.children.forEach(mesh => {
                      mesh.material.map = texture.img
                      mesh.textureName = texture.typeName
                    })
                  }
                })
              })
            }
            selectedObject.parent.children.forEach(item => {
              if (item.textureName !== 'SELECTED') {
                this.curTexture = item.textureName
                item.material.map = selected
                item.textureName = 'SELECTED'
                this.selectedPort = selectedObject
                this.$emit('portClick', selectedObject.parent.portData)
              } else {
                item.material.map = this.portStatus[this.curTexture].img
                item.textureName = this.curTexture
                this.$emit('portClick', null)
              }
            })
            this.faceGroup.forEach(item => {
              const obj = item.find(port => port.portId === selectedObject.parent.portData.connPortId)
              if (obj) {
                obj.children.forEach(mesh => {
                  if (mesh.textureName !== 'SELECTED') {
                    mesh.material.map = selected
                    mesh.textureName = 'SELECTED'
                  } else {
                    mesh.material.map = this.portStatus[this.curTexture].img
                    mesh.textureName = this.curTexture
                  }
                })
              }
            })
          }
          // console.log(selectedObject)
        }
      },
      // 监听尺寸变化
      changeSize() {
        // 重置渲染器输出画布canvas尺寸
        this.renderer.setSize(
          this.$refs.draw.offsetWidth,
          this.$refs.draw.offsetHeight
        )
        this.clientWidth = this.$refs.draw.clientWidth
        this.clientHeight = this.$refs.draw.clientHeight
        const k = this.clientWidth / this.clientHeight // 窗口宽高比
        // 重置相机投影的相关参数
        this.camera.aspect = k
        // 如果相机的一些属性发生了变化,
        // 需要执行updateProjectionMatrix ()方法更新相机的投影矩阵
        this.camera.updateProjectionMatrix()
      },
      removeObj(obj) {
        let arr = obj.children.filter(x => x)
        arr.forEach(item => {
          if (item.children.length) {
            this.removeObj(item)
          } else {
            item.clear()
          }
        })
        obj.clear()
        arr = null
      }
    }
  }
</script>

css代码

<style lang="scss">
#three_div{
 width:100%;
 height: 100%;
}

//进度条
.loadingBox{
  position: absolute;
  top:0;
  left:0;
  right: 0;
  bottom: 0;
  background: #020721;
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1;
}
.progress {
    height: 17px;
    background: #262626;
    padding: 3px;
    overflow: visible;
    border-radius: 20px;
    border-top: 1px solid #000;
    border-bottom: 1px solid #7992a8;
    margin-top: 50px;
    width: 300px;

    .progress-bar {
        border-radius: 20px;
        position: relative;
        animation: animate-positive 2s;
        float: left;
        width: 0;
        height: 100%;
        font-size: 12px;
        line-height: 20px;
        color: #fff;
        text-align: center;
        background-color: #2962f9;
        -webkit-transition: width .6s ease;
        -o-transition: width .6s ease;
        transition: width .6s ease;

    }

    .active {
        animation: reverse stripes 0.40s linear infinite, animate-positive 2s;
    }

    .progress-value {
        display: none;
        padding: 3px 7px;
        font-size: 13px;
        color: #fff;
        border-radius: 4px;
        background: #191919;
        border: 1px solid #000;
        position: absolute;
        top: -40px;
        right: -10px;
    }

    .progress-value:after {
        content: "";
        border-top: 10px solid #191919;
        border-left: 10px solid transparent;
        border-right: 10px solid transparent;
        position: absolute;
        bottom: -6px;
        left: 26%;
    }
}

.progress-bar-striped {
    background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
    background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
    background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
    -webkit-background-size: 40px 40px;
    background-size: 40px 40px;
}

@-webkit-keyframes stripes {
    from {
        background-position: 40px 0
    }

    to {
        background-position: 0 0
    }
}

@-o-keyframes stripes {
    from {
        background-position: 40px 0
    }

    to {
        background-position: 0 0
    }
}

@keyframes stripes {
    from {
        background-position: 40px 0
    }

    to {
        background-position: 0 0
    }
}

@-webkit-keyframes animate-positive {
    0% {
        width: 0;
    }
}

@keyframes animate-positive {
    0% {
        width: 0;
    }
}

</style>

到了这里,关于threejs引入glb格式模型展示的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 如何使用 ThreeJs 以 glTF、FBX 和 OBJ 文件格式加载 3D 模型,使用 ThreeJS 加载和显示带有纹理的 3D 模型

    在本文中,我展示了如何使用 ThreeJS 在 Web 视图中加载 3D 模型。Three.js 是一个跨浏览器的 JavaScript 库和应用程序编程接口,用于使用 WebGL 在 Web 浏览器中创建和显示动画 3D 计算机图形。加载不完整的原因有很多,例如纹理和材质渲染不正确。 创建场景 渲染场景 动画立方体

    2023年04月08日
    浏览(56)
  • Vue 引入高德地图:实现地图展示与交互

    本文将介绍如何在Vue项目中引入高德地图,以及如何实现地图的展示和交互功能。我们将从安装依赖开始,然后配置高德地图的密钥和相关插件,最后演示如何在Vue组件中使用地图组件和实现基本的交互功能。通过本文的指导,您将能够轻松地在Vue项目中集成高德地图,实现

    2024年02月08日
    浏览(50)
  • 前端 img图片如何 展示 base64 格式(并且下载到本地)

    如题:最近在做项目发现页面上有些图片是动态获取的,也就是后台给我们返回图片的存放地址,一般都是放在服务器上的某个位置,我们直接拿到渲染一下就行了,(前提是不存在跨域问题), 但是由于项目特殊性,后台使用了Python 渲染出来的图片是svg格式的图片,并且

    2024年02月09日
    浏览(61)
  • 将图片转换成Base64格式存入数据库以及在前端页面展示

    这个示例接口假设已经有了一个数据库连接池,并且已经注入或初始化了数据源。这个接口的功能是读取指定路径的图片文件,将其转换为Base64编码字符串,然后将其存入数据库中。可以通过调用 saveImageToDB 方法来实现这个功能。调用该方法时需要传入要存储的图片文件的路

    2024年02月16日
    浏览(52)
  • uniapp vue3中使用threejs渲染3D模型

    前言: 因为公司需求, 需要在App中内嵌一个3D模型. 在市场上看了一下情况, 大部分都是vue2的, 并没有vue3的版本...现在vue3也不是个新东西了. 后期模型会放入App内. 下面写法并不支持App(已解决在App中渲染, 关注我可见), 支持h5 template: js: 上面写法并不优雅, 只是临时作为一个demo可

    2024年02月12日
    浏览(42)
  • VUE展示JSON格式数据【vue-json-viewer】

    一、安装组件 vue-json-viewer 二、在main.js中引入并使用 vue-json-viewer 三、在.vue文件中使用 vue-json-viewer 其中obj数据如果是JSON格式字符串,要先用 JSON.parse() 转成对象 未格式化效果 JSON格式化后

    2024年02月16日
    浏览(43)
  • JavaScript、Vue实现大数据大屏展示3D旋转动画效果

    最近在写一些数据大屏的时候客户需要做个3D旋转动效的效果,简单整理之后写了一个小demo做下记录,先看一下效果: 当点击next的时候,整个模块旋转切换到下个菜单,点击prev的时候也可以切换到上一个菜单效果。 首先我们先构建一个大体的dom结构,如下: 编写基本的css样

    2024年02月11日
    浏览(41)
  • vue项目引入video.js播放不同格式视频

    很多小伙伴使用原生video标签播放服务器返回的地址的视频,但是会发现video标签对视频的格式限制很多,限制MP4,WebM,Ogg三种格式的视频格式。但是对于需求不限制于此,就需要引入插件库,这里引入第三方插件库video.js来实现更多的需求。注意:video.js也限制视频格式,可在

    2024年02月11日
    浏览(54)
  • threeJs入门 js引入

    除了three.js核心库以外,在threejs文件包中examples/jsm目录下,你还可以看到各种不同功能的扩展库。 一般来说,你项目用到那个扩展库,就引入那个,用不到就不需要引入。 如果不是正式开发Web3D项目,只是学习threejs功能,完全没必要用webpack或vite搭建一个开发环境。 学习使

    2024年02月01日
    浏览(22)
  • 前端Vue页面中如何展示本地图片

    我们使用img标签展示图片,src属性设置成图片请求路径 \\\"http://localhost:8888/image/img.jpg\\\" 的格式,也就是会向后端发送这个请求获取图片。 但是图片是存放在本地的某个文件里,那如何才能找到呢? 这就需要对这个请求的路径进行映射,以找到真正的存放图片的地址。

    2024年02月04日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包