运用相关:vue3、three.js
模型下载:https://github.com/mrdoob/three.js/
<template>
<div ref="sceneRef">
</template>
<script setup>
import {onMounted,ref }from 'vue'
import *as THREEfrom "three"
import { OrbitControls }from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader }from 'three/examples/jsm/loaders/GLTFLoader.js'
import gsapfrom 'gsap'
let scene,camera,renderer,orbitControls
let sceneRef =ref()
let model =ref(null)//用于模型移动
let mixer =ref(null)
let clock =new THREE.Clock()
let curve =null
//启动动画
function startAnimation(skinnedMesh, animations, animationName) {
// 申明动画场景
const m_mixer =new THREE.AnimationMixer(skinnedMesh)
// 查找动画
const clip = THREE.AnimationClip.findByName(animations, animationName)
if (clip) {
// 播放
const action =m_mixer.clipAction(clip)
action.play()
}
return m_mixer
}
// 曲线/移动
function makeCurve() {
// 创建曲线
curve =new THREE.CatmullRomCurve3([
new THREE.Vector3(0,0,0),
new THREE.Vector3(5,0,0),
new THREE.Vector3(0,0,5)
])
// 曲线类型
curve.curveType ="catmullrom"
// 设置曲线闭环
curve.closed =true
// 设置曲线张力0-1
curve.tension =0.5
// 获取点位
const points =curve.getPoints(100)
// 存储属性
const geometry =new THREE.BufferGeometry().setFromPoints(points)
// 更改线的颜色
const material =new THREE.LineBasicMaterial({color:0x000000})
// 生成线
const curveObject =new THREE.Line(geometry,material)
// 加入场景
scene.add(curveObject)
let obj ={num:0 }
gsap.to(obj, {
num:1,
// 时间
duration:30,
// 循环
repeat: -1,
// 缓动效果
ease:'none',
onUpdate() {
let point =curve.getPoint(obj.num)
model.value.scene.position.copy(point)
let pointNext =curve.getPoint(obj.num -0.001)
model.value.scene.lookAt(pointNext)
}
})
// console.log(points)
// console.log('gltf', model)
}
// 引入模型
function loadModel() {
const gltfLoader =new GLTFLoader()
gltfLoader.setPath('/modules/')
.load('Soldier.glb', (gltf) => {
console.log(gltf,'gltf')
// 旋转模型
gltf.scene.rotation.y =Math.PI
// 模型缩放
gltf.scene.scale.set(1,1,1)
// 遍历模型对象(网格对象:改变属性)
gltf.scene.traverse((object) => {
if (object.isMesh) {
// 阴影
object.castShadow =true
// 接受投影
object.receiveShadow =true
}
})
// 获取跑步动画
mixer.value =startAnimation(
gltf.scene,
gltf.animations,
gltf.animations[3].name
)
model.value = gltf
makeCurve()
scene.add(gltf.scene)
})
}
function init() {
// 场景
scene =new THREE.Scene()
// 相机 透视相机 fov视场角 aspect宽高比 near靠近摄像机的裁剪平面的距离 far远处的裁剪平面的距离
camera =new THREE.PerspectiveCamera(75,window.innerWidth /window.innerHeight,0.1,1000)
// 渲染器
renderer =new THREE.WebGLRenderer()
// 相机位置
camera.position.set(5,5,5)
// 相机看向那边
camera.lookAt(scene.position)
// 添加辅助坐标x(红色),y(绿色),z(蓝色)
const axes =new THREE.AxesHelper(20)
scene.add(axes)
// 场景背景
scene.background =new THREE.Color(0xa0a0a0)
// 场景边界雾化效果
scene.fog =new THREE.Fog(0xa0a0a0,10,30)
// 半球形光源
const hemiLight =new THREE.HemisphereLight(0xffffff,0x444444)
hemiLight.position.set(0,10,0)
scene.add(hemiLight)
// 创建一个虚拟的球形网格 Mesh 的辅助对象来模拟 半球形光源HemisphereLight
const hemiLightHelper =new THREE.HemisphereLightHelper(hemiLight,5)
scene.add(hemiLightHelper)
// 地面 Mesh三位网格对象(集合体对象,材质) PlaneGeometry创建平面几何体 MeshPhongMaterial模拟光滑表面的高光效果
const mesh =new THREE.Mesh(new THREE.PlaneGeometry(100,100),new THREE.MeshPhongMaterial({color:0x999999,depthWrite:false}))
mesh.rotation.x = -Math.PI /2
mesh.receiveShadow =true
scene.add(mesh)
// 平行光
const directionalLight =new THREE.DirectionalLight(0xffffff)
// 灯光需要开启“引起阴影”
directionalLight.castShadow =true
// 阴影样式
directionalLight.shadow.camera.near =0.5
directionalLight.shadow.camera.far =50
directionalLight.shadow.camera.left = -10
directionalLight.shadow.camera.right =10
directionalLight.shadow.camera.top =10
directionalLight.shadow.camera.bottom = -10
directionalLight.position.set(0,5,5)
// 阴影清晰度 默认512
directionalLight.shadow.mapSize.set(2048,2048)
scene.add(directionalLight)
// 用于模拟场景中平行光 DirectionalLight 的辅助对象. 其中包含了表示光位置的平面和表示光方向的线段
const directionalLightHelper =new THREE.DirectionalLightHelper(directionalLight,5)
scene.add(directionalLightHelper)
// 渲染阴影
renderer.shadowMap.enabled =true
renderer.setSize(window.innerWidth,window.innerHeight)
sceneRef.value.appendChild(renderer.domElement)
}
function animate() {
requestAnimationFrame(animate)
if(mixer.value){
mixer.value.update(clock.getDelta())
}
renderer.render(scene,camera)
}
function initOrbitControls() {
orbitControls =new OrbitControls(camera,renderer.domElement)
// orbitControls.enableDamping = true
//相机位置与观察目标点最大值
// orbitControls.maxDistance = 1300
// orbitControls.maxPolarAngle = Math.PI / 2
// // 上下旋转范围
// orbitControls.minPolarAngle = -Math.PI / 2 //默认值0
// orbitControls.maxPolarAngle = Math.PI / 2 //默认值Math.PI
// // // 左右旋转范围
// orbitControls.minAzimuthAngle = -Math.PI / 2
// orbitControls.maxAzimuthAngle = Math.PI / 2
}
// 监听窗口大小变化事件
window.addEventListener('resize',function() {
// 更新渲染器尺寸
renderer.setSize(window.innerWidth,window.innerHeight);
// 更新相机宽高比
camera.aspect =window.innerWidth /window.innerHeight;
camera.updateProjectionMatrix();
});
onMounted(() => {
init()
loadModel()
initOrbitControls()
animate()
})
<style scoped lang="scss">
</style>