[Maya] SplineIKで伸びるリグの実装
概要
Mayaで伸びる多関節のSplineIKリグの実装をしました。作ったアニメーションはUnityで動かす想定です。またリグのセットアップを半自動化できるように、スクリプトも書きましたので参考にしていただけますと幸いです。
SplineIKを使った多関節リグを作る
まずSplineIKを使った多関節のリグを作る方法を解説します。
こういった多関節で動く想定のモデルを用意します。
こういった選択した頂点からボーンを作るスクリプトを用意しておくと、正確に骨を用意することができます。
import maya.cmds as cmds
selected_verts = cmds.ls(selection=True, flatten=True)
if len([vert for vert in selected_verts if 'vtx' in vert]) is 0:
cmds.warning("頂点が選択されていません。")
else:
# 選択した頂点の中央のポジションを用意
total_pos = [0.0, 0.0, 0.0]
for vert in selected_verts:
vert_pos = cmds.pointPosition(vert, w=True)
total_pos[0] += vert_pos[0]
total_pos[1] += vert_pos[1]
total_pos[2] += vert_pos[2]
center_pos = [
total_pos[0] / len(selected_verts),
total_pos[1] / len(selected_verts),
total_pos[2] / len(selected_verts)]
bone_name = "joint"
bone = cmds.joint(name=bone_name, p=center_pos)
object_name = selected_verts[0].split('.')[0]
cmds.select(object_name)
cmds.selectMode(component=True)
cmds.selectType(softSelect=0, vertex=True)
print("create joint in {0}".format(center_pos))
Create IK Spline HandleでSplineIKを作ります。
動かしたいJointの根本を選択 → 動かしたいJointの先端を選択
SplineIKはCurveにJointを沿わせるような計算が行われます。
生成されたCurveはなるべくControlPointが少ないほうが動かしやすいと思います。ここでは、肘関節をリターゲットしたいので、中点1つのControlPointを追加しました。
CurveのControlPointにConstraintを設定できるようにそれぞれのControlPointにClusterを設定します。 Deform > Create - Cluster
このCという点がそれぞれのControlPointに設定されていると、C点を動かすことによってCurveが変形するのがわかるかと思います。そのCurveの変形に沿ってJointが動くと成功です。
ただ、このままだと伸びないので、JointのScaleを計算する処理を加えて、Jointを伸びるようにしていこうかと思います。
伸びるSplineIKの多関節リグ
ここからはNode Editorでプログラムを組む作業になります。
CurveInfoノードでカーブの長さを取得できるので、通常時の長さと現在の長さの比率を計算してJointのScaleに代入することで伸びるJointを実装できます。
ノードはこのように組みます。
これでJointが伸びるようになるかと思います。
SplineIKTargetのTwistの設定
SplineIKにTwistの設定をしたい場合は、SplineIKSolverのAttributeをいじる必要がありますが、向いている軸によって設定方法が違うので注意が必要です。
- Enable Twist Controlsにチェックを入れる
- WorldUpTypeをObject Rotation Up (Start/End)に
- Forward AxisとUpAxisをJointが向いている方向に設定
- World Up ObjectにSplineIKの根本のJointの名前を設定
- World Up Object2️にSplineIKの先端のJointの名前を設定
これでSplineIKのTwistの設定ができました。
注)リグの設定の過程でBoxControllerをCurveの先端にParentConstraintさせています。また先端のJointをBoxControllerにOrientConstraintさせています。こうなると、上記のWorldUpObject2の参照とOrientConstraintで参照がダブってしまいます。こうなると正常にリグが動作しないので、使用するリグに合わせてTwistの設定は柔軟に設定できるように各パラメーターの挙動への理解が必要になってくる点に注意してください。
MayaとUnityでのScaleCompensate問題への対処
いったんマニュアル通りに伸びるSplineIKを組むと、今度はUnityに持って行ったときに問題が発生します。
MayaではSegmentScaleCompensateという機能があり、Scaleの値が子に引き継がれないように、親のScaleの逆行列が子に適応されてしまうのです。
ちなみにSegmentScaleCompensateを外しても今度はSearが悪さをします。Mayaの行列合成はUnityより複雑で、Scaleを使ったリグを組むときは注意が必要になってきます。
このスクショの通り、Scaleが子に適応されていって、腕が彼方まで伸びてしまっているのがわかるかとおもいます。
今回のパターンの解決方法はシンプルで、Scaleを使わず、X軸成分のみTranslateとして適応してあげれば解決できるので、その方法を紹介します。
TranslateをScaleを元に計算する
Scaleを使うと問題が発生するので、Scaleを元にTranslateを計算して結果を代入する方法で、上記の問題の解決をしました。
具体的にはこのようにノードを組みました。
このノードを組んだ後、各JointのSegmentScaleCompensateを外しておけばOKです。
Nodeを組んでPythonScriptで自動化
以上の処理をある程度自動化するためにスクリプト化しました。
Twistの設定だけ手動ではあります。
import maya.cmds as cmds
# StartJointからEndJointに関連付けられているJointをListで取得
# Childが一つの想定なので骨が複数あるとおかしくなるかも。
def get_all_spline_joints(joint_list, parent_joint, last_joint):
child = cmds.listRelatives(parent_joint, children=True, type="joint")
joint_list.append(child[0])
if child and last_joint not in child:
get_all_spline_joints(joint_list, child[0], last_joint)
else:
return joint_list
# StretchySplineIKを自動作成するスクリプト
def create_stretchy_spline_ik(start_joint, end_joint, ik_name):
# 始点~終点までの骨のChainを取得。
spline_joints = [start_joint]
get_all_spline_joints(spline_joints, start_joint, end_joint)
# Spline IKの作成
ik_handle, effector, curve = cmds.ikHandle(
sj=start_joint,
ee=end_joint,
sol='ikSplineSolver',
createCurve=True,
numSpans=2,
n=ik_name)
#curve_infoノード作成
curve_info = cmds.arclen(curve, ch=True)
# ジョイントチェーンの初期長さを取得
original_curve_length = cmds.getAttr(curve_info + ".arcLength")
# スケールノードを作成
mult_node = cmds.shadingNode('multiplyDivide', asUtility=True, n=ik_name + "_scale")
cmds.setAttr(mult_node + ".operation", 2) # Divide
cmds.setAttr(mult_node + ".input2X", original_curve_length)
# ノードを接続
cmds.connectAttr(curve_info + ".arcLength", mult_node + ".input1X")
# 各Jointの設定
for jnt in spline_joints:
#segmentScaleCompensateを外す
cmds.setAttr(jnt+".segmentScaleCompensate", 0)
moveNode = cmds.shadingNode('multiplyDivide', asUtility=True, n=jnt + "_tr")
# cmds.connectAttr(mult_node + ".outputX", jnt + ".scaleX")
cmds.connectAttr(mult_node+".outputX", moveNode + ".input1X")
cmds.setAttr(moveNode + ".input2X", cmds.getAttr(jnt + ".translateX"))
cmds.connectAttr( moveNode+".outputX", jnt + ".translateX")
return ik_handle, curve
# CurveのControlPointにClusterを自動作成するスクリプト
def apply_clusters_to_curve(curve_name):
curveCVs = cmds.ls('{0}.cv[:]'.format(curve_name), fl=True)
for cv in curveCVs:
cmds.cluster(cv)
# 2つのJointを選択
selected_joints = cmds.ls(sl=True, flatten=True, type="joint")
start_joint = selected_joints[0]
end_joint = selected_joints[1]
ik_name = "{0}_splineIK".format(end_joint.split("_")[-1]) # ex: Character_LeftArm
ik_handle, curve = create_stretchy_spline_ik(start_joint, end_joint, ik_name)
apply_clusters_to_curve(curve)
一度スクリプトを書くことによって、イテレーションが早くなりますね。
作業してみての感想
このように一見設定が複雑そうに見えるSplineIKの設定も、積み上げていけばある程度システマチックに作業ができます。
また、Mayaの行列計算の仕組みやノード評価の仕組みなど、詰まったときにツールへの理解が結構必要不可欠でしたので、結構勉強になりました。
参考リンク
Discussion