物理演算(揺れもの)の仕組みと、扱うときの割り切り
髪・スカート・胸の揺れは、モーションではなく物理演算が毎フレーム作っています。ここを「モーションの一部」と思って扱うと、姿勢の指定と綱引きになったり、シークのたびに髪が暴れたりします。何が計算で決まっていて、何が決まらないのかを整理します。
剛体には3つの種別がある
| 種別 | 意味 | 何が動かすか |
|---|---|---|
| 0 | ボーン追従 | ボーン。 物理は動かさない(当たり判定としてだけ働く) |
| 1 | 物理演算 | 物理。 ボーンは結果を受け取る |
| 2 | 物理演算 + ボーン位置合わせ | 物理だが、ボーンの位置に引き戻される |
実モデルでの内訳です。ボーン追従(0)は体幹や腕といった「モーションが動かす側」に付いていて、揺れものは1か2になっています。
| モデル | ボーン | 剛体 | 内訳 | ジョイント |
|---|---|---|---|---|
| アリシア | 150本 | 79個 | 0が26 / 2が53 | 53個 |
| ad_org8 | 310本 | 141個 | 0が27 / 1が58 / 2が56 | 236個 |
ジョイントは剛体と剛体を繋ぐ拘束です。髪の房のように数珠つなぎの構造は、剛体の数だけジョイントが要ります。ad_org8 は剛体141個に対してジョイント236個で、剛体より多いのは1つの剛体が複数の相手に繋がっているためです。
揺れるボーンの姿勢をVMDに書かない
種別0以外の剛体が付いているボーンは、物理が毎フレーム上書きします。そこへVMDでも姿勢を指定すると、両方が同じボーンを引っ張り合います。 結果は「揺れなくなる」か「不自然に硬くなる」で、どちらもモーションの不具合に見えます。
手でポーズを作って書き出すときは、種別0以外の剛体が付いているボーンを除外してください。実測では、アリシアで150本中53本、ad_org8 で310本中114本が除外対象になります(詳しくは手付けモーションの記事)。
シークすると揺れが再現できない
物理演算は現在の状態から時間を進めて積分する方式です。つまり「30秒目の髪の位置」は、0秒から30秒までの経過に依存します。シークバーで30秒目に飛んでも、そこまでの経過が無いので正しい状態を作れません。
実際に起きることは「シーク直後の数フレームだけ髪とスカートが不自然になり、すぐ落ち着く」です。正確に再現するには0秒から目的の時刻まで再シミュレーションする必要があり、30秒なら30秒ぶんの計算が要ります。このサイトはそこまでやっていません(飛んだ直後が少し乱れるのを受け入れています)。
サムネイルを撮る・静止画を書き出すときは、シークの直後に撮らないこと。 このサイトはモーションの40%までシークしてから900ミリ秒描いてから撮るようにしています。すぐ撮ると、髪がねじれた瞬間が保存されます。
重力は変えられる(既定は -9.8 × 10)
重力の既定値は -98(単位はMMDの1ユニット/秒²)です。MMDのスケールは1ユニット ≒ 8cm なので、-9.8 ではなくその10倍がちょうど見た目に合う、という経験的な値です。three.jsのMMDPhysicsの既定値もこれです。
弱めると髪がゆっくり大きく揺れ、強めるとすぐ落ちます。0にすると無重力になり、髪とスカートが浮きます。モーションそのものは変わらないので、見た目の調整として使えます。
体型を変えたときの剛体の扱い
モデルの体型を変えるなら、剛体も一緒に動かさないと当たり判定だけ元の位置に残ります。 位置はボーンの移動に合わせて動かせますが、大きさは素直にいきません。
- 剛体は自分の回転を持っています。 そのため「X方向に1.5倍」のように軸ごとに拡大すると、回転している剛体では形が合いません(斜めのカプセルが潰れた形になる)。
- このサイトは体積比から等倍の倍率を1つ決めて大きさに掛けています。極端な値では、髪やスカートの揺れ方が元と変わります。
- 材質(マテリアル)単位の拡大・縮小では剛体は動きません。 スカートの材質だけ大きくすると、揺れの当たり判定は元の大きさのまま残ります。ボーン経由で体型を変えたときは剛体も追従します。
ブラウザで動かすときに要るもの
物理演算に使う Ammo.js(Bullet物理エンジンのWebAssembly版)は three.js に含まれていません。 別途自分でホストして読み込む必要があります。
MMDPhysicsはグローバルのwindow.Ammoを直接参照します。 モジュールとしてimportするのではなく、<script>タグで読み込んでからAmmo()の解決を待つ形になります。- 重力を渡す位置に注意。
add()に渡すパラメータの直下にgravityを置きます(入れ子のオブジェクトに入れても読まれず、黙って既定値のままになります)。 - three.js の型定義は
gravityを数値と宣言していますが、実装はVector3を前提にしています(型定義側の誤り)。 - 物理はメッシュのスケール適用より前(MMDのネイティブ単位)で計算されます。 モデルを縮小してもしなくても物理の挙動は変わりません(スケールの記事)。
WebGLのコンテキストは実際に失われます(タブを裏に回す・メモリ不足でOSが破棄する)。復帰したときに経過時間をそのまま足すと、物理が一気に進んで髪が吹き飛びます。復帰時は経過時間を捨てるのが簡単で確実です。