ブログ

blog

【WebGL特集】第5回:Blenderでアニメーション出力


※2018/05 加筆修正中。2015年頃アニメーション仕様について大幅な変更がされました。この記事は書き直します。書き直し予定メモ:モーフでの出力が非推奨にボーンアニメーションに変更。MeshFaceMaterialが廃止。

前回までの内容で、Blenderからオリジナルモデルを出力できるようになりました。
ここから先はスケルトンを用いたアニメーションの解説となります。さあ、ここからが本番です。

1、【モデリングから.json吐き出しまでの手順】

1、Blenderで人体モデルを用意します。
Boxの一面ポリゴンを選択して、 左にあるメッシュツールの「個々に押し出し」を繰り返して人体形成
押し出し

2、骨入れます。
オブジェクトモードに戻り>追加>アーマーチェアで、カーソルのあるところに一本骨ができる。
編集モードにしてEキー(左のアーマーチェアツール > 押し出し)を押すと1本目の先端から2本目のアーマーチェアを伸ばせる。
骨入れ

3、エンベロープの設定をします。
モデルを選択した後、シフト押しながらアーマーチェア選択。ctlPでペアレントする。自動エンベロープでウェイト設定
エンベロープ

4、リギングします。今回は足だけIKにします。
骨をオブジェクト選択 >ポーズモード > 先端の骨を選択 > ポーズ > インバースキネマティクス>IKをボーンに追加>新規エンプティーオブジェクトでIKを設定。 IKのルートを足の付け根にするためには、IKを設定した先端の骨を選択し、右メニューにあるボーンコンストレイントタブから、チェーンの長さを調整します。
リギング

5、動かします。
ポーズモードにしてアーマーチェアやIKを動かしてキーを打つ。これを繰り返してアニメーションを作る。
アニメーション

6、出力します。
出来たら、オブジェクトモードに戻し、モデルを選択。Three.jsでエキスポートします。エキスポート設定はデフォルトのままでOKですが、MorphAnimationにチェックが入っていることを確認してください。
エキスポート

2、【scriptの記述】

では、実装します。青色表記のところはオリジナルのパスに変更してね。

<html>
<body>
<script src="build/three.min.js"></script>
<script src="js/controls/TrackballControls.js"></script>
<script>
var clock = new THREE.Clock();
var morphs = [];

init();
animate();

function init() {
	//Camera
	camera = new THREE.PerspectiveCamera( 70, 640/480, 1, 2000 );
	camera.position.set( 0, 50, 500 );
	
	trackball = new THREE.TrackballControls( camera );
	
 	//Scene
	scene = new THREE.Scene();

	// Json
	var loader = new THREE.JSONLoader();
	loader.load( 'charaAni.js', function ( geometry, materials ) {
		var material = materials[ 0 ];
		material.morphTargets = true;;

		var faceMaterial = new THREE.MeshFaceMaterial( materials );
		morph = new THREE.MorphAnimMesh( geometry, faceMaterial );
		morph.duration = 1000;	// one second duration
			
		var s = 40
		morph.scale.set( s, s, s );
		morph.position.set( 0, 0, 0 );
		morph.matrixAutoUpdate = false;
		morph.updateMatrix();
		scene.add( morph );
		morphs.push( morph );
	} );
	
 	//グリッド
    planeGeometry = new THREE.PlaneGeometry( 1000, 1000, 10, 10 );
    planeMaterial = new THREE.MeshBasicMaterial( { color: 0x000000, wireframe: true } );
    planeMesh = new THREE.Mesh( planeGeometry, planeMaterial );
    planeMesh.rotation.x = 90 * 2 * Math.PI / 360; //左に角度いれるとラジアンに変換
    scene.add( planeMesh );

	
	//ライティング
	var directionalLight = new THREE.DirectionalLight( 0xffffff, 3);
	directionalLight.position.set(0,0,3);
	scene.add( directionalLight );

	//レンダラー
	renderer = new THREE.WebGLRenderer();
	renderer.setSize( 640, 480 );
	document.body.appendChild( renderer.domElement );


}


function animate() {
	requestAnimationFrame( animate );
	
	// animate morphs
	var delta = clock.getDelta();
	if ( morphs.length ) {
		for ( var i = 0; i < morphs.length; i ++ )
			morphs[ i ].updateAnimation( 600 * delta );
	}
	render();
}

function render() {
	renderer.render( scene, camera );
	trackball.update();   
}

</script>
</body>
</html>

3、【ソースの解説】

出力したモデルの読み込み部分と、animate関数の中身が前回とは違います。
どうやら、アニメーション専用の記述が必要そうです。

var clock = new THREE.Clock();
var morphs = []

グローバルとして使われる変数を先に宣言しておきます。変数clockに、three.jsの時間にかかわるオブジェクトを取得し、変数morphsは、配列として変数宣言します。二つとも後に使います。


オブジェクトを読み込む

	// Json
	var loader = new THREE.JSONLoader();
	loader.load( 'charaAni.js', function ( geometry, materials ) {
		var material = materials[ 0 ];
		material.morphTargets = true;;

実は、このwebGLはスケルトンアニメーションではなく、モーフアニメーションで動いています。
マテリアルも同様にモーフの対象になりますが、今回はマテリアルのアニメーションはしていません。
なので、マテリアルのモーフのターゲットは一つのみ。という設定をしているのが25~26行目です。
「最初のマテリアルをそのまま維持しろ!」と命令しているわけです。

        var faceMaterial = new THREE.MeshFaceMaterial( materials );
        morph = new THREE.MorphAnimMesh( geometry, faceMaterial );
        morph.duration = 1000;  // one second duration

jsonによって出力されたすべてのマテリアルを取得し、ジオメトリーとくっつけて、オブジェクトを生成しています。ただし、前回と違いMorphAnimMeshというメソッドを使っています。モーフアニメーションさせるときはこれでオブジェクトを生成します。30行目のdurationプロパティに、1秒間を、何ミリ秒で表現するかを指定します。再生スピードの調整にも使えます。

	var s = 40
	morph.scale.set( s, s, s );
	morph.position.set( 0, 0, 0 );
	morph.matrixAutoUpdate = false;
	morph.updateMatrix();
	scene.add( morph );
	morphs.push( morph )

サイズ、位置を調整しています。matrixAutoUpdateとupdateMatrixで、モーフのアップデートを自動から手動に切り替えています。「モーフの読み込みは再描画ごとにやらなくてもいいよ、一度読み込んでしまえば、あとはそれを使い続けるよ」と言っています。あとは、シーンにオブジェクトを追加し、グローバル変数のmorphsにmorphを追加してあげます。こうすることでmorphsには、配列として複数のアニメーションポーズのモデルが格納され、後のanimate関数で使用できるようになるわけです。


animate関数の中身

    // animate morphs
    var delta = clock.getDelta();
    if ( morphs.length ) {
        for ( var i = 0; i < morphs.length; i ++ )
            morphs[ i ].updateAnimation( 600 * delta );

clock.getDeltaで経過した秒数を取得します。68行目以降でモーフターゲットの数(morphsの配列)だけ、表示されるモデルを更新していく。という処理をします。どのポーズになるかは「morphs[ i ].updateAnimation( 600 * delta );」で決定され、経過時間×600ミリ秒のモーフポーズを表示します。ちょっと複雑すぎてよくわかりませんがそんな感じです。ちなみに600の数値を1200にすると倍速になります。

以上です。
次回は、各種CGソフトからBlenderへのアニメーション移植方法を解説します。

Mox-Motionに興味をお持ちの方はお気軽にご連絡ください!

採用希望の方は・・・