22
17

More than 3 years have passed since last update.

THREE.IKでIK入門してみた

Last updated at Posted at 2019-12-06

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とは

Dec-06-2019 23-54-46.gif

  • Three.jsベースで提供されているIKライブラリ
  • 開発者は元GoogleでDaydream WebXR R&D担当してた人(Jordan Santell
  • クラスのドキュメントもある
  • FABRIK対応している
    • いわゆるフルボディIKのこと
    • 任意の数のボーンに対して影響を与える事ができる

FABRIKの解説は以下がわかりやすいので参考にして下さい。さすが、ゲームエンジンだと普通に実装されてますねっ!

Three.jsでは標準でCCDIKSolverクラスでIK対応してるようです。CCDIKFABRIKとまた違ったアルゴリズムで、用途によって使い分けてると思われます。

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・・・ボーンの回転角度を制限するクラス スクリーンショット 2019-12-07 1.04.00.png

最終的な親子関係は以下の感じですね(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));
}

最終的にはこんな動きになります。
Dec-07-2019 02-46-47.gif

その他

他にももっと枝分かれしたボーンを制御したり、FBXをスキニング制御するサンプルもあるので実装の参考になると思います。
Dec-07-2019 02-52-58.gif

最後に

IKを使う事でキャラクターの腕を引いて動かしたり、VRでコントローラーとキャラクターの腕の動きを同期したりできます。
本当はthree-vrmと組み合わせてVRで遊びたかったけど、時間が足りないので冬休みに遊んでみようかと。

ちなみにすでにVRMとIKを組み合わせて自前実装してる人もいて、VRM界隈のエンジニアは強いですねっ!

以上、最後まで読んで頂きありがとうございました。

2019/12/18追記

VTuber Techアドカレの方でもっと便利なIKライブラリについて書かれてました
exokitxr/avatarsでWebVRなVtuberのシステムを動かしてみた

22
17
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
17