🤖

[Maya] SplineIKで伸びるリグの実装

2023/12/26に公開

概要

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を実装できます。

\text{各JointのScale} = \frac{\text{現在のCurveの長さ}}{\text{通常時のCurveの長さ}}

ノードはこのように組みます。

これで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を計算して結果を代入する方法で、上記の問題の解決をしました。

\text{各JointのTranslateX} = \frac{\text{現在のCurveの長さ}}{\text{通常時のCurveの長さ}}通常時のTranslateX

具体的にはこのようにノードを組みました。

このノードを組んだ後、各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)

一度スクリプトを書くことによって、イテレーションが早くなりますね。

https://gist.github.com/yohawing/3f5eb61dffe7713e82bb6acbe46d1b8a

作業してみての感想

このように一見設定が複雑そうに見えるSplineIKの設定も、積み上げていけばある程度システマチックに作業ができます。
また、Mayaの行列計算の仕組みやノード評価の仕組みなど、詰まったときにツールへの理解が結構必要不可欠でしたので、結構勉強になりました。

参考リンク

https://3d.crdg.jp/tech/archives/339
http://3dcgr2lab.com/2019/03/30/maya_spline-ik_stretch/
https://inopoa.com/maya-rigging-stretch-ik-spline/
http://www.jp.square-enix.com/tech/library/pdf/RigAndTools_ScaleRig.pdf
http://www.jp.square-enix.com/tech/library/pdf/BDSeminar20190711.pdf

PY

Discussion