公式Githubのコードから見る物理演算

こんにちは、がねーしゃです。

軽く自己紹介をするとゆっくり実況方面からLive2Dをいじり始めて下絵からモデリング、システムづくりまで通しでやっています。
Live2D好きが長じて今はLive2D社でプログラマーをやらせてもらっています。

アドベントカレンダーということで色々と縁の深かった物理演算のことを公式のコードを交えながら解説していきたいと思います。

Live2Dの物理演算ってどんな仕組み?

演算器

一般的に物理演算と一言でいうと衝突判定で力の連動を計算するシステムだと考える人が多いのではないでしょうか?
しかしLive2Dで物理演算と言うときは、振り子演算器によるパラメーターのリアルタイム焼き込みのことを指しています。

画像1

上の画像はCubism3~4で物理演算を使った人なら見たことのある物理モデルのパネル内容ですが、この右側にあるのが振り子演算器にあたります。
振り子の根本の位置や角度をパラメーターの状態から取得して振り子演算器を操作し、振り子のおもり部分の位置や角度を他のパラメーターとして焼き込む。この一連の流れがLive2Dの物理演算となります。
入力も出力もパラメーターを介してやり取りされるところがミソですね。

ソースコードにアクセスしよう!

物理演算の中身のソースコードは公開されています。
Cubism SDK for Unity, Cubism SDK for Native, Cubism SDK for Webと3種類の
SDKが公開されていますが、Frameworkの一機能としてPhysicsというフォルダが含まれています。この中身が物理演算のコードに当たります。
参考として一番いじりやすいUnityの物理演算部分リンクを示しておきます。

またEditorとSDKの間のやり取りはphysics3.jsonというファイルでデータがやり取りされます。
jsonファイルの中身についてはCubismSpecsをみればその書き方が確認できます。


SDKじゃないとできない指定

public void CubismPhysicsInput::InitializeGetter()
{
   switch (SourceComponent)
   {
       case CubismPhysicsSourceComponent.X:
           {
               GetNormalizedParameterValue =
                   GetInputTranslationXFromNormalizedParameterValue;
           }
           break;
       case CubismPhysicsSourceComponent.Y:
           {
               GetNormalizedParameterValue =
                   GetInputTranslationYFromNormalizedParameterValue;
           }
           break;
       case CubismPhysicsSourceComponent.Angle:
           {
               GetNormalizedParameterValue =
                   GetInputAngleFromNormalizedParameterValue;
           }
           break;
   }
}
 
 
public void CubismPhysicsOutput::InitializeGetter()
{
   switch (SourceComponent)
   {
       case CubismPhysicsSourceComponent.X:
           {
               GetScale =
                   GetOutputScaleTranslationX;

               GetValue =
                   GetOutputTranslationX;
           }
           break;
       case CubismPhysicsSourceComponent.Y:
           {
               GetScale =
                   GetOutputScaleTranslationY;

               GetValue =
                   GetOutputTranslationY;
           }
           break;
       case CubismPhysicsSourceComponent.Angle:
           {
               GetScale =
                   GetOutputScaleAngle;

               GetValue =
                   GetOutputAngle;
           }
           break;
   }
}

まず注目したいのはInput、Outputの項目です。
Editorの操作ではInputでX,Angleのみ、Outputでは出力タイプの項目自体がありませんが、処理としてX,Y,AngleがInput,Outputに用意されています。
これはEditorの実装漏れ…… というわけではなく、Editorの設定機能は良くわからない人でも直感的に設定できるようにしようという流れで、あえて設定を規制しているものです。

影響度の考え方と正しい使い方

ここはちょっと難しいのでガッツリ目にコードを読んでいきます。
コード読むのが苦手な人向けに結論だけ先に述べておくと……

画像5

・パラメータ→振り子演算器の入力はX,Y,Angleのそれぞれで、合計で100%になるように積算されていく想定になっています。エディタの設定ではきちんと規制されますがSDK読み取りの際はチェックがないので直接指定時は注意です。

画像4

・振り子演算器→パラメータの出力は、変換単体でどれだけ演算器からの値に近づけるかという割合になります。物理演算全体の実行順序を確認しながら、最終的な影響割合を逆算して指定してやる必要があります。
つまり、3つの出力を均等に影響させたい場合、1つ目を100%、2つ目を50%、3つ目を33%で指定してやる必要があります。

細かいコードを見ていきましょう。まずは入力側の該当コードを提示します。

/// weight = Input[i].Weight / CubismPhysics.MaximumWeight;
private void GetInputTranslationXFromNormalizedParameterValue(
    ref Vector2 targetTranslation,
    ref float targetAngle,
    CubismParameter parameter,
    CubismPhysicsNormalization normalization,
    float weight
)
{
   targetTranslation.x += CubismPhysicsMath.Normalize(
                           parameter,
                           normalization.Position.Minimum,
                           normalization.Position.Maximum,
                           normalization.Position.Default,
                           IsInverted
                           ) * weight;
}

X,Y,Angleを関数を変数として扱う形で場合分けしてあります。
またこの関数に入力されるweightは呼び出し前に前持って想定される総Weight(100)でjsonからの入力を割った0.0~1.0範囲でのweightを入れてあります。エディターの設定ではjsonに出力される影響度は合計100になるように規制されるます。
CubismPhysicsMath.NormalizeはパラメーターをnormalizationのMinimum~Maximumの範囲に変換して返す関数です。
つまりこの形態は影響度がそのまま影響の割合になる指定の仕方になっています。

次は出力側を見てみましょう。

private void UpdateOutputParameterValue(
    CubismParameter parameter,
    float translation, 
    CubismPhysicsOutput output)
{
   var outputScale = 1.0f;
   outputScale = output.GetScale();
   var value = translation * outputScale;

   if (value < parameter.MinimumValue)
   {
       if (value < output.ValueBelowMinimum)
       {
           output.ValueBelowMinimum = value;
       }

       value = parameter.MinimumValue;
   }
   else if (value > parameter.MaximumValue)
   {
       if (value > output.ValueExceededMaximum)
       {
           output.ValueExceededMaximum = value;
       }

       value = parameter.MaximumValue;
   }

   var weight = (output.Weight / CubismPhysics.MaximumWeight);

   if (weight >= 1.0f)
   {
       parameter.Value = value;
   } else {
       value = (parameter.Value * (1.0f - weight)) + (value * weight);
       parameter.Value = value;
   }
}

こちらのWeightは入力とは変わって単体の書き込み時に書き込み先のパラメーターとの割合を書き込む形になっています。
ここで重要になってくるのが実行順序です。
物理演算(CubismPhysicsController)が動作するタイミング、物理演算グループ(subrig)の実行される順、物理演算の出力が実行される順
この3つを正しく把握しないと出力側の影響度は100%のまま運用するのが安全でしょう。

画像3

影響するのは物理演算からパラメーターへ受け取るものだけとは限りません。モーションや他のUpdateの影響も考慮する必要があります。ただし、物理演算内部で最初に書き込まれる影響度を100%にしてしまえば物理演算だけで制御することができます。
結論でも書きましたが等倍に影響するように設定するにはそれまでにどれだけの情報が入力される見込みか把握しておく必要があります。

まとめ

今回のお話をまとめてみます。
・入力、出力の属性はエディターでは規制されていて、Unity上やjson上でフルに指定できる。
・入力の影響度はわかりやすいので直感的に影響度を操作しても大丈夫。
・出力の影響度は全体状況をよく把握してから操作する。わからないときは100%にしておく。

ほんとはもっと色々と書く予定だったのですが、割当予定の14,15日が風邪で寝込んでしまったため思ったほど書けませんでした。
追加の記事で実用よりの記事を用意できればなと思っています。

明日、18日のLive2Dアドベントカレンダーの記事はカワナミさんの「射出型」パーティクルの作り方+αです。


この記事が気に入ったらサポートをしてみませんか?