Skeleton Animation
Skeleton animation is a type of model animation, which drives the model animation by rotating and translating the Joint
of the skeleton, transforming the position of the Mesh
vertex.
TIP
- Currently, the engine only supports bone animations that are built into the model, requiring users to prepare the corresponding skeletal animation assets in advance using 3D modeling software.
- From
v0.8
, both skeletal animations and Morph animations are driven uniformly through the AnimatorComponent.
Introduction
Each vertex data on a Mesh
contains the index numbers of the bones that affect that vertex, as well as the weights of those influences. This type of data is collectively referred to as skinning information. The number of bones that can influence a vertex is generally limited to 4
; more bones only increase the computational load without significantly improving animation quality.
In the AnimatorComponent
, PrefabBoneData
includes data related to bone joints, such as names, rotation, translation, and parent bones. Multiple PrefabBoneData
entries together form a complete skeletal structure called PrefabAvatarData
.
PropertyAnimationClip
is a dataset of curves representing a series of transformations for skeletal poses, storing transformation data for scaling, rotation, and translation for each bone node.
PropertyAnimationClipState
represents the animation playback state, which is associated with PropertyAnimationClip
and is used to maintain playback status, interpolation weights, and other related data.
The AnimatorComponent
is the driving component for the entire animation. It is associated with multiple PropertyAnimationClipState
instances to switch and blend between various animation states, driving the final transformation posture of the entire skeletal animation.
Load Animation Model
When loading a model file with skeleton animation data, the engine will automatically add a AnimatorComponent
component to the model, and add the animation clip data in the model to it. You can directly get the AnimatorComponent
component on the root entity of the model, and play the specified animation.
// Load external model;
let soldier = await Engine3D.res.loadGltf('gltfs/glb/Soldier.glb');
soldier.rotationY = -90;
soldier.localScale.set(2, 2, 2);
scene.addChild(soldier);
// Get animation controller;
let animator = soldier.getComponentsInChild(AnimatorComponent)[0];
animator.playAnim('Walk');
Get Animation Name
This component provides the clips property to get all animation clip data objects, which all have a unique clipName
property to distinguish different animation states.
let clips = animation.clips;
for (var i = 0; i < clips.length; i++) {
console.log("Name:", clips[i].clipName)
}
Play Specified Animation
AnimatorComponent
provides the playAnim method to play the specified animation:
//Play animation with name 'Walk'
animator.playAnim('Walk');
// Play the first animation in the list
let clips = animation.clips;
animator.playAnim(clips[0].clipName);
Adjust Playback Speed
playAnim
method plays the specified animation at the default normal speed (1.0)
, if you need to accelerate the playback, set the parameter speed
, the larger the number, the faster the playback speed, the smaller the number, the slower the playback speed, when the value is negative, it will be reversed.
// Normal speed
animator.playAnim('Walk', 1);
// 2 times slower
animator.playAnim('Walk', 0.5);
// 3 times faster
animator.playAnim('Walk', 3.0);
// Reverse playback
animator.playAnim('Walk', -1.0);
// 3 times faster reverse playback
animator.playAnim('Walk', -3.0);
You can also set the global timeline scaling through the timeScale
property on AnimatorComponent
, which is the same as speed
. The larger the number, the faster the playback speed, the smaller the number, the slower the playback speed, and when the value is negative, it will be reversed.
// Normal speed
animator.timeScale = 1.0;
// 2 times slower
animator.timeScale = 0.5;
// 2 times faster
animator.timeScale = 2.0;
// 2 times faster reverse playback
animator.timeScale = -2.0;
import { Engine3D, Scene3D, Object3D, AtmosphericComponent, View3D, DirectLight, HoverCameraController, Color, CameraUtil, SkeletonAnimationComponent, Vector3, AnimatorComponent } from '@orillusion/core';
import * as dat from 'dat.gui';
// Init Engine3D
await Engine3D.init();
// Create Scene3D
let scene = new Scene3D();
// add a camera object with Camera3D
let mainCamera = CameraUtil.createCamera3DObject(scene);
mainCamera.perspective(60, Engine3D.aspect, 0.1, 10000.0);
let hc = mainCamera.object3D.addComponent(HoverCameraController);
hc.setCamera(0, -15, 5, new Vector3(0, 1, 0));
// add a dir light
{
let ligthObj = new Object3D();
ligthObj.rotationY = 135;
ligthObj.rotationX = 45;
let dl = ligthObj.addComponent(DirectLight);
dl.lightColor = new Color(1.0, 0.95, 0.84, 1.0);
scene.addChild(ligthObj);
dl.castShadow = true;
dl.intensity = 5.0;
}
// load test model
let soldier = await Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/glb/Soldier.glb');
soldier.rotationY = -90;
soldier.localScale.set(2, 2, 2);
scene.addChild(soldier);
// get animator component
let soldierAnimation = soldier.getComponentsInChild(AnimatorComponent)[0];
soldierAnimation.playAnim('Idle');
const GUIHelp = new dat.GUI();
GUIHelp.addFolder('Animation');
GUIHelp.add(soldierAnimation, 'timeScale', -6, 6, 0.01);
GUIHelp.add({ Idle: () => soldierAnimation.playAnim('Idle') }, 'Idle');
GUIHelp.add({ Walk: () => soldierAnimation.playAnim('Walk') }, 'Walk');
GUIHelp.add({ Run: () => soldierAnimation.playAnim('Run') }, 'Run');
// set skybox
scene.addComponent(AtmosphericComponent).sunY = 0.6;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
Engine3D.startRenderView(view);
Animation Transition
You can use the crossFade method to transition the current animation to the specified state. The first parameter is the name of the animation state to transition to, and the second parameter is the transition time (seconds)
.
// Play walk animation
animation.playAnim('Walk');
// Transition from walk state to run state in 1 second
animation.crossFade('Run', 1.0);
import { Engine3D, Scene3D, Object3D, AtmosphericComponent, View3D, DirectLight, HoverCameraController, Color, CameraUtil, SkeletonAnimationComponent, Vector3, AnimatorComponent } from '@orillusion/core';
import * as dat from 'dat.gui';
// Init Engine3D
await Engine3D.init();
// Create Scene3D
let scene = new Scene3D();
scene.exposure = 0.3;
// add a camera object with Camera3D
let mainCamera = CameraUtil.createCamera3DObject(scene);
mainCamera.perspective(60, Engine3D.aspect, 0.1, 10000.0);
let hc = mainCamera.object3D.addComponent(HoverCameraController);
hc.setCamera(0, -15, 5, new Vector3(0, 1, 0));
// set light
{
let ligthObj = new Object3D();
ligthObj.rotationY = 135;
ligthObj.rotationX = 45;
let dl = ligthObj.addComponent(DirectLight);
dl.lightColor = new Color(1.0, 0.95, 0.84, 1.0);
scene.addChild(ligthObj);
dl.castShadow = true;
dl.intensity = 5.0;
}
// load test model
let soldier = await Engine3D.res.loadGltf('https://cdn.orillusion.com/gltfs/glb/Soldier.glb');
soldier.rotationY = -90;
soldier.localScale.set(2, 2, 2);
scene.addChild(soldier);
// get animator component
let animator = soldier.getComponentsInChild(AnimatorComponent)[0];
animator.playAnim('Idle');
const GUIHelp = new dat.GUI();
let f = GUIHelp.addFolder('Animation-weight');
animator.clipsState.forEach((clipState, _) => {
f.add(clipState, 'weight', 0, 1.0, 0.01).name(clipState.clip.clipName);
});
f.open();
f = GUIHelp.addFolder('Animation-play');
animator.clipsState.forEach((clipState, _) => {
f.add({ click: () => animator.playAnim(clipState.clip.clipName) }, 'click').name(clipState.clip.clipName);
});
f.open();
f = GUIHelp.addFolder('Animation-crossFade');
animator.clipsState.forEach((clipState, _) => {
f.add({ click: () => animator.crossFade(clipState.clip.clipName, 0.3) }, 'click').name('crossFade(' + clipState.clip.clipName + ')');
});
f.open();
// set skybox
scene.addComponent(AtmosphericComponent).sunY = 0.6;
// create a view with target scene and camera
let view = new View3D();
view.scene = scene;
view.camera = mainCamera;
// start render
Engine3D.startRenderView(view);