安裝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 = null
const initEvents = {
raycaster: new THREE.Raycaster(),
pickedObject: null,
pickedObjectSavedColor: 0,
pickPosition: new THREE.Vector2(), //建立二維平面
}
export const renderInit = (container) => {
return new Promise((resolve, reject) => {
canvasContainer = container.current
clearModel()
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.2
console.log('THREE', THREE)
renderer.setPixelRatio(window.devicePixelRatio) // 設定裝置畫素比
canvasContainer.appendChild(renderer.domElement)
controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
animate()
}
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 = obj
group.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() * 10
camera.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 } = config
const loader = modeLoader[type]()
if (['gltf', 'glb'].includes(type)) {
const dracoloader = getDracoLoader()
loader.setDRACOLoader(dracoloader)
}
loader.load(url, (object) => {
const obj = object.scene
model = 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>
)
}