Three.jsのアニメーションシステムの概要
- Three.jsのアニメーションシステムは、「キーフレーム」を使用してアニメーションを定義します
- 「キーフレーム」とは:特定の時間におけるプロパティの値を指定するもの
- 「KeyframeTrack」とは:「キーフレーム」をまとめたもでアニメーションの1つの要素になります
- 「AnimationClip」とは:複数の「KeyframeTrack」を組み合わせて作成、これがアニメーションデータとなります
アニメーションの実装
- アニメーションを適用したいオブジェクトを「AnimationMixer」に紐付けます
- AnimationMixerのclipActionメソッドを使い、「AnimationAction」を作成
「AnimationAction」には再生や一時停止などのコントロールがあり、アニメーションの操作ができます - 毎フレームごとにAnimationMixerのupdateメソッドを呼び出す
let meshObj;
// アニメーションミキサーの作成
const mixer = new THREE.AnimationMixer(meshObj);
// オブジェクトのアニメーションクリップの取得
const clips = meshObj.animations;
// 各フレームでミキサーを更新
function update(deltaSeconds) {
mixer.update(deltaSeconds);
}
// 特定のアニメーションを再生
const clip = THREE.AnimationClip.findByName(clips, 'dance');
const action = mixer.clipAction(clip);
action.play();
// 全てのアニメーションを再生
clips.forEach(function(clip) {
mixer.clipAction(clip).play();
});
Mixamoを利用する
Mixamoについて
MixamoはAdobeによって提供される、3Dキャラクターアニメーションのためのオンラインプラットフォームです
- 広範囲のアニメーションライブラリ:歩行、走行、ジャンプなど様々なアニメーションを提供しており、それらは様々なタイプのキャラクターに適用可能
- 自動リギング機能:独自の3Dキャラクターモデルをアップロードすると、Mixamoは自動的にモデルに骨格を追加し、アニメーションを適用できる状態にします
- カスタマイズ可能:アニメーションの速度、範囲、角度などを調整し、プロジェクトのニーズに合わせてカスタマイズできます
Mixamoは3Dキャラクターモデルも提供していますが
今回はSketchfabからダウンロードしたモデルにアニメーションを実装します
*Sketchfabは多様な3Dモデルを提供するプラットフォームです
Blenderで調整
Mixamoのアップロードできるファイル形式は、FBX、OBJ、ZIP です
まずはSketchfabからダウンロードしたモデルが対応するファイル形式でない場合や、スケール等の調整が必要な場合は、3Dモデリングソフトウェア(今回はBlenderを使用します)を使い調整して、FBX形式でダウンロードします
*Blenderのエクスポート設定時に注意すること
- 「パスモード」を「コピー」にしてモデルに「テクスチャを含め込む」に設定します
*ただしそれでもモデルのテクスチャが反映されない場合があるので、その場合はMixsomからエクスポートしたモデルに元のモデルのマテリアルをコピーして対応します - 「前方」と「上」はモデルの向きです(Mixamo正面にするため)
ちなみに、Blenderのシェーディングモード(3Dビューポートでオブジェクトをどのように表示するかを決定する機能)を「マテリアルプレビュー」に切り替えないと、マテリアルは表示されません
Mixamoですること
1:モデルをアップロード後に正面を向いていたらNextをクリック
2:マーカーを設定します(自動リギング)
*左右対称の場合「use symmetry」のチェックはそのまま
- 顎 (CHIN): 顔のアニメーションに必要な顎の位置にマーカーを配置(通常は下唇のちょうど下)
- 手首 (WRISTS): 両手首の中心にマーカーを配置
- 肘 (ELBOWS): 両肘の関節の中心点にマーカーを配置
- 膝 (KNEES): 両膝の関節の中心点にマーカーを配置
- 股 (GROIN): キャラクターの股間部分、つまり両足の付け根の中心にマーカーを配置
3:左上のタブの「Animations」をクリックして好きなアニメーションを選択
選択したアニメーションのカスタマイズ
- Arm Wave Range:キャラクターの腕の振り幅
- Overdrive:高く設定すると、アニメーションの動きが大げさに
- Character Arm-Space:腕が体からどれだけ離れるかを調整
- Trim:アニメーションの長さをトリミング
- Mirror:アニメーションが鏡像になります(本来右手が行う動きを左手が行うように)
4 :右上の「DownLoad」ボタンからエクスポート
- Format:ファイルの形式を指定
- Skin:「With Skin」を選択すると、スキン(キャラクターのテクスチャやマテリアル)を含む状態でエクスポートされます。
- Frames per Second (FPS):フレームレートが高いほど、動きが滑らかになりますが、ファイルサイズが大きくなる
- Keyframe Reduction:キーフレームの削減を行うと、アニメーションが持つキーフレームの数が減少し、アニメーションが単純化されますが、ファイルサイズが小さくなる
「none」はキーフレーム削減は行われません
Mixamoからダウンロードしたファイル(通常はFBX形式)をthree.jsで直接利用するためには、Blenderにアップロードして、glTF形式に変換します
備考:もしマテリアルが反映していない場合は、元ファイルのモデルのマテリアルをコピーして貼り付けます
- 最初にコピー先のオブジェクトを選択
- Shiftキーを押しながら、コピー元のオブジェクトを選択
- マテリアルプロパティを見ると、最後に選択されたオブジェクトに適用されたマテリアルが表示されています
- 「マテリアルを選択物にコピー」をクリックします
「ファイル」メニューから「エクスポート」を選び、「glTF 2.0 (.glb/.gltf)」を選択してエクスポートします
three.jsでブラウザに表示する
CDNを使用した必要最低限のコードです(OrbitControlsでマウス操作可能)
ダウンロードした「.glb」ファイルを読み込むます
*CDNを使用すると、追加のビルドプロセスや設定なしに直接ライブラリを読み込むことができますが、three.jsではモジュールバンドラーの使用が望ましいようです
<!DOCTYPE html>
<html lang="ja">
<head>
<title></title>
<style>
* { margin: 0;
padding: 0;
}
html,
body{
overflow: hidden;
}
.webgl{
position: fixed;
top: 0;
left: 0;
outline: none;
}
</style>
</head>
<body>
<canvas class="webgl"></canvas>
<script async src="https://unpkg.com/es-module-shims@1.3.6/dist/es-module-shims.js"
></script>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/three@0.121.1/build/three.module.js",
"three/addons/": "https://unpkg.com/three@0.121.1/examples/jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three'
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
const gltfLoader = new GLTFLoader();
const sizes = {
width: window.innerWidth,
height: window.innerHeight,
aspectRatio: window.innerWidth / window.innerHeight,
};
let model
let mixer;
gltfLoader.load("ここにダウンロードしたモデルのパス", (gltf) => {
model = gltf.scene;
model.position.y =-1
scene.add(model);
mixer = new THREE.AnimationMixer(gltf.scene);
let currentAction = mixer.clipAction(gltf.animations[0]);
currentAction.play();
});
// Canvas
const canvas = document.querySelector("canvas.webgl");
// Scene
const scene = new THREE.Scene();
//Lights
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
directionalLight.position.set(1, 1, 3);
scene.add(directionalLight);
//camera
const camera = new THREE.PerspectiveCamera(35, sizes.width / sizes.height, 0.1, 100)
camera.position.z = 5
scene.add(camera);
// Controls
const controls = new OrbitControls(camera, canvas);
controls.enableDamping = true;
const renderer = new THREE.WebGLRenderer({
canvas: canvas,
});
const clock = new THREE.Clock()
let previousTime = 0
let deltaTime
function tick() {
const elapsedTime = clock.getElapsedTime();
deltaTime = elapsedTime - previousTime;
previousTime = elapsedTime;
if (mixer) {
mixer.update(deltaTime);
}
renderer.render(scene, camera);
window.requestAnimationFrame(tick);
}
tick()
function handleResize() {
sizes.width = window.innerWidth;
sizes.height = window.innerHeight;
sizes.aspectRatio = sizes.width / sizes.height;
camera.aspect = sizes.aspectRatio;
camera.updateProjectionMatrix();
renderer.setSize(sizes.width, sizes.height);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
handleResize();
window.addEventListener("resize", handleResize);
</script>
</body>
</html>