美文网首页
基于threeJs的风机模型加载及旋转

基于threeJs的风机模型加载及旋转

作者: 浮萍逐浪 | 来源:发表于2020-03-04 13:19 被阅读0次
想用threeJs实现3D效果,你需要三个条件:
  • 第一:像Blender一样不花钱的开源轻量级3D软件
  • 第二:你得会用它制作和导出3D模型
  • 第三:熟悉threeJs
好了,等你掌握以上三个技能再看来吧。

前两个条件可以用一个可以调用的UI妹子代替,所以本文是聊代码实现的


Three.js是基于原生WebGL封装运行的三维引擎,在所有WebGL引擎中,Three.js是国内文资料最多、使用最广泛的三维引擎

下面的代码完整展示了在react项目下通过three.js引擎创建的一个三维场景,在场景中导入一个风机的3D模型,并且通过动画使其扇叶进行旋转的效果。

一、threeJs的安装

  • 装包

npm install three
npm i import-three-examples --save-dev

  • 导入
import { WEBGL } from 'three/examples/jsm/WebGL'
import * as THREE from 'three'//全局变量THREE的引入
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'//obj文件加载器
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'// mtl材质文件加载器

二、创建一个场景

为了真正能够让你的场景借助three.js来进行显示,我们需要以下几个对象:场景、相机和渲染器,这样我们就能透过摄像机渲染出场景

 // 创建场景对象
    this.scene = new THREE.Scene()

    // 光源设置
    // 半球光 第一个参数是天空的颜色,第二个参数是地上的颜色,第三个参数是光源的强度
    let hemisphereLight = new THREE.HemisphereLight(0xFFFFFF, 0x000000, 1)
    this.scene.add(hemisphereLight)
    //环境光
    let light = new THREE.AmbientLight(0x404040) // soft white light
    this.scene.add(light)

    // 相机设置
    let width = this.props.width ? this.props.width : window.innerWidth //窗口宽度
    let height = this.props.height ? this.props.height : window.innerHeight //窗口高度
    let camera = new THREE.PerspectiveCamera(5, window.innerWidth / window.innerHeight, 0.1, 6000)
    camera.position.set(400, 0, 400)
    camera.lookAt(this.scene.position)
    // 创建渲染器对象
    let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
    renderer.setSize(width, height)
    renderer.shadowMap.enabled = true
    renderer.shadowMap.type = THREE.PCFSoftShadowMap
    document.getElementById('bigfan').appendChild(renderer.domElement)

三、通过加载器加载模型

本项目采用了obj3D文件和mtl材质文件,故而选择OBJLoader模型加载器和MTLLoader材质加载器

    // 模型加载器
    let loader = new OBJLoader()
    //材质文件加载器
    let mtlLoader = new MTLLoader()
    mtlLoader.load(fanImage.bigFanMtl, (materials) => {
      // 返回一个包含材质的对象MaterialCreator
      // obj的模型会和MaterialCreator包含的材质对应起来
      materials.preload()
      loader.setMaterials(materials)
      // 加载obj模型
      loader.load(
        // 路径
        fanImage.bigFanObj,
        // 当模型加载完成(100%)后执行
        (object) => {
          this.object = object
          // 位置调整
          object.position.y = -50
          object.position.x = -10
          // 添加到场景
          this.scene.add(object)
          this.scene.add(flabellum)
          renderer.render(this.scene, camera)
        },
        // 正在加载的时候
        (xhr) => {
          if (xhr.loaded === xhr.total) {
            this.setState({ spinning: false })
          }
        },
        // 加载出错的时候
        (error) => {
          console.log('An error happened')
        }
      )
    })

在componentDidMount执行上述代码,一个美丽的大风车就出现了


四、添加动画效果

想要实现一个完美的动画,首先你要对模型进行拆解,把能动的扇叶、外转轴、转心等放到一个THREE.Group里,然后通过THREE.Group的旋转。如何知道这些能动的部位在模型的那里呢?
在模型加载后console一下就行了



在浏览器中看一下



显而易见object.children[5]就是外转轴了。

话不多说上代码

         // 抽出外转轴,扇叶,转心
          let yaw = object.children[5]//外转轴
          let fanCenter = object.children[6]//扇叶
          let fanLeaf = object.children[10]//转心

          // 将扇叶和转心单独放到一个组里
          flabellum.add(yaw)
          flabellum.add(fanCenter)
          flabellum.add(fanLeaf)

          // 添加到场景
          this.scene.add(flabellum)

添加并执行动画

 this.animate = () => {
      this.animateId = requestAnimationFrame(this.animate)
      flabellum.rotation.z -= 0.06
      this.scene.dispose()
      renderer.render(this.scene, camera)
    }
    this.animate()

五、查看效果

旋转部分和机体完全分开了,大声的喊出来这叫什么:


bug

六、问题解决

很显然这是由于flabellum组的旋转中心和风扇旋转中心不在一点引起的
解决方法:

          // 通过模型边界框获取模型中心(目标旋转点)
          let objBbox = new THREE.Box3().setFromObject(object.children[10])
          let center = objBbox.clone().getCenter()
          // 反方向移动物体
          yaw.position.set(-center.x, -center.y, -center.z)
          fanCenter.position.set(-center.x, -center.y, -center.z)
          fanLeaf.position.set(-center.x, -center.y, -center.z)
          // 移动Group的位置到目标旋转点
          flabellum.position.set(center.x - 10, center.y - 50, center.z)

