美文网首页前端开发那些事儿
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