在本课中,我们将创建与 FBX 动画课程中创建的项目等效的 GLTF模型。
我们使用Blender将主要的FBX模型及其相关的动画文件转换为GLB文件。
资源
如果您不想使用Blender将文件转换为视频中所示的文件,则可以直接下载它们并保存到fbxglb./dist/client/models
可使用GLTF编辑器预览、编辑这几个模型。
启动脚本
./src/client/client.ts
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import Stats from 'three/examples/jsm/libs/stats.module'
import { GUI } from 'dat.gui'
const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))
const light1 = new THREE.PointLight(0xffffff, 100)
light1.position.set(2.5, 2.5, 2.5)
scene.add(light1)
const light2 = new THREE.PointLight(0xffffff, 100)
light2.position.set(-2.5, 2.5, 2.5)
scene.add(light2)
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.set(0.8, 1.4, 1.0)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.target.set(0, 1, 0)
let mixer: THREE.AnimationMixer
let modelReady = false
const animationActions: THREE.AnimationAction[] = []
let activeAction: THREE.AnimationAction
let lastAction: THREE.AnimationAction
const gltfLoader = new GLTFLoader()
gltfLoader.load(
'models/vanguard.glb',
(gltf) => {
// gltf.scene.scale.set(.01, .01, .01)
mixer = new THREE.AnimationMixer(gltf.scene)
const animationAction = mixer.clipAction((gltf as any).animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'default')
activeAction = animationActions[0]
scene.add(gltf.scene)
// //add an animation from another file
// gltfLoader.load(
// 'models/vanguard@samba.glb',
// (gltf) => {
// console.log('loaded samba')
// const animationAction = mixer.clipAction(
// (gltf as any).animations[0]
// )
// animationActions.push(animationAction)
// animationsFolder.add(animations, 'samba')
// //add an animation from another file
// gltfLoader.load(
// 'models/vanguard@bellydance.glb',
// (gltf) => {
// console.log('loaded bellydance')
// const animationAction = mixer.clipAction(
// (gltf as any).animations[0]
// )
// animationActions.push(animationAction)
// animationsFolder.add(animations, 'bellydance')
// //add an animation from another file
// gltfLoader.load(
// 'models/vanguard@goofyrunning.glb',
// (gltf) => {
// console.log('loaded goofyrunning');
// (gltf as any).animations[0].tracks.shift() //delete the specific track that moves the object forward while running
// const animationAction = mixer.clipAction(
// (gltf as any).animations[0]
// )
// animationActions.push(animationAction)
// animationsFolder.add(animations, 'goofyrunning')
// modelReady = true
// },
// (xhr) => {
// console.log(
// (xhr.loaded / xhr.total) * 100 + '% loaded'
// )
// },
// (error) => {
// console.log(error)
// }
// )
// },
// (xhr) => {
// console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
// },
// (error) => {
// console.log(error)
// }
// )
// },
// (xhr) => {
// console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
// },
// (error) => {
// console.log(error)
// }
// )
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
},
(error) => {
console.log(error)
}
)
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
const stats = new Stats()
document.body.appendChild(stats.dom)
const animations = {
default: function () {
setAction(animationActions[0])
},
samba: function () {
setAction(animationActions[1])
},
bellydance: function () {
setAction(animationActions[2])
},
goofyrunning: function () {
setAction(animationActions[3])
},
}
const setAction = (toAction: THREE.AnimationAction) => {
if (toAction != activeAction) {
lastAction = activeAction
activeAction = toAction
//lastAction.stop()
lastAction.fadeOut(1)
activeAction.reset()
activeAction.fadeIn(1)
activeAction.play()
}
}
const gui = new GUI()
const animationsFolder = gui.addFolder('Animations')
animationsFolder.open()
const clock = new THREE.Clock()
function animate() {
requestAnimationFrame(animate)
controls.update()
if (modelReady) mixer.update(clock.getDelta())
render()
stats.update()
}
function render() {
renderer.render(scene, camera)
}
animate()
最终脚本
./src/client/client.ts
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import Stats from 'three/examples/jsm/libs/stats.module'
import { GUI } from 'dat.gui'
const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))
const light1 = new THREE.PointLight(0xffffff, 1000)
light1.position.set(2.5, 2.5, 2.5)
scene.add(light1)
const light2 = new THREE.PointLight(0xffffff, 1000)
light2.position.set(-2.5, 2.5, 2.5)
scene.add(light2)
const camera = new THREE.PerspectiveCamera(
75,
window.innerWidth / window.innerHeight,
0.1,
1000
)
camera.position.set(0.8, 1.4, 1.0)
const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
controls.target.set(0, 1, 0)
let mixer: THREE.AnimationMixer
let modelReady = false
const animationActions: THREE.AnimationAction[] = []
let activeAction: THREE.AnimationAction
let lastAction: THREE.AnimationAction
const gltfLoader = new GLTFLoader()
gltfLoader.load(
'models/vanguard.glb',
(gltf) => {
// gltf.scene.scale.set(.01, .01, .01)
mixer = new THREE.AnimationMixer(gltf.scene)
const animationAction = mixer.clipAction((gltf as any).animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'default')
activeAction = animationActions[0]
scene.add(gltf.scene)
//add an animation from another file
gltfLoader.load(
'models/vanguard@samba.glb',
(gltf) => {
console.log('loaded samba')
const animationAction = mixer.clipAction(
(gltf as any).animations[0]
)
animationActions.push(animationAction)
animationsFolder.add(animations, 'samba')
//add an animation from another file
gltfLoader.load(
'models/vanguard@bellydance.glb',
(gltf) => {
console.log('loaded bellydance')
const animationAction = mixer.clipAction(
(gltf as any).animations[0]
)
animationActions.push(animationAction)
animationsFolder.add(animations, 'bellydance')
//add an animation from another file
gltfLoader.load(
'models/vanguard@goofyrunning.glb',
(gltf) => {
console.log('loaded goofyrunning')
;(gltf as any).animations[0].tracks.shift() //delete the specific track that moves the object forward while running
const animationAction = mixer.clipAction(
(gltf as any).animations[0]
)
animationActions.push(animationAction)
animationsFolder.add(animations, 'goofyrunning')
modelReady = true
},
(xhr) => {
console.log(
(xhr.loaded / xhr.total) * 100 + '% loaded'
)
},
(error) => {
console.log(error)
}
)
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
},
(error) => {
console.log(error)
}
)
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
},
(error) => {
console.log(error)
}
)
},
(xhr) => {
console.log((xhr.loaded / xhr.total) * 100 + '% loaded')
},
(error) => {
console.log(error)
}
)
window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
renderer.setSize(window.innerWidth, window.innerHeight)
render()
}
const stats = new Stats()
document.body.appendChild(stats.dom)
const animations = {
default: function () {
setAction(animationActions[0])
},
samba: function () {
setAction(animationActions[1])
},
bellydance: function () {
setAction(animationActions[2])
},
goofyrunning: function () {
setAction(animationActions[3])
},
}
const setAction = (toAction: THREE.AnimationAction) => {
if (toAction != activeAction) {
lastAction = activeAction
activeAction = toAction
//lastAction.stop()
lastAction.fadeOut(1)
activeAction.reset()
activeAction.fadeIn(1)
activeAction.play()
}
}
const gui = new GUI()
const animationsFolder = gui.addFolder('Animations')
animationsFolder.open()
const clock = new THREE.Clock()
function animate() {
requestAnimationFrame(animate)
controls.update()
if (modelReady) mixer.update(clock.getDelta())
render()
stats.update()
}
function render() {
renderer.render(scene, camera)
}
animate()
原文链接:GLTF动画 (mvrlink.com)