Three.js 如何将多个没有皮肤的 Mixamo 动画添加到 FBX 模型?
Three.js how to add multiple Mixamo animations without skin to an FBX model?
我正在尝试创建一个游戏,其中有一个动画角色,其中包含来自 Mixamo 的多个动画。我希望动画根据用户在游戏中的操作而变化,例如 Walking
、Running
或 Idle
。这是我加载 FBX
模型(没有动画)的方式:
loader.load('Assets/Animations/Main.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
scene.add(object);
});
我还有以下文件,都是动画没有皮肤
Idle.fbx
Walking.fbx
Running.fbx
我的目标是尝试做出类似 this or like this 的东西。这两个链接的唯一问题是,在第一个链接中,他们使用了一个附加了多个动画的模型(我有一个带有 3 个没有皮肤的动画的普通模型),在第二个链接中,代码是使用 TypeScript
(我更喜欢JavaScript
)。
我是 3D 建模的新手,所以我不知道如何将所有没有皮肤的动画附加到主 fbx 模型。 如何在 Blender 中将动画组合到一个模型中,或者有没有办法在 three.js 中做到这一点?
感谢您对此提供的任何帮助,谢谢!
编辑:
根据@GuyNachshon 的说法,这是我应该如何处理的吗?
所以我首先加载没有动画的模型 (yourMesh
),然后创建一个 AnimationMixer
:
var mixer;
loader.load('Assets/Animations/Main.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
mixer = new THREE.AnimationMixer(object);
object.rotation.x = Math.PI / 2
object.position.x = 11;
scene.add(object);
});
然后,我必须加载没有皮肤的 3 个动画文件并将它们添加到 animationsArray
。 (不确定我是否正确加载了动画...):
loader.load('Assets/Animations/Idle.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
animationsArray.push(object);
scene.add(object);
});
loader.load('Assets/Animations/Walking.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
animationsArray.push(object);
scene.add(object);
});
loader.load('Assets/Animations/Running.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
animationsArray.push(object);
scene.add(object);
});
一切都完全加载后,我创建 actions
:
let actions = mixer.clipAction(animationsArray).play();
但是,在你说到做到之后:
actions.play();
那句台词要播放什么? animationsArray
会播放第一个动画吗?
您需要创建一个 AnimationMixer
。
假设您已经创建了一个场景,添加了一个网格等。现在您可以初始化一个动画混合器
let mixer = new THREE.AnimationMixer(yourMesh);
然后添加动画使用 clipActions
,
let actions = mixer.clipAction(animationsArray).play();
现在播放
actions.play();
但要真正了解如何使用它,您应该阅读文档(附在上面 :))
编辑 - 回应您的编辑
为了控制要播放的动画,您可以做几件事,这里是 docs 中的示例:
const mixer = new THREE.AnimationMixer( mesh );
const clips = mesh.animations;
// Update the mixer on each frame
function update () {
mixer.update( deltaSeconds );
}
// Play a specific animation
const clip = THREE.AnimationClip.findByName( clips, 'dance' );
const action = mixer.clipAction( clip );
action.play();
// Play all animations
clips.forEach( function ( clip ) {
mixer.clipAction( clip ).play();
} );
现在,如果您在构建代码时遇到问题,这里有一个关于如何将动画附加到 fbx 并控制它们的一般示例:
let mixer = THREE.AnimationMixer
let modelReady = false
const animationActions = THREE.AnimationAction
let activeAction = THREE.AnimationAction
let lastAction = THREE.AnimationAction
const fbxLoader = new FBXLoader()
在我们启动了我们需要的一切之后,让我们加载一切:
fbxLoader.load(
(object) => {
'path/to/your/model.fbx',
object.scale.set(0.01, 0.01, 0.01)
mixer = new THREE.AnimationMixer(object)
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'default')
activeAction = animationActions[0] // sets current animation
scene.add(object) // adds animated object to your scene
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded animation')
const animationAction = mixer.clipAction(.animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/other/animation.fbx',
(object) => {
console.log('loaded second animation')
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded third animation');
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
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)
}
)
现在我们应该设置我们的动画和动作是什么:
const animations = {
function default() {
setAction(animationActions[0])
},
function firstAnimation() {
setAction(animationActions[1])
},
function sceondAnimation() {
setAction(animationActions[2])
},
function thirdAnimation() {
setAction(animationActions[3])
}
}
const setAction = {
if (toAction != activeAction) {
lastAction = activeAction
activeAction = toAction
//lastAction.stop()
lastAction.fadeOut(1)
activeAction.reset()
activeAction.fadeIn(1)
activeAction.play()
}
}
让动画!
const clock = new THREE.Clock()
function animate() {
requestAnimationFrame(animate)
controls.update()
if (modelReady) {mixer.update(clock.getDelta())}
render()
}
function render() {
renderer.render(scene, camera)
}
animate()
放在一起:
import * as THREE from 'three'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
let mixer = THREE.AnimationMixer
let modelReady = false
const animationActions = THREE.AnimationAction
let activeAction = THREE.AnimationAction
let lastAction = THREE.AnimationAction
const fbxLoader = new FBXLoader()
fbxLoader.load(
(object) => {
'path/to/your/model.fbx',
object.scale.set(0.01, 0.01, 0.01)
mixer = new THREE.AnimationMixer(object)
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'default')
activeAction = animationActions[0] // sets current animation
scene.add(object) // adds animated object to your scene
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded animation')
const animationAction = mixer.clipAction(.animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/other/animation.fbx',
(object) => {
console.log('loaded second animation')
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded third animation');
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
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)
}
)
const animations = {
function default() {
setAction(animationActions[0])
},
function firstAnimation() {
setAction(animationActions[1])
},
function sceondAnimation() {
setAction(animationActions[2])
},
function thirdAnimation() {
setAction(animationActions[3])
}
}
const setAction = {
if (toAction != activeAction) {
lastAction = activeAction
activeAction = toAction
//lastAction.stop()
lastAction.fadeOut(1)
activeAction.reset()
activeAction.fadeIn(1)
activeAction.play()
}
}
const clock = new THREE.Clock()
function animate() {
requestAnimationFrame(animate)
controls.update()
if (modelReady) {mixer.update(clock.getDelta())}
render()
}
function render() {
renderer.render(scene, camera)
}
animate()
我正在尝试创建一个游戏,其中有一个动画角色,其中包含来自 Mixamo 的多个动画。我希望动画根据用户在游戏中的操作而变化,例如 Walking
、Running
或 Idle
。这是我加载 FBX
模型(没有动画)的方式:
loader.load('Assets/Animations/Main.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
scene.add(object);
});
我还有以下文件,都是动画没有皮肤
Idle.fbx
Walking.fbx
Running.fbx
我的目标是尝试做出类似 this or like this 的东西。这两个链接的唯一问题是,在第一个链接中,他们使用了一个附加了多个动画的模型(我有一个带有 3 个没有皮肤的动画的普通模型),在第二个链接中,代码是使用 TypeScript
(我更喜欢JavaScript
)。
我是 3D 建模的新手,所以我不知道如何将所有没有皮肤的动画附加到主 fbx 模型。 如何在 Blender 中将动画组合到一个模型中,或者有没有办法在 three.js 中做到这一点?
感谢您对此提供的任何帮助,谢谢!
编辑:
根据@GuyNachshon 的说法,这是我应该如何处理的吗?
所以我首先加载没有动画的模型 (yourMesh
),然后创建一个 AnimationMixer
:
var mixer;
loader.load('Assets/Animations/Main.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
mixer = new THREE.AnimationMixer(object);
object.rotation.x = Math.PI / 2
object.position.x = 11;
scene.add(object);
});
然后,我必须加载没有皮肤的 3 个动画文件并将它们添加到 animationsArray
。 (不确定我是否正确加载了动画...):
loader.load('Assets/Animations/Idle.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
animationsArray.push(object);
scene.add(object);
});
loader.load('Assets/Animations/Walking.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
animationsArray.push(object);
scene.add(object);
});
loader.load('Assets/Animations/Running.fbx', function(object){
object.traverse(function (child){
if (child.isMesh) {
child.castShadow = true;
child.receiveShadow = true;
child.frustumCulled = false;
}
});
object.rotation.x = Math.PI / 2
object.position.x = 11;
animationsArray.push(object);
scene.add(object);
});
一切都完全加载后,我创建 actions
:
let actions = mixer.clipAction(animationsArray).play();
但是,在你说到做到之后:
actions.play();
那句台词要播放什么? animationsArray
会播放第一个动画吗?
您需要创建一个 AnimationMixer
。
假设您已经创建了一个场景,添加了一个网格等。现在您可以初始化一个动画混合器
let mixer = new THREE.AnimationMixer(yourMesh);
然后添加动画使用 clipActions
,
let actions = mixer.clipAction(animationsArray).play();
现在播放
actions.play();
但要真正了解如何使用它,您应该阅读文档(附在上面 :))
编辑 - 回应您的编辑
为了控制要播放的动画,您可以做几件事,这里是 docs 中的示例:
const mixer = new THREE.AnimationMixer( mesh );
const clips = mesh.animations;
// Update the mixer on each frame
function update () {
mixer.update( deltaSeconds );
}
// Play a specific animation
const clip = THREE.AnimationClip.findByName( clips, 'dance' );
const action = mixer.clipAction( clip );
action.play();
// Play all animations
clips.forEach( function ( clip ) {
mixer.clipAction( clip ).play();
} );
现在,如果您在构建代码时遇到问题,这里有一个关于如何将动画附加到 fbx 并控制它们的一般示例:
let mixer = THREE.AnimationMixer
let modelReady = false
const animationActions = THREE.AnimationAction
let activeAction = THREE.AnimationAction
let lastAction = THREE.AnimationAction
const fbxLoader = new FBXLoader()
在我们启动了我们需要的一切之后,让我们加载一切:
fbxLoader.load(
(object) => {
'path/to/your/model.fbx',
object.scale.set(0.01, 0.01, 0.01)
mixer = new THREE.AnimationMixer(object)
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'default')
activeAction = animationActions[0] // sets current animation
scene.add(object) // adds animated object to your scene
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded animation')
const animationAction = mixer.clipAction(.animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/other/animation.fbx',
(object) => {
console.log('loaded second animation')
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded third animation');
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
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)
}
)
现在我们应该设置我们的动画和动作是什么:
const animations = {
function default() {
setAction(animationActions[0])
},
function firstAnimation() {
setAction(animationActions[1])
},
function sceondAnimation() {
setAction(animationActions[2])
},
function thirdAnimation() {
setAction(animationActions[3])
}
}
const setAction = {
if (toAction != activeAction) {
lastAction = activeAction
activeAction = toAction
//lastAction.stop()
lastAction.fadeOut(1)
activeAction.reset()
activeAction.fadeIn(1)
activeAction.play()
}
}
让动画!
const clock = new THREE.Clock()
function animate() {
requestAnimationFrame(animate)
controls.update()
if (modelReady) {mixer.update(clock.getDelta())}
render()
}
function render() {
renderer.render(scene, camera)
}
animate()
放在一起:
import * as THREE from 'three'
import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader'
let mixer = THREE.AnimationMixer
let modelReady = false
const animationActions = THREE.AnimationAction
let activeAction = THREE.AnimationAction
let lastAction = THREE.AnimationAction
const fbxLoader = new FBXLoader()
fbxLoader.load(
(object) => {
'path/to/your/model.fbx',
object.scale.set(0.01, 0.01, 0.01)
mixer = new THREE.AnimationMixer(object)
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'default')
activeAction = animationActions[0] // sets current animation
scene.add(object) // adds animated object to your scene
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded animation')
const animationAction = mixer.clipAction(.animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/other/animation.fbx',
(object) => {
console.log('loaded second animation')
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
//add an animation from another file
fbxLoader.load(
'path/to/animation.fbx',
(object) => {
console.log('loaded third animation');
const animationAction = mixer.clipAction(animations[0])
animationActions.push(animationAction)
animationsFolder.add(animations, 'animationName')
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)
}
)
const animations = {
function default() {
setAction(animationActions[0])
},
function firstAnimation() {
setAction(animationActions[1])
},
function sceondAnimation() {
setAction(animationActions[2])
},
function thirdAnimation() {
setAction(animationActions[3])
}
}
const setAction = {
if (toAction != activeAction) {
lastAction = activeAction
activeAction = toAction
//lastAction.stop()
lastAction.fadeOut(1)
activeAction.reset()
activeAction.fadeIn(1)
activeAction.play()
}
}
const clock = new THREE.Clock()
function animate() {
requestAnimationFrame(animate)
controls.update()
if (modelReady) {mixer.update(clock.getDelta())}
render()
}
function render() {
renderer.render(scene, camera)
}
animate()