three.js 用にボーンアニメを持つ 3D モデルを書き出すために

Posted :

three.js は skeletal animation (ボーンやリグ)を持った 3D モデルを読み込み、再生することができます。3D モデルを three.js 用に書き出すため一番いいツールは Blender です。

Blender 上でボーンとアニメーションをつけていきます。Dope Sheet を使って、アニメーションを複数持たせると、three.js 上でも異なる複数のアニメーションとして利用することができます。

なお、Blender 上のボーンをつけて書きだすまでの操作は動画にしているので合わせて参考にどうぞ。

1. アニメーションを Bake する

FK のみでアニメーションをさせている場合には、この設定は必要ありません。

three.js 上では基本的に FK のみ対応しています。IK や物理演算による自動計算には対応していません。そのため、FK 以外で動かしているボーンがある場合は、書き出す直前にそれらをすべて FK として焼き付ける必要があります。

すべてを FK として焼き付けるためには、Bake Action を利用します。3D ビュー上でスペースキーを押し、”Bake Action” を検索し実行します。Bake Action の設定は、Frame Step を 1 にし、Visual Keying と Clear Constraints のみ、チェックをつけ、Bake Data の項目は、Pose を選択します。

すると、IK 以外のボーンはすべて IK として焼き付けられます。

ただし、Frame Step を 1 で Bake すると、キーフレームが多く、JSON として出力する際に、ファイル容量が上がる原因となってしまいます。必要に応じて間引くといいでしょう。間引くには、再度 Bake Action を行い、2 回目の Bake Action では Frame Step を 3 などすることで、 1/3 にキーフレームを減らすことができます。

(物理演算を利用している場合には、1度目の Bake Action は 必ず Frame Step を 1 にして行いましょう )

2. Rest Pose を登録する

書きだす前の準備として、もう一つ。Rest Pose を登録します。これをしないと、three.js に読み込んだ後、動いているけど奇妙な動きをしてしまいます。(意図しない起点をもとに、メッシュの変形が行われるため)

Rest Pose を登録するためには、

  1. Pose Mode に移行
  2. Pose から、Clear Transforms の All を実行し、一度意図する Rest Pose に戻す
  3. Object Mode に移行
  4. Mesh 全体を選択し、CTRL + A で Apply のメニューを出し、 Location を選択する。Rotation と Scale についても同様に行う
  5. 全てのボーンを選択し、先程と同様の手順で、CTRL + A で Apply のメニューを出し、 Location を選択する。Rotation と Scale についても同様に行う

で完了します。とても重要なのでお忘れなく。

3. マルチバイト文字をアスキーにしておく

メッシュ名、マテリアル名、アニメーション名などに日本語などが使われていると、うまく書き出すことができません。事前にそれらをすべてアスキー文字にしておいてください。

4. JSON に書き出す

three.js 書き出し用のプラグインを有効にしている状態で File > Export > Three.js(.js) を実行し、書き出し設定を開きます。

Skeltal Animation にチェックを入れます。Copy Textures にチェックを入れると、テクスチャー画像を画像ファイルとして同時に書き出すことができますが、既に同名のファイルが存在している場合、上書きできずエラーになってしまいますのでご注意を。

5. three.js で読み込む

このコードを参考にどうぞ。(r64 現在のコードです)

var loader = new THREE.JSONLoader();
loader.load( 'filename.js', function( geo, mats ) {

  mats.forEach(function ( mat ) {
    if ( isWebGLVersionLessThan1 ) {
      // IE11 does not render if THREE.DoubleSide (gl_frontFacing) is applyed
      mat.side = THREE.DoubleSide;
    }
    mat.skinning = true;
  } );

  mesh = new THREE.SkinnedMesh(
    geo,
    new THREE.MeshFaceMaterial( mats )
  );

  mesh.traverse( function ( child ) {
    child.castShadow = true;
    child.receiveShadow = false;
  } );

  THREE.AnimationHandler.add( mesh.geometry.animations[ 0 ] );
  THREE.AnimationHandler.add( mesh.geometry.animations[ 1 ] );

  walk = new THREE.Animation(
    mesh,
    mesh.geometry.animations[ 0 ].name,
    THREE.AnimationHandler.CATMULLROM
  );

  run = new THREE.Animation(
    mesh,
    mesh.geometry.animations[ 1 ].name,
    THREE.AnimationHandler.CATMULLROM
  );

  scene.add( mesh );
} );

あわせて、レンダリングの前に THREE.AnimationHandler.update を実行しておきます。

( function renderLoop (){
  requestAnimationFrame( renderLoop );

  // clock is a instance of THREE.Clock() 
  var delta = clock.getDelta();
  THREE.AnimationHandler.update( delta );
  renderer.render( scene, camera );
} )();

myAnimation というアニメーションインスタンスを再生する場合は

otherAnimation.stop();
myAnimation.play();

のように、実行中のアニメーションを止めてから再生を始めます。

myAnimation を停止する場合には、

myAnimation.stop();

をします。