今天主要学习如何加载glb模型,这个案例主要用到的相关知识点:加载glb模型,动画的使用、阴影和gui控制面板。具体效果如下:
首先需要基础的场景、相机、渲染器、控制器,不清楚的话请转至three.js基础案例day01
1. 创建平面和灯光
// 灯光
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444)
scene.add(hemiLight)
dirLight = new THREE.DirectionalLight(0xffffff)
dirLight.position.set(-5, 2, 0)
scene.add(dirLight)
// 平面
const planeGeometry = new THREE.PlaneGeometry(10, 10)
const planeMaterial = new THREE.MeshPhongMaterial({
color: 0x808080,
side: THREE.DoubleSide,
})
plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.rotateX(Math.PI / 2)
scene.add(plane)
2.引入GLTFLoader和加载模型
// 引入
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
const loader = new GLTFLoader()
loader.load('static/Soldier.glb', (glb) => {
model = glb.scene
// 模型显示在场景中是背对着的,所以旋转180度,面对相机
model.rotateY(Math.PI)
scene.add(model)
})
3.阴影(这个之前的案例中使用过)
// 遍历模型的每一个子模型,判断子模型是是否是mesh,是mesh设置阴影为true
model.traverse((item) => {
if (item.isMesh) {
item.castShadow = true
}
})
plane.receiveShadow = true
dirLight.castShadow = true
renderer.shadowMap.enabled = true
4.动画
// 将模型中的动画存储到animations变量中,方便后面使用
animations = glb.animations
5.引入GUI面板并使用
// 引入
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
// 使用
gui = new GUI()
6.将模型动画和GUI面板结合
let modelControls = {
run() {
const clip = animations[1]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
walk() {
const clip = animations[3]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
TPose() {
const clip = animations[2]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
Idle() {
const clip = animations[0]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
}
gui.add(modelControls, 'Idle').name('蹲')
gui.add(modelControls, 'run').name('跑')
gui.add(modelControls, 'walk').name('走')
gui.add(modelControls, 'TPose').name('站立')
7.在render中更新动画
function render() {
requestAnimationFrame(render)
// 获取帧时间,在最开始的时候有设置clock = new THREE.Clock()
const delta = clock.getDelta()
controls.update()
renderer.render(scene, camera)
// 更新
mixer.update(delta)
}
全部代码:
<template>
<div id="threeId" ref="elementRef"></div>
</template>
<script setup>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
import { ref, onMounted, onBeforeUnmount } from 'vue'
let width, height, scene, camera, renderer, controls
let model, dirLight, plane
let animations, gui
let mixer = new THREE.AnimationMixer(model)
let clock = new THREE.Clock()
const elementRef = ref(null)
onMounted(() => {
const element = elementRef.value
width = element.offsetWidth
height = element.offsetHeight
initScene()
initModel()
initGUI()
initShadow()
render()
})
function initScene() {
// 初始化场景: 创建场景,相机,物体,渲染器
scene = new THREE.Scene()
scene.background = new THREE.Color(0x888888)
camera = new THREE.PerspectiveCamera(60, width / height, 1, 1000)
camera.position.set(0, 2, 5)
scene.add(camera)
// 三维坐标
// const axesHelper = new THREE.AxesHelper(40)
// scene.add(axesHelper)
renderer = new THREE.WebGLRenderer()
renderer.setSize(width, height)
elementRef.value.appendChild(renderer.domElement)
controls = new OrbitControls(camera, renderer.domElement)
// 光
const hemiLight = new THREE.HemisphereLight(0xffffff, 0x444444)
scene.add(hemiLight)
dirLight = new THREE.DirectionalLight(0xffffff)
dirLight.position.set(-5, 2, 0)
scene.add(dirLight)
}
function initModel() {
const planeGeometry = new THREE.PlaneGeometry(10, 10)
const planeMaterial = new THREE.MeshPhongMaterial({
color: 0x808080,
side: THREE.DoubleSide,
})
plane = new THREE.Mesh(planeGeometry, planeMaterial)
plane.rotateX(Math.PI / 2)
scene.add(plane)
const loader = new GLTFLoader()
loader.load('static/Soldier.glb', (glb) => {
model = glb.scene
animations = glb.animations
model.rotateY(Math.PI)
model.traverse((item) => {
if (item.isMesh) {
item.castShadow = true
}
})
scene.add(model)
})
}
function initGUI() {
let modelControls = {
run() {
const clip = animations[1]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
walk() {
const clip = animations[3]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
TPose() {
const clip = animations[2]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
Idle() {
const clip = animations[0]
mixer = new THREE.AnimationMixer(model)
const action = mixer.clipAction(clip)
action.play()
},
}
gui = new GUI()
gui.add(modelControls, 'Idle').name('蹲')
gui.add(modelControls, 'run').name('跑')
gui.add(modelControls, 'walk').name('走')
gui.add(modelControls, 'TPose').name('站立')
}
function initShadow() {
plane.receiveShadow = true
dirLight.castShadow = true
renderer.shadowMap.enabled = true
}
function render() {
requestAnimationFrame(render)
const delta = clock.getDelta()
controls.update()
renderer.render(scene, camera)
mixer.update(delta)
}
onBeforeUnmount(() => {
if (gui) {
// 销毁操作-清空场景、从DOM上删除渲染器等
scene.remove(camera)
// 停止渲染循环
cancelAnimationFrame(render)
// 释放渲染器的内存
renderer.forceContextLoss() // 强制上下文丢失
// 释放所有相关内容
renderer.dispose()
gui.destroy()
gui = null
}
})
</script>