七、完整代码

// 风机旋转
import React, { Component } from 'react'
import { Spin } from 'antd'
import { fanImage } from './images/index'

import { WEBGL } from 'three/examples/jsm/WebGL'
import * as THREE from 'three'//全局变量THREE的引入
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'//obj文件加载器
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'// mtl材质文件加载器
class BigFan extends Component {
  constructor(props) {
    super(props)
    this.state = {
      spinning: true
    }
    this.scene = null
    this.animateId = null
  }
  drawFan = () => {
    // 创建场景对象
    this.scene = new THREE.Scene()

    // 光源设置
    // 半球光 第一个参数是天空的颜色,第二个参数是地上的颜色,第三个参数是光源的强度
    let hemisphereLight = new THREE.HemisphereLight(0xFFFFFF, 0x000000, 1)
    this.scene.add(hemisphereLight)
    //环境光
    let light = new THREE.AmbientLight(0x404040) // soft white light
    this.scene.add(light)

    // 相机设置
    let width = this.props.width ? this.props.width : window.innerWidth //窗口宽度
    let height = this.props.height ? this.props.height : window.innerHeight //窗口高度
    let camera = new THREE.PerspectiveCamera(5, window.innerWidth / window.innerHeight, 0.1, 6000)
    camera.position.set(400, 0, 400)
    camera.lookAt(this.scene.position)
    // 创建渲染器对象
    let renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true })
    renderer.setSize(width, height)
    renderer.shadowMap.enabled = true
    renderer.shadowMap.type = THREE.PCFSoftShadowMap
    document.getElementById('bigfan').appendChild(renderer.domElement)

    // 模型加载器
    let loader = new OBJLoader()
    //材质文件加载器
    let mtlLoader = new MTLLoader()
    // 存放旋转部分
    let flabellum = new THREE.Group()
    mtlLoader.load(fanImage.bigFanMtl, (materials) => {
      // 返回一个包含材质的对象MaterialCreator
      // obj的模型会和MaterialCreator包含的材质对应起来
      materials.preload()
      loader.setMaterials(materials)
      // 加载obj模型
      loader.load(
        // 路径
        fanImage.bigFanObj,
        // 当模型加载完成(100%)后执行
        (object) => {
          this.object = object
          // 位置调整
          object.position.y = -50
          object.position.x = -10
          // 抽出偏航系统,扇叶,转心
          let yaw = object.children[5]//偏航系统
          let fanCenter = object.children[6]//扇叶
          let fanLeaf = object.children[10]//转心
          // 通过模型边界框获取模型中心(目标旋转点)
          let objBbox = new THREE.Box3().setFromObject(object.children[10])
          let center = objBbox.clone().getCenter()
          // 反方向移动物体
          yaw.position.set(-center.x, -center.y, -center.z)
          fanCenter.position.set(-center.x, -center.y, -center.z)
          fanLeaf.position.set(-center.x, -center.y, -center.z)
          // 移动Group的位置到目标旋转点
          flabellum.position.set(center.x - 10, center.y - 50, center.z)
          // 将扇叶和转心单独放到一个组里
          flabellum.add(yaw)
          flabellum.add(fanCenter)
          flabellum.add(fanLeaf)
          // 添加到场景
          this.scene.add(object)
          this.scene.add(flabellum)
          renderer.render(this.scene, camera)
        },
        // called when loading is in progresses
        (xhr) => {
          if (xhr.loaded === xhr.total) {
            this.setState({ spinning: false })
          }
        },
        // called when loading has errors
        (error) => {
          console.log('An error happened')
        }
      )
    })
    this.animate = () => {
      this.animateId = requestAnimationFrame(this.animate)
      flabellum.rotation.z -= 0.06
      this.scene.dispose()
      renderer.render(this.scene, camera)
    }
    this.animate()
  }
  componentDidMount() {
    if (WEBGL.isWebGLAvailable()) {
      this.drawFan()
    } else {
      let warning = WEBGL.getWebGLErrorMessage()
      document.getElementById('bigfan').appendChild(warning)
    }
  }
  componentWillUnmount() {
    this.scene.dispose()
    cancelAnimationFrame(this.animateId)
  }
  render() {
    return (
      <div id='bigfan'
        style={{
          width: window.innerWidth,
          height: window.innerHeight,
          display: 'flex',
          justifyContent: 'center',
          alignItems: 'center',
          background: '#001527'
        }}
      >
        <Spin style={{ position: "absolute" }} tip="Loading..." size='large' spinning={this.state.spinning} />
      </div>
    )
  }
}

export default BigFan

八、总结

至此,本文已经完成了,希望本文为读者提供一条比较简单直接迅速的道路去了解threeJs,想要深入了解,官方文档才是根本。
点了订阅和关注下次找我不迷路,不点赞,打赏也可以。

相关文章

网友评论

      本文标题:基于threeJs的风机模型加载及旋转

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