threeJs常用函数封装
three小知识
模型渲染顺序 Mesh属性 renderOrder = 0
如果在中途修改纹理色彩空间,需要设置`texture.needsUpdate=true`才可以生效。
压缩模型
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
function loaderDraco() {
const loader = new GLTFLoader();
const draco = new DRACOLoader();
draco.setDecoderPath('./draco/');
loader.setDRACOLoader(draco);
return loader;
}
export default loaderDraco;
加载模型
import * as THREE from 'three';
import loaderDraco from '../hook/DracoLoader';
function modelFn(url: any) {
let modelArr = new THREE.Group();
loaderDraco().load(url, (gltf: any) => {
modelArr.add(gltf.scene);
})
return modelArr
}
export default modelFn;
范围随机数
const getRandomIntFn = (min: number, max: number) => {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
export default getRandomIntFn;
getRandomIntFn(0, 20)
纹理贴图
import { THREE } from "../hook/web3d";
const texLoader = new THREE.TextureLoader()
function textureFn(url: any, material: any = 0) {
let Mesh: any
let texture = texLoader.load(url)
texture.colorSpace = THREE.SRGBColorSpace
if (material == 0) {
const geometry = new THREE.BoxGeometry(1, 1, 1)
const materials = new THREE.MeshBasicMaterial({
map: texture,
transparent: true,
})
Mesh = new THREE.Mesh(geometry, materials)
}
if (material == 'Sprite') {
const spriteMaterial = new THREE.SpriteMaterial({
map: texture,
transparent: true,
})
Mesh = new THREE.Sprite(spriteMaterial)
}
return Mesh
}
export default textureFn
gsap动画
import { useStore } from '../../pinia/index'
const store = useStore()
import { camera, gsap, controls } from '../hooks/web3d';
let GsapFn = (posArr:any,delays:number=0,durations:number=2)=>{
let posArrz = posArr.z + 30;
gsap.to(camera.position, {
x: posArr.x +30,
y: posArr.y +30,
z: posArrz,
delay: delays,
duration: durations,
onStart: () => {
controls.reset();
store.contrbol = false;
},
onUpdate: () => {
camera.lookAt(posArr);
controls.target.set(posArr.x,posArr.y,posArr.z);
controls.update();
},
});
}
const dbgsapFn = ()=>{
gsap.to(camera.position, {
x: 0,
y: 150,
z: 300,
delay:1,
duration: 1.5,
onStart: () => {
controls.reset()
},
onUpdate: () => {
camera.lookAt(0,0,0);
},
})
}
export { dbgsapFn }
export default GsapFn;
动画节流
import { ref } from 'vue'
import { stats, controls, renderer, scene, camera } from "./hook/web3d"
let renderbol = ref(false)
let render = () => {
if (renderbol.value) {
stats.update()
controls.update()
console.log('动画防抖节流')
}
renderer.render(scene, camera)
requestAnimationFrame(render)
}
render()
let timer: any = null
const throttle = (delay = 10000) => {
renderbol.value = true
if (timer == null) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
renderbol.value = false
}, delay)
}
}
throttle(10000)
controls.addEventListener('change', () => {
renderbol.value = true
throttle(5000)
})
web3d
import * as THREE from 'three'
import gsap from "gsap"
import { PathGeometry, PathPointList } from 'three.path'
import Stats from 'three/addons/libs/stats.module.js'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'
import model from '../model/model'
/* 墨 */
let width = window.innerWidth
let height = window.innerHeight
/* 墨 */
/* 一. 创建3D场景对象Scene */
const scene = new THREE.Scene()
/* 检测器 */
const stats: any = new Stats()
/* 二.相机 */
/* 1.透视投影相机视锥体 */
let camera = new THREE.PerspectiveCamera(60, width / height, 0.5, 1650)
/* 2.相机位置 */
camera.position.set(0, 100, 300)
/* 3.观察目标 */
// camera.lookAt(model.position)
/* 三.WebGL渲染器 */
/* 1.创建渲染器 */
let renderer = new THREE.WebGLRenderer({
// alpha: true,
antialias: true,
logarithmicDepthBuffer: true
})
/* 2.定义尺寸 颜色 抗锯齿 设备像素 条纹影响渲染效果优化*/
renderer.setSize(width, height)
renderer.setClearColor('
renderer.setPixelRatio(window.devicePixelRatio)
/* 3.渲染 场景及相机 */
renderer.render(scene, camera)
/* 墨 */
/* 平行光 环境*/
const directionalLight = new THREE.DirectionalLight('#fff', 3)
directionalLight.position.set(300, 150, 150)
const ambient = new THREE.AmbientLight('#fff', 3)
/* 相机控件 */
const controls = new OrbitControls(camera, renderer.domElement)
// 上下旋转范围
controls.minPolarAngle = 0
controls.maxPolarAngle = Math.PI / 2.1
controls.minDistance = 20
controls.maxDistance = 800
controls.enableDamping = true
controls.dampingFactor = 0.03
/* 1. 添加模型 */ // directionalLight sphereMesh dibanMod
let sceneArr = [ambient, model]
scene.add(...sceneArr)
/* 墨 窗口响应 */
window.onresize = () => {
let width = window.innerWidth
let height = window.innerHeight
renderer.setSize(width, height)
camera.aspect = width / height
camera.updateProjectionMatrix()
renderer.render(scene, camera)
}
/* import { ref } from 'vue'
let renderbol = ref(false)
let render = () => {
if (renderbol.value) {
stats.update()
controls.update()
renderer.render(scene, camera)
}
requestAnimationFrame(render)
}
render()
let timer: any = null
const throttle = (delay = 10000) => {
renderbol.value = true
if (timer == null) {
timer = setTimeout(() => {
clearTimeout(timer)
timer = null
renderbol.value = false
}, delay)
}
}
throttle(10000) */
export {
scene, camera, renderer, controls, width, height,
THREE, stats, gsap, PathGeometry, PathPointList
}
关键帧动画物体状态最后一步监听
const mixer = new THREE.AnimationMixer(gltf.scene)
const action=mixer.clipAction(gltf.animations[item.animationNumber])
action.play()
action.loop = THREE.LoopOnce
action.clampWhenFinished = true
if (action.paused) {
cancelAnimationFrame(planAnimarenderbol)
}
mixer.addEventListener( 'finished', function(e) {
cancelAnimationFrame(planAnimarenderbol)
})
异步加载模型
let gltfLoader:any = null
let dracoLoader:any = null
const initLoaders = (dracoDecoderPath = "./draco/") => {
if (!gltfLoader) {
dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath(dracoDecoderPath)
gltfLoader = new GLTFLoader()
gltfLoader.setDRACOLoader(dracoLoader)
}
}
initLoaders()
const loadModel = async (url:any, onProgress:any) => {
if (!gltfLoader) {
throw new Error('mesh loader not initialized')
}
return new Promise((resolve, reject) => {
gltfLoader.load(
url,
(gltf:any) => resolve(gltf),
(xhr:any) => {
if (typeof onProgress === 'function') {
onProgress((xhr.loaded / xhr.total) * 100)
}
},
(error:any) => reject(error)
)
})
}
export default loadModel;
const loadDemoModel = async () => {
try {
const model: any = await loadModel(
'/mo-Group/shunxN.glb',
(progress: any) => {
console.log(`Loading: ${progress.toFixed(1)}%`)
}
)
console.log('模型加载完成', model)
modelNeiGroup.add(model.scene)
modelNeiGroup.visible = false;
} catch (error) {
console.error('模型加载失败:', error)
}
}