安装threejs
yarn add three
首先创建 renderModel.js文件
import * as THREE from 'three'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import * as TWEEN from 'three/examples/jsm/libs/tween.module';const modeLoader = {gltf: () => {return new GLTFLoader()},glb: () => {return new GLTFLoader()}
}let canvasContainer = null
let camera = null
let scene = null
let renderer = null
let controls = null
let model = nullconst initEvents = {raycaster: new THREE.Raycaster(),pickedObject: null,pickedObjectSavedColor: 0,pickPosition: new THREE.Vector2(), //创建二维平面
}export const renderInit = (container) => {return new Promise((resolve, reject) => {canvasContainer = container.currentclearModel()init()setLight()resolve()})
}const init = () => {camera = new THREE.PerspectiveCamera(45,canvasContainer.clientWidth / canvasContainer.clientHeight,0.1,1000)camera.position.set(0, 0, 150)camera.up.x = 0;camera.up.y = 1;camera.up.z = 0;camera.updateProjectionMatrix()scene = new THREE.Scene()scene.background = new THREE.Color(0xf8f8f8);renderer = new THREE.WebGLRenderer({antialias: true,alpha: true})renderer.setSize(canvasContainer.clientWidth, canvasContainer.clientHeight)renderer.gammaFactor = 2.2console.log('THREE', THREE)renderer.setPixelRatio(window.devicePixelRatio) // 设置设备像素比canvasContainer.appendChild(renderer.domElement)controls = new OrbitControls(camera, renderer.domElement)controls.enableDamping = trueanimate()
}const animate = () => {requestAnimationFrame(() => animate())renderer.render(scene, camera)controls.update()// TWEEN.update()
}const setLight = () => {const light1 = new THREE.DirectionalLight(0xffffff, 2)light1.position.set(0, 50, 0)scene.add(light1)const light2 = new THREE.DirectionalLight(0xffffff, 2)light2.position.set(0, 50, 100)scene.add(light2)const light3 = new THREE.DirectionalLight(0xffffff, 2)light3.position.set(0, 50, -100)scene.add(light3)const ambientLight = new THREE.AmbientLight(0xffffff, 2)scene.add(ambientLight)
}// 设置模型大小
const initSize = (obj) => {let group = objgroup.updateMatrixWorld()const box = new THREE.Box3().setFromObject(group)const size = box.getSize(new THREE.Vector3())const center = box.getCenter(new THREE.Vector3())const maxSize = Math.max(size.x, size.y, size.z)const targetSize = 1 // 目标大小const scale = targetSize / (maxSize > 1 ? maxSize : 0.5)group.scale.set(scale, scale, scale)controls.maxDistance = size.length() * 10camera.lookAt(center)camera.updateProjectionMatrix()
}const getDracoLoader = () => {const dracoloader = new DRACOLoader()dracoloader.setDecoderPath('3d/draco/')dracoloader.setDecoderConfig({ type: 'js' })dracoloader.preload()return dracoloader
}export const loadModel = (config) => {return new Promise((resolve, reject) => {const { type, url } = configconst loader = modeLoader[type]()if (['gltf', 'glb'].includes(type)) {const dracoloader = getDracoLoader()loader.setDRACOLoader(dracoloader)}loader.load(url, (object) => {const obj = object.scenemodel = obj.children[0]scene.add(obj)// 添加世界坐标辅助器const axesHelper = new THREE.AxesHelper(5);scene.add(axesHelper);initSize(obj)resolve(true)}, () => {}, err => {console.log(err)reject()})})
}// 模型切换清除上个模型
const clearModel = () => {// 必须要清空当前div下的canvas不然canvas会继续叠加if (canvasContainer) {canvasContainer.firstChild && canvasContainer.removeChild(canvasContainer.firstChild)}
}export const tween3D = (Position) => { //传递任意目标位置,从当前位置运动到目标位置camera.lookAt(new THREE.Vector3(0, 0, 0));// var p1 = { //定义相机位置是目标位置到中心点距离的2.2倍// x: camera.position.x / 2.2,// y: camera.position.y - 15,// z: camera.position.z / 2.2// }var p1 = { //定义相机位置是目标位置到中心点距离的2.2倍x: camera.position.x,y: camera.position.y,z: camera.position.z}var p2 = {x: Position.x,y: Position.y,z: Position.z,}var update = function (object) {// camera.position.set(object.x * 2.2, object.y + 15, object.z * 2.2);camera.position.set(object.x, object.y, object.z);// controls.target.set(object.x, object.y, object.z);camera.lookAt(0, 0, 0); //保证动画执行时,相机焦距在中心点controls.enabled = false;controls.update();};var tween = new TWEEN.Tween(p1).to(p2, 1200)//第一段动画.onUpdate(update)// 动画完成后的执行函数.onComplete(() => {controls.enabled = true; //执行完成后开启控制}).easing(TWEEN.Easing.Quadratic.InOut).start();// 放大动画var targetScale = { x: 2, y: 2, z: 2 }; // 目标缩放比例const tweenScale = new TWEEN.Tween(model.scale) // 缩放对象.to(targetScale, 1000) // 动画时间.onComplete(() => {// Tween动画完成后重新绑定事件监听器// window.addEventListener('wheel', onMouseWheel, false);}).easing(TWEEN.Easing.Quadratic.InOut) // 缓动函数.start(); // 开始动画
}// 组合模型加载(以obj和mtl 为例)
// const loadGroupModel = () => {
// const { type, url } = config
// const typeList = type.split(',')
// let t1 = typeList[0]
// let t2 = typeList[1]
// const loader1 = modeLoader[t1]()
// const loader2 = modeLoader[t2]()
// loader1.load(url[0], (materials) => {
// materials.preload()
// loader2.setMaterials(materials)
// loader2.load(url[1], (object) => {
// scene.add(object)
// initSize(object)
// })
// })
// }
DRACOLoader需要将 node_modules/three/examples/jsm/libs下的draco文件夹复制到 public 文件下
需要安装 copy-webpack-plugin 打包时将public文件夹复制出来
yarn add copy-webpack-plugin
webpack.config.js中
const copyWebpackPlugin = require('copy-webpack-plugin');module.exports = {...plugins: [...new copyWebpackPlugin([{ from: "public" }]),],
}
使用
import React, { useState, useEffect, useRef } from 'react';
import { renderInit, loadModel } from './renderModel'
import s from './ThreeModel.less'import model from './images/3d/pump3d.glb';export default function ThreeModel(props) {const container = useRef()const config = {type: 'glb',url: model,}useEffect(() => {if (container.current && props.show?.pool3D) {loadObj(config)}}, [container, props.show?.pool3D])const loadObj = async (params) => {await renderInit(container)const load = await loadModel(params)// load =true 表示加载完成console.log('load', load)}return (<div className={s.modelCon}><div className={s.modelBox} style={{ width: '100%', height: '100%' }} ref={container}></div></div>)
}