想用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()
五、查看效果

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

六、问题解决
很显然这是由于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,想要深入了解,官方文档才是根本。
点了订阅和关注下次找我不迷路,不点赞,打赏也可以。
![]()
网友评论