Three.js Advent Calendar 2019の6日目の記事です!
今回はThree.js Meetup Tokyo #0で話せなかったIKライブラリ**「THREE.IK」**の話です。
開発環境
Three.js: v0.111.0
THREE.IK: v0.1.0
THREE.IKとは
- Three.jsベースで提供されているIKライブラリ
- IKの説明はShade3D 第9回「FKとIK」が分かりやすいので見てみて下さい
- 開発者は元GoogleでDaydream WebXR R&D担当してた人(Jordan Santell)
- クラスのドキュメントもある
- FABRIK対応している
- いわゆるフルボディIKのこと
- 任意の数のボーンに対して影響を与える事ができる
FABRIKの解説は以下がわかりやすいので参考にして下さい。さすが、ゲームエンジンだと普通に実装されてますねっ!
Three.jsでは標準でCCDIKSolverクラスでIK対応してるようです。CCDIKはFABRIKとまた違ったアルゴリズムで、用途によって使い分けてると思われます。
FABRIK は一般的に、CCD よりも少ない反復回数でターゲットに到達することができますが、チェーンに回転の制約がかけられている場合は、1 回の反復処理が CCD よりも遅くなります。
Unity Blogから上記の気になる一文を見つけましたが、今回はFABRIK対応のTHREE.IKの方を解説していきます。
ちなみに他のFABRIK JSライブラリには、物理演算ライブラリのOimo.js開発者のfullikもあります(こちらはドキュメントがない)
IKの専門用語
いくつかIKの専門用語があります。こちらを把握しておくとIKのコード理解がはかどるかと思います。
- エフェクター:実際に動かすボーンなどジョイント情報
- ソルバー:動く範囲の影響を受けるボーン。THREE.IKではボーンの状態(回転・位置)を更新する
- コンストレイント:ボーンの回転に制約を加えるもの。THREE.IKではボーンの回転角度を制限します
コンストレイントは分かりやすいリンクがあったので貼っておきます。
ソルバーとコンストレイントは後で関数名に登場するので、心の片隅に置いてもらえればと。
THREE.IKのクラス達
THREE.IKのクラスは以下5つです。
-
THREE.IK
・・・IKを実現する親クラス -
THREE.IKChain
・・・複数のジョイントを繋ぐクラス -
THREE.IKJoint
・・・ボーンを繋ぐ関節クラス -
THREE.IKHelper
・・・IKを視覚化するヘルパークラス -
THREE.IKBallCXonstraint
・・・ボーンの回転角度を制限するクラス
最終的な親子関係は以下の感じですね(THREE.Bone
はThree.js標準のクラス)
THREE.IK
└──THREE.IKChain
└──THREE.IKJoint
└──THREE.Bone
サンプルコードと解説
ポイントだけ解説します。全コードはgithubをご覧ください(three-ik-practice)
まずはTHREE.IK
クラスをimportします。
import { IK, IKChain, IKJoint, IKBallConstraint, IKHelper } from 'three-ik';
つぎにIKの向き先用のMeshを生成します。後でこのMeshを動かしてIKの向きを動的に動かします。
const movingTarget = new THREE.Mesh(
new THREE.SphereBufferGeometry(0.1),
new THREE.MeshBasicMaterial({ color: 0xff0000 })
);
movingTarget.position.z = 2;
const pivot = new THREE.Object3D();
pivot.add(movingTarget);
this.scene.add(pivot);
つぎは親となるIKを作ります。isIKはtrueデフォルトです(IKHelperでエラーになるため)
ik.add()
の引数の中身はIKChain
が渡ります。
const ik = new IK();
ik.isIK = true;
ik.add(createBonesAndChain(movingTarget));
ik.add()
に渡していたcreateBonesAndChain()
の中身です。最終的にはIKChain
を返します。
createBonesAndChain(movingTarget: THREE.Mesh): IKChain {
const chain = new IKChain();
// Constraintに90と渡した事で円形で90°以上回転しなくなる
const constraints = [new IKBallConstraint(90)];
const bones: THREE.Bone[] = [];
// Max6つのBoneを生成する
for (let i = 0; i < 7; i++) {
const bone = new THREE.Bone();
// 最初のBone以外は位置をずらして配置
bone.position.y = i === 0 ? 0 : 0.5;
if (bones[i - 1]) {
bones[i - 1].add(bone);
}
bones.push(bone);
// IKの向く方向となるMeshは最後のBoneのみセットする
const target = i === 7 - 1 ? movingTarget : null;
// IKJointは関節なので複数addすれば指の動きも可能
chain.add(new IKJoint(bone, { constraints }), { target });
}
return chain;
}
最後にマイフレームrenderする処理でik.solve()
するとIKが更新されます。
render(ik:IK, pivot: THREE.Object3D) {
// IKの向き先を回転させる
pivot.rotation.x += 0.03;
pivot.rotation.y += 0.03;
pivot.rotation.z += 0.03;
// IKの状態を更新する
ik.solve();
this.renderer.render(this.scene, this.camera);
requestAnimationFrame(() => this.render(ik, pivot));
}
その他
他にももっと枝分かれしたボーンを制御したり、FBXをスキニング制御するサンプルもあるので実装の参考になると思います。
最後に
IKを使う事でキャラクターの腕を引いて動かしたり、VRでコントローラーとキャラクターの腕の動きを同期したりできます。
本当はthree-vrmと組み合わせてVRで遊びたかったけど、時間が足りないので冬休みに遊んでみようかと。
ちなみにすでにVRMとIKを組み合わせて自前実装してる人もいて、VRM界隈のエンジニアは強いですねっ!
以上、最後まで読んで頂きありがとうございました。
2019/12/18追記
VTuber Techアドカレの方でもっと便利なIKライブラリについて書かれてました
exokitxr/avatarsでWebVRなVtuberのシステムを動かしてみた