チュートリアル / Mayaにおける魅惑のプロップリグ~モジュラーリギングシステムの基本と応用~
第8回:リグモジュールの開発とセットアップ その7〜自動車のアップデート〜
- GAZEN
- Maya
- アニメ
- キャラクター・リグ
- ゲーム
- コラム
- スクリプト・API
- チュートリアル
- 学生・初心者
- 映画・TV
今回の題材
こんにちは、久保です。
前回に引き続き・・・・ すみません 例によって前回から間があいてしまいました。
前回は日本ではよく見かける感じの車を、必要最低限でセットアップしました。
今回はそれをもう少し使いやすくなるように調整していこうと思います。
アップデート案
さて、前回は基本的な機能を付けたのでここからどのようにアップデートするかを考えます。
・車体傾きコントローラー 角度リミット追加
車体上部をworld向きに固定できるのですが、
車体全体を傾けた際に上部が傾きすぎて廃車の危機が訪れるので
傾きの角度にリミットを設けようと思います。
・タイヤの自動回転
現段階でもタイヤを個別に回転(走行)できるのですが、
面倒なので 回転ONにしたらずっと回り続けるセットアップを追加しようと思います。
・接地面 pivotコントローラー
全体移動・回転コントローラーは基本構造にありますが、
車体の方向を変える際に若干手間がかかるので
支点位置が変更可能な回転コントローラーを追加しようと思います。
・全体スクワッシュ
ちょっと車体全体に動きを付けられるように、
宝箱で追加したような全体をスクワッシュするコントローラーを追加しようと思います。
車体傾きコントローラー 角度リミット追加
傾きにリミットを設けるには、
・リミット値を設定するパラメーターをコントローラーへ設置する
・リミット値パラメーターをギミック内のノードにコネクトする
の2点を行います。
1つ目 リミット値を設定するパラメーターをコントローラーへ設置する
リミットを設けたいですが、傾く方向は 前・後・左・右 の4方向あります。
それぞれに対してリミットを制御できるようにするか、前後・左右でそれぞれセットにして2方向として制御するかが考えられます。
4方向すべてを1つの数値で制御する方法も考えられますが、車体が長方形なので使い勝手が悪そうです。
支点のオフセットが前後左右4方向制御なので、それに揃えて4方向個別制御にします。
2つ目 リミット値パラメーターをギミック内のノードにコネクトする
さて、実際に傾きをリミット値で止める方法を以前に作った図をみつつ考えます。
moving pivot で回転を止めるのが良さそうです。
orientConstraintからmoving pivotに対するコネクションの間に clampノードなどを差し込んでも良さそうですが、
transformノードに標準でついている minRotLimit maxRotLimit で制御することにします。
いざ改修しようと思いリグモジュールを見直していましたが、tiltCtrlモジュールを作成した際に
trans_jnt と rotPivot あたりの軸の向きを固定にしてしまい(例えば -Y = front、 -Z = left)、 軸の定義に合わせてこれらのノードの向きを調整すればシンプルな処理に出来そうです。
と書いておりました。
が、内容を見てると全然そんな感じになっていないので併せて調整しようと思います。
minRotLimitEnable maxRotLimitEnable をonにし忘れて、あれー? ってしばらくなりましたが、無事実装できました。
タイヤの自動回転
次にタイヤの回転制御を追加します。
全体移動に対してタイヤを自動回転させるセットアップも存在しますが、そんなハイレベルなものではなく、スウィッチをONにしたらタイヤが回り続けるだけのセットアップを目指します。
制御するためのパラメーターとしては
・回転のオン・オフ
・回転速度
が最低限あれば良いかなと思います。
"回転速度"の定義をどうするかですが、
例えば
・1秒に N 回転する
・時速 N km 想定
など 考えられますが今回は厳密さは求めていないので、 1秒にN回転 = 回転速度 ということにします。
ちなみに作品の方向性によっては速度を考慮して真面目に回転させると、
止まってるように見えてしまったり 逆回転してるようにみえたり 遅く見えてしまったり と
見た目重視なリテイクが発生することもありますね。
さて既存のリグモジュールでなにか使いまわせそうなものはないかなと眺めてみると、
frameTranslateLoopSwitch というのがありました。
無限廊下で作ったやつですかね。
これをベースに改造して frameRotateLoopSwitch を作成します。
さてexpressionベースで実装しました。
frameTranslateLoopSwitch のアトリビュートなどを一部踏襲しています。
rigModules/frameRotateLoopSwitch.py
#rigModules/frameRotateLoopSwitch.py
_ATTRS = [
"ctrlNode", # 制御パラメーターを設置するコントローラー名
"useParamPrefix",
"attrName", # アトリビュート名 <prefix> + <attrName> + "_rotOn/_frameOffset/_speed"
"targets", # 制御対象ノード
"useRotateX", # 制御対象回転軸
"useRotateY", # 制御対象回転軸
"useRotateZ" # 制御対象回転軸
]
_ATTR_DICT = {
"ctrlNode": {"type":"string", "multi":False, "default":""},
"attrName": {"type":"string", "multi":False, "default":""},
"targets": {"type":"message","default":True,"multi":True},
"useRotateX": {"type":"bool","multi":False, "default":True},
"useRotateY": {"type":"bool","multi":False, "default":False},
"useRotateZ": {"type":"bool","multi":False, "default":False},
}
回転用のjoint と モジュールを追加し、ビルドしました。
早速回転させてみます。
回ってます。が、判りませんね。
こうすると回ってるのが確認できますね。
止まって見える原因としては、
回転の制御が 1秒(今回の設定は24f) で 360 * N回転 としているので、
1fあたりに動く角度は ( 360 * N ) / 24 となります。
そして、このタイヤモデルの分割数は 24 なので・・・・
1fあたりの回転が、ちょうど分割1つ分回転になっているので完全に止まって見えてしまうわけですね。
接地面 pivotコントローラー
車体の向き変え用のコントローラーを追加します。
回転の支点となる位置を移動でき、回転させる。
移動値を支点移動制御に割り振り、回転値はそのまま回転の制御にあてたいと思います。
構造を組むだけであればシンプルに作れそうです。
pivotA
コントローラー から 別階層のノードの rotatePivot に対して translate をコネクトしたタイプ
pivotB
コントローラー の translate に対して反転移動するノードを挟んで疑似的にpivotを移動させるタイプ
ただし検証していると1つ不満が出てきます。
支点を移動し、キーフレームを打ち、回転させて、また支点を移動させようとすると
親空間が動いてしまってるので色々動いてしまいます。
欲を言えば、支点を動かす際にとまっていて欲しいのですが・・・
構造でこれを実現するのは少々難易度が高いですね。
ひとまずこのどちらかの構造で組むとして、別途スクリプトなどで補助できたらよさそうです。
それを視野に入れると、
例えば
pivotを動かせるコントローラーとは別に ジョイントを直に動かせるコントローラーを設置しておき、
pivotコントローラーを動かす前にそのコントローラーをworldに固定し、
pivotコントローラーを動かし、コントローラーをlocalに戻す。
という流れでしょうか。
これらを加味して構造を図に起こし、slideRotPivot を実装しました。
rigModules/slideRotPivotCtrl.py
_ATTRS = [
"joints", # コントローラーを設置するjoint
"useTranslateX", # 支点移動コントローラーの translate軸
"useTranslateY",
"useTranslateZ",
"ctrlColor",
"mirrorSetupSetting"
]
_ATTR_DICT = {
"joints": {"type":"message", "multi":True, "default":""},
"useTranslateX": {"type":"bool", "multi":False, "default":True},
"useTranslateY": {"type":"bool", "multi":False, "default":True},
"useTranslateZ": {"type":"bool", "multi":False, "default":True},
"mirrorSetupSetting":{"type":"compound","ch":["mirrorSetup","mirrorCtrlColor"],"multi":False},
"mirrorSetup": {"type":"bool", "multi":False, "default":False},
"mirrorCtrlColor": {"type":"enum","enumList": rigSettings.COLORS_LIST,"default":"yellow"},
}
支点移動は body_jnt(車体全体傾き) よりも上位に配置しました。
切り替え用スクリプト
では切り替えを補助するためのスクリプトを作成してみます。
スクリプトは2種用意します。
1つ目は 編集モードをONにするためのスクリプトです。
処理としては
・オフセットコントローラーの ワールド空間における位置・回転 を取得
・ピボットコントローラーとオフセットコントローラーに現在のフレームの1つ前でキーフレームを設定
・コントローラーのモードを editに変更
・オフセットコントローラーに 取得しておいた 位置・回転を反映
・ピボットコントローラーとオフセットコントローラーに現在のフレームでキーフレームを設定
これをスクリプトにするとこんな感じです。
## edit mode ON
namespace = ""
switchCtrl = namespace + ":bodyPivot_ctrl"
switchAttr = "editPivotMode"
pinCtrl = namespace +":bodyPivotOffset_ctrl"
##get curWorldPosition
curPosition = cmds.xform(pinCtrl,q=True,ws=True,t=True)
curRotation = cmds.xform(pinCtrl,q=True,ws=True,ro=True)
curFrame = cmds.currentTime(query=True)
##set keyframe
cmds.setKeyframe(switchCtrl, time = curFrame - 1)
cmds.setKeyframe(pinCtrl, time = curFrame - 1)
##change switch
cmds.setAttr(switchCtrl + "." +switchAttr, 1)
##set worldPosition
cmds.xform(pinCtrl,ws=True, t = curPosition)
cmds.xform(pinCtrl,ws=True, ro = curRotation)
##set keyframe
cmds.setKeyframe(switchCtrl, time = curFrame)
cmds.setKeyframe(pinCtrl, time = curFrame)
2つ目は編集モードをOFFにするためのスクリプトです。
処理としては1つめとほぼ同じです。
・オフセットコントローラーの ワールド空間における位置・回転 を取得
・コントローラーのモードを pivot に変更
・オフセットコントローラーに 取得しておいた 位置・回転を反映
・ピボットコントローラーとオフセットコントローラーに現在のフレームでキーフレームを設定
## edit mode OFF
namespace = ""
switchCtrl = namespace + ":bodyPivot_ctrl"
switchAttr = "editPivotMode"
pinCtrl = namespace +":bodyPivotOffset_ctrl"
##get curWorldPosition
curPosition = cmds.xform(pinCtrl,q=True,ws=True,t=True)
curRotation = cmds.xform(pinCtrl,q=True,ws=True,ro=True)
curFrame = cmds.currentTime(query=True)
##change switch
cmds.setAttr(switchCtrl + "." +switchAttr, 0)
##set worldPosition
cmds.xform(pinCtrl,ws=True, t = curPosition)
cmds.xform(pinCtrl,ws=True, ro = curRotation)
##set keyframe
cmds.setKeyframe(switchCtrl, time = curFrame)
cmds.setKeyframe(pinCtrl, time = curFrame)
実際に使ってみるとこのようになります。
ネームスペースの設定をスクリプトエディタで変えるのが面倒になりそうなので、
ピボットコントローラーを選択して、そこから名前でオフセットコントローラーを指定しに行く方が便利そうですね。
全体スクワッシュ
最後に全体スクワッシュを追加したいと思います。
スクワッシュのセットアップ自体は latticeDeformer と lengthToScaleSetupで実装できます。
ですがセットアップが複雑になってきてしまっているので、少しやり方を変えようと思います。
レイヤーセットアップの追加
いわゆるレイヤーアプローチ(呼び方は色々)のセットアップを追加します。
今までのセットアップによる変形を追加したレイヤーに移して、worldでの変形をなるべくlatticeだけにします。
レイヤーセットアップはやり方が色々ありますが、今回私がよく使う手法を紹介します。
メッシュを複製し、最終的なメッシュの上流にシェイプをコネクトします。
特定のjointより親の階層の動きをカットするため、skeletonの特定の階層から下を複製しworldのskeletonからコネクトします。
これによって 特定のjointを親空間とした限定的なセットアップ空間を定義し、そこでセットアップを行えるようにします。
ただ、これが概念としてなかなか理解しづらいので簡単なサンプルデータと共にまとめてみます。
sampleScenes/layerSetupSample_v001.ma
arm_jnt で armMesh_geo(最終的なメッシュ)をバインドすることで、全体的な移動を制御します。
localarm_jnt/localElbow_jntでlocalArmMesh_geo をバインドし、曲がるようにします。
outMesh -> inMesh でコネクトすることで最終的には 全体移動と曲がる動きが加算されます。
メッシュをコネクトするにあたっての注意点1
メッシュにvtxの移動履歴が残っていた場合、
それを複製して outMesh to inMesh でコネクトすると
移動履歴が加算されてしまうので、最終的なメッシュの形状が変わってしまいます。
そして、コネクトを手動で行った場合 undoをしてもこれはもとに戻らないので注意してください。
vtxの移動履歴に関しては、
transform Component で移動値0にした後ヒストリ削除するか
latticeなどのデフォーマーを掛けてヒストリを削除するなどしてリセットできます。
メッシュをコネクトするにあたっての注意点2
元のメッシュでファセットアサイン(ポリゴン単位でのマテリアルアサイン)を使用している場合は少々注意が必要です。
ファセットアサインされているメッシュはこのようなコネクションになっています。
これらのノードは標準状態のnodeEditorでは表示されません。 show > show auxiliary nodes をonにすると表示されます。
それでも表示されない場合は、auxiliary nodes でリストの編集が必要です。
この場合、inMeshに上流のメッシュをコネクトしてもファセットアサイン情報が上手く再現されません。
コネクトしたシーン上では一見問題なく見えますが、保存して開き直すと上流メッシュのマテリアルアサイン情報が適用されています。
そのため、ファセットアサイン情報を格納している groupID・groupPartsノードへのケアが必要になります。
今回はこの処理をセットアッププロセスの最後に追加してあります。
rigUtil.adjustmentGroupID()
ローカルレイヤーの追加
では既存のセットアップにレイヤーを追加してみます。
リグモジュールを追加し、ビルドを行います。
localレイヤーが作成されました。
コントローラーを移動させると、localレイヤーは body_jnt とその親のjointの影響を受けていないことがわかります。
localLayerSetup_gp 内のjointとメッシュをバインドします。
その上で改めて コントローラーを動かしてみると、body_jntより下の階層の変形が再現されるようになりました。
ですがこのままでは足りないので、一時的に model_gp内のメッシュを body_jntでバインドします。
これによって全体移動などのコントローラーの挙動も反映されるようになりました。
スクワッシュの追加
では本来の目的である スクワッシュを追加しようと思います。
追加したモジュールは以下4つです。
・latticeDeformer
・aimConstraintSetup
・lengthToScaleSetup ※モジュールに不具合があったのでついでに修正しました。
・pointCtrl
スクワッシュ用のjointも追加してビルドを行います。
今回 ワールドの動きはスクワッシュと共に全て latticeで制御しようとおもうので、
latticeShape および baseShapeは setup_gpに格納してあります。
latticeShapeを スクワッシュ用jointでバインドして、動かしてみます。
latticeShapeのskinWeightを書き出して完成・・・としたいですが、割とよくある問題が発生しています。
よくある問題対応
メッシュがローポリ過ぎてちょっと形がイマイチになっています。
タイヤもこのタイプのトポロジーだと、綺麗に変形してくれません。
とても欲深い感じで言うと
・メッシュはある程度均等に割られていて欲しい
極端に少ない箇所があると、その部分は変形せずに直線的な見た目になってしまいます。
・1点に複数のエッジが収束している割り方は避けてほしい
先述の画像にあるように、変形させた際に変な角が立ってしまいます。
というリクエストをモデラーさんに出したいですね。
というわけで、モデラー(自分)に修正依頼をだしまして、
更新されたモデルデータに入れ替えてセットアップを行います。
完成
モデルを入れ替えて更新したデータが出来上がりました。
latticeの変形結果も問題なさそうです。
こちらを使用してテストアニメーションをつけてみました。
※ステアリングの方向とか車体の傾きとかはノリで付けてます。
おわりに
さて、かっこいい軽トラのセットアップが完了しました。
まだまだ色々追加できそうな気はします。
例えば・・・
・地面の凹凸に対してタイヤが上下動する
・ヘッドライト・ウィンカー などの光ものを追加する
・サスペンション周りのモデルを追加して、セットアップする
・タイヤが接地面で若干つぶれるようにする
割と細かい部分のアップグレードができそうです。
基本的な部分も色々できることはまだまだあると思うので、是非色々と試してみてください。


































