美文网首页前端开发那些事儿
ThreeJS渲染一个.obj三维模型文件(Vue)

ThreeJS渲染一个.obj三维模型文件(Vue)

作者: 萍璠as田丹丹 | 来源:发表于2020-07-17 13:03 被阅读0次

    先来看一个效果:


    image.png

    最近有个项目需要实现三维模型的web端渲染,以前虽然也做过类似的项目,单是两个项目一个是java Application,一个是安卓结合,两个我都只参与到的建模环节,所以知道三维模型文件的大概结构,要想在web端实现渲染,首先要做的就是读取这些模型文件,对里面的点、面、法线、材质进行逐行解析。

    各种对比后,发现了ThreeJS。它不仅可以解析obj模型文件,还可以解析大部分市场上有的模型格式文件。
    npm 安装后,在node_modules/three/examples/jsm/loaders/目录下可以看到它支持的模型格式。


    image.png
    image.png
    image.png

    PS:demo中使用了最流行vue语法。

    1.首先要有个dom容器

    <div id="canvas_div"></div>
    

    2.引用

    import * as THREE from 'three'
    import Stats from 'three/examples/jsm/libs/stats.module.js'
    import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'
    import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader.js'
    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
    

    3.数据存储

    data () {
        return {
          msg: 'This is webGL',
          renderer: null,
          camera: null,
          scene: null,
          light: null,
          stats: null,
          controls: null,
          canvasDiv: {
            element: null,
            wigth: '',
            height: ''
          },
          mesh: null,
          objChildren: [],
          mousePosition: {
            mouseX: 0,
            mouseY: 0
          },
          raycaster: null,
          mouse: null,
          selectObj: null,
          timer: null,
          animationRenId: null,
          animationAniId: null
        }
    

    4.方法

    容器
    /* *
         * 初始化容器
         * */
        initThree () {
          let _el = document.getElementById('canvas_div')
          this.canvasDiv.element = _el
          this.canvasDiv.wigth = _el.clientWidth
          this.canvasDiv.height = _el.clientHeight
        }
    
    渲染器
    initRenderer () {
          this.renderer = new THREE.WebGLRenderer()
          // this.renderer.setPixelRatio(window.devicePixelRatio) // 设置像素比
          this.renderer.setSize(this.canvasDiv.wigth, this.canvasDiv.height)
          this.renderer.setClearColor(0xCCCCCC, 1.0)
          this.canvasDiv.element.appendChild(this.renderer.domElement)
    
        }
    
    相机
    /* *
         * 初始化相机
         * */
        initCamera () {
    
          this.camera = new THREE.PerspectiveCamera(75, this.canvasDiv.wigth / this.canvasDiv.height, 0.5, 1000)
    
          // 设置相机距离原点坐标的位置
          this.camera.position.x = 200
          this.camera.position.y = 200
          this.camera.position.z = 200
    
          // this.camera.up.x = 0
          // this.camera.up.y = 0
          // this.camera.up.z = 1
          // this.camera.lookAt({
          //   x: 0,
          //   y: 0,
          //   z: 0
          // })
        }
    
    场景
     /* *
         * 初始化场景
         * */
        initScene () {
          this.scene = new THREE.Scene()
        }
    
    加载模型
    /* *
         * 加载模型
         * */
        initModel () {
          // 辅助工具:一个轴对象,以一种简单的方式可视化三个轴。
          // X轴为红色。 Y轴为绿色。 Z轴为蓝色。
          var helper = new THREE.AxesHelper(1000)
          this.scene.add(helper)
    
          let that = this
    
          var objLoader = new OBJLoader()
          var mtlLoader = new MTLLoader()
          mtlLoader.setPath('./static/webgl/')
          // 加载mtl文件
          mtlLoader.load('rate.mtl', function (material) {
            // 预加载
            material.preload()
    
            // 设置当前加载的纹理
            objLoader.setMaterials(material)
            objLoader.setPath('./static/webgl/')
            objLoader.load('rate.obj', function (object) {
              if (object.children) {
                var meshes = object.children
                meshes.forEach(element => {
                  element.material.color.setHex('0xfafafa')
                })
                // 获取模型的某个部位
                var obj2 = object.children[2]
    
                // 设置模型某部位的样式
                obj2.material.color.setHex('0xff0000')
                obj2.material.opacity = 0.6
                obj2.material.transparent = true
                // obj2.material.depthTest = false
                that.objChildren.push(obj2)
    
                // 将模型缩放并添加到场景当中
                object.scale.set(1, 1, 1)
                that.scene.add(object)
              }
            }, that.onProgress, that.onError)
          })
        }
    
    模型加载进程
    /*
         * 模型加载进程
         */
        onProgress (xhr) {
          if (xhr.lengthComputable) {
            var percentComplete = xhr.loaded / xhr.total * 100
            console.log(Math.round(percentComplete, 2) + '% downloaded')
          }
        }
    
    灯光
    initLight () {
          const ambientLight = new THREE.AmbientLight(0xCCCCCC, 0.4) // 环境光
          this.scene.add(ambientLight)
    
          this.light = new THREE.PointLight(0xffffff, 1) // 点光源
          this.light.position.set(50, 200, 100)
          // this.light.position.multiplyScalar(1.3) // 标量
          // this.light.castShadow = true // 告诉平行光需要开启阴影投射
          // this.light.shadow.mapSize.width = 500
          // this.light.shadow.mapSize.height = 500
    
          // var d = 300
    
          // this.light.shadow.camera.left = -d
          // this.light.shadow.camera.right = d
          // this.light.shadow.camera.top = d
          // this.light.shadow.camera.bottom = -d
    
          // this.light.shadow.camera.far = 1000
    
          this.scene.add(this.light)
        }
    
    性能检测插件
    /* *
         * 初始化性能检测插件
         * */
        initStats () {
          this.stats = new Stats()
          this.stats.dom.style.position = 'absolute'
          this.stats.dom.style.left = '0px'
          this.stats.dom.style.top = '0px'
          this.canvasDiv.element.appendChild(this.stats.dom)
        }
    
    用户交互插件
    /* *
         * 用户交互插件 鼠标左键按住旋转,右键按住平移,滚轮缩放
         * */
        initControls () {
          this.controls = new OrbitControls(this.camera, this.renderer.domElement) // , this.renderer.domElement
    
          // 如果使用animate方法时,将此函数删除
          // this.controls.addEventListener('change', this.render)
          // 使动画循环使用时阻尼或自转 意思是否有惯性
          this.controls.enableDamping = true
          // 动态阻尼系数 就是鼠标拖拽旋转灵敏度
          // controls.dampingFactor = 0.25;
          // 是否可以缩放
          this.controls.enableZoom = true
          // 是否自动旋转
          this.controls.autoRotate = false
          // 设置相机距离原点的最远距离
          this.controls.minDistance = 1
          // 设置相机距离原点的最远距离
          this.controls.maxDistance = 1000
          // 是否开启右键拖拽
          this.controls.enablePan = true
        }
    
    渲染
    /* *
          * 渲染
         * */
        render () {
          this.renderer.clear()
          this.renderer.render(this.scene, this.camera)
          requestAnimationFrame(this.render)
        }
    
    鼠标事件
    /*
         * 鼠标跟随事件
         */
        onDocumentMouseMove (event) {
          event.preventDefault()
    
          if (this.timer) {
            clearTimeout(this.timer)
          }
          this.timer = setTimeout(() => {
            // this.mousePosition.mouseX = (event.clientX - this.canvasDiv.wigth) / 2
            // this.mousePosition.mouseY = (event.clientY - this.canvasDiv.height) / 2
            // 光标的位置
    
            this.mouse = new THREE.Vector2()
            this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1
            this.mouse.y = -(event.clientY / window.innerHeight) * 2 + 1
    
            // 获取焦点
            var raycaster = new THREE.Raycaster()
            if (this.camera) {
              raycaster.setFromCamera(this.mouse, this.camera)
            }
            // console.log(this.scene.children[3])
            if (this.scene && this.scene.children && this.scene.children.length > 0) {
              var vPicker = this.scene.children[3] !== undefined ? this.scene.children[3]['children'] : []
              var intersects = raycaster.intersectObjects(vPicker)
              // console.log(this.scene.children[3])
              // console.log(this.scene.children[3]['children'])
              // console.log(intersects)
              if (intersects.length > 0) {
                var res = intersects.filter(function (res) {
                  return res && res.object
                })[0]
                if (res && res.object) {
                  this.selectObj = res.object
                  // 暂存原有材质颜色
                  // var _color = res.object.material.color.getHex()
                  // this.selectObj.currentHex = parseInt('0x' + _color)
                  // console.log(this.selectObj.currentHex)
    
                  document.getElementsByTagName('body')[0].style.cursor = 'pointer' // 移到物体上时鼠标显示为手
                  this.selectObj.material.color.setHex('0xffc466')
                }
              } else {
                // 鼠标移除时恢复材质颜色
                if (this.selectObj) this.selectObj.material.color.setHex('0xfafafa')
                document.getElementsByTagName('body')[0].style.cursor = 'default' // 移出物体时鼠标显示为默认
                // this.selectObj = null
              }
            }
          }, 200)
        }
    
    页面优化第一步

    组件注销前,解绑全局事件、停止刷新。beforeDestroy()

    beforeDestroy () {
        // 解绑事件、停止刷新
        window.removeEventListener('mousemove', this.onDocumentMouseMove, false)
        window.cancelAnimationFrame(this.animationRenId)
        window.cancelAnimationFrame(this.animationAniId)
        this.renderer.dispose()
    
        // 清理数据
        this.raycaster = null
        this.objChildren = []
        this.camera = null
        this.light = null
        this.scene = null
        this.renderer = null
      }
    

    调用方法:
    window.addEventListener('mousemove', this.onDocumentMouseMove, false)
    mousemove事件是一个频繁发生的事件,这里使用setTimeout控制延迟200ms执行,达到优化的目的。
    思路:鼠标移入获取焦点,设置材质的颜色为黄色

    效果如下:


    image.png

    PS:鼠标移入时,先暂存材质的颜色,移除后再恢复。实现过程有bug,getHex()未得到有效的颜色,所以恢复不到原有的材质颜色,正在查找原因。如有有哪位大神发现问题,请赐教。

    页面加载性能优化:
    问题描述:页面模型加载后,页面整体性能下降,出现卡顿。

    优化内容:

    1. 本项目不需要对象和纹理显示单独的加载进度,所以在模型加载步骤中,去掉了LoadingManager
    var manager = new THREE.LoadingManager()
    

    2.页面组件销毁时,解绑鼠标事件
    3.组件销毁时,清除缓存数据

    相关文章

      网友评论

        本文标题:ThreeJS渲染一个.obj三维模型文件(Vue)

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