この記事はMaya Advent Calender 2019の24日目の記事です。
おはこんばんちわ。
手近にコーヒーがないと落ち着きません、山本ほっさんです。
今回はAutodesk Maya®において、伸縮するSplineIKリグの事例を紹介したいと思います。
SplineIKを使用したリグは、モンスターのしっぽや触手、鞭などの武器によく使われます。
特に伸縮する機構は、魅力的な演出やキャラクターなどの挙動には便利な要素で、弊社スタジオでもこのリグに対する対応要望は毎プロジェクト発生している様な印象です。
しかし、SplineIKの伸縮機能は実は一筋縄にはいかない、少しややこしい実装を伴います。
結果を先に
では、ここに弊社での実装例を紹介します。
①骨に沿ってbezierカーブを作成し、ジョイントに適応するSplineIKのカーブに使用する。
②各ジョイントの長さに応じた割合とpointOnCurveInfoノードで、カーブ上の座標を取得する。
③各座標間の長さがどれくらい変化するかを計算。
④計算結果を対応する各ジョイントに代入する。
というようなことをしました。
しかし、なぜこのような手法を採択したのでしょうか。
普通にSplineIKを設定して、Spline curveの長さからスケール値を算出すれば済みそうな気がします。
従来の方法では、SplineIKの伸縮とリグがずれる。
伸縮性SplineIKリグのセットアップとして、よく見る方法なのが…
伸縮倍率 = 現在のSpline curveの長さ ÷ 元のSpline curveの長さ
で、「curveの長さ変化から倍率を求める」方法でスケール値を計算し、これを骨のスケール値に戻してあげる方法です。
ノードグラフで組むと
こんな感じ。
これを実際に動かしてみると…
うん。上手くいってそう。
でも強めに曲げると…
アイエー!?
この方法では、リグの末端と骨の最終結果が必ずしも一致しません。
なぜならば、必ずしもカーブの長さ=骨ツリーの全長にならないからです。
図で書くとこうですね。
ちょうど骨とカーブが弧と弦の関係になっているわけです。
つまり、このように強めに曲げると…
弧と弦の差分が強くなって、最終結果に表れるわけです。
しかし、上記のリグの良いところは、シンプルで分かりやすい所だと思います。
なので、簡易に組んだり精度が必要でない場合などには便利です。(私もよく採択しています。
今回は、このSplineIKリグでのスケール精度をあげる方法について、追及してみたいと思います。
正しいスケールは、「弧」ではなく「弦」の長さ
上記の考察から、スケール値はカーブの長さではなく、「弦」にあたる部分の長さから算出すると良さそうです。
「弦」の長さを算出するにあたって、まずはカーブ上のジョイントが位置する座標を取得したいところ。
ここでは、「pointOnCurveInfo」ノードを使って、その座標を取得してみます。
■ pointOnCurveInfoノードのParameterアトリビュートの挙動
pointOnCurveInfoノードは、主にinputCurveアトリビュートにカーブの
・Local アトリビュート
または
・World spaceアトリビュート
…を接続して使用します。
その後、Parameterアトリビュートを操作することで、カーブ上の座標が取得できます。
しかし、Parameterアトリビュートとは?
公式ドキュメントには
"The parameter value on curve to investigateThe parameter value on curve to investigate"
(google翻訳:調査する曲線上のパラメーター値)
https://help.autodesk.com/cloudhelp/2020/JPN/Maya-Tech-Docs/Nodes/pointOnCurveInfo.html
とあります。 …なんのこっちゃ。
試しにEP Curveを作って接続してみると、こんな挙動になります。
なるほど。Edit pointの間を移動するんですね。
例えば、Edit pointがA~Cまであったとして。
・0.0~1.0でA~Bまで移動
・1.0~2.0でB~Cまで移動
…という風に、
各edit point間のカーブ上のポイント位置を割合で示すアトリビュートのようです。
この特性を使って、カーブ全体から見た各ポイント間のカーブの長さから割合を計算して、カーブ上の各ポイントを取得できそうです。
<注意>Parameterはカーブの密度も影響します。
下記のようにbezierカーブ等作成し、cvポイントを編集してカーブに過密粗密を作ったうえで、上記同様にpointOnCurveInfoのParameterを操作してみると…
この様な挙動をとりました。
最初のうちは速度遅めで、あとになってスピードが上がっているのがわかると思います。
ここから、Parameterは単にEdit point間の割合を見るだけでなく、カーブの密度も影響することがわかります。
この点は、リグを実装するうえでの挙動に加味しなければなりません。
実際にやってみよう
では今までの考察と調査から、実際にリグを組んでみましょう。
■ SplineIKを設定する
まずは、ジョイントツリーを用意します。(Primary AxisはX軸にしました。)
次にカーブを作成します。
ここでは始点と終点だけ指定してでSplineカーブを作りたいので、bezierカーブをジョイント位置に合わせて作成します。
※注意※
前述の通り、pointOnCurveInfoのParameterアトリビュートはカーブの密度も影響するので、初期の段階では均一な密度に設定します。
そのため、bezierのハンドルにあたるcontrol pointを均一に配置するようにします。
bezierカーブを作成したら、control pointをtranslate操作できるよう、SelectメニューからCluster Curveを適応します。
続いて、SplineIK Toolをアクティブにし、
始点ジョイント → 末端ジョイント → bezierカーブ の順番で選択して、SplineIKを作成します。
とりあえず、これで伸縮しないSplineIKリグができました。
■ pointOnCurveInfoノードを使って、bezierカーブ上のジョイント座標を決める
次に、伸縮に必要な「弦の長さ」を計算できる仕組みを実装しましょう。
まず、ジョイントの全長と各ジョイントまでのジョイント長をplusMinusAverageノードを使って算出します。
ジョイントはPrimary AxisをX軸に指定して一直線に作成しているので、translateXの値を加算してゆけば、各ジョイントまでのジョイント長さが算出できます。
算出された各ジョイントまでのジョイント長を全長で割って、「そのジョイントまでの割合」を計算します。
floatMathノードを作成し、float Aにジョイント長、float Bにジョイント全長を接続して、割り算させます。
※floatMathの計算方式はDivideを選択します。
さて、いよいよpointOnCurveInfoノードを使います。
ジョイントの個数分pointOnCurveInfoノードを作成し、各々をbezierカーブのLocalアトリビュートをInput Curveに接続していきます。
次に、先ほど算出したジョイント位置の割合を、pointOnCurveInfoノードのParameterに接続していきます。
※最初のジョイントは割合0%なので、接続はありません。
算出されたポイント位置をわかりやすく可視化するため、ジョイントの個数分ロケーターを作成し、
pointOnCurveInfoのResultsからpositionアトリビュートをロケーターのtranslateに接続します。
すると、現時点での見た目はこんな感じになります。
ジョイントの位置にロケーターが来ていますね。
でもまだジョイントはスケーラブルではありません。 そりゃそうか
※このロケーターにpositionを接続するフローは実際には不要なので、次のノードへの接続に変えてしまっても問題ありません。
スケールの算出
上記プロセスで生成したロケーターの位置から各ジョイントのスケール値を算出してみましょう。
各ロケーターをdistanceBetweenノードに接続し、各ロケーターの間隙を計算します。
ここで求まった数値の初期値が、スケール1.0時点での長さ、つまり規定値となります。
スケールを計算するために、floatMathノードを作成して、いったんFloat A,Float B 両アトリビュートにDistanceアトリビュートを接続します。※floatMathノードの計算方式はDivideに設定。
で、分母となるFloat Bの接続を切ります。
そうすることで、接続時にFloat Bに入力された数値が残って、現在の算出値を定数として入力することが可能です。
※コピペや手入力ですと、その時のMayaの表示状態、例えば小数点第三位以下が切り捨てられた状態で入力されるので、上記方法のほうが精密かと思います。
そして、ここで算出されたスケール値を接続すると…
伸びる!!
強めに曲げても…
ブレない!!
ハラショー!
まとめ
今回は、伸縮性SplineIKリグの事例の一つを紹介いたしました。
これはあくまで一例で、他にもいろいろな伸縮性SplineIKリグの実装例があると思います。
例えば、別途スケールアトリビュートを設けて、手動でスケール値をスライダーコントロールなどで代入する方法も良いと思います。
いろいろな方策を試行錯誤してみるのも、面白いのではないでしょうか。
いやーリグって、本当に面白いものですね。
それではまた、お会いしましょう。(おじぎ
さて、いよいよ明日はクリスマス!
@redglasses67さんの「OpenMaya PythonAPI 2.0 の逆引きメモ」が公開予定ですね。
楽しみにしております!