メッシュの形状から自動でジョイントを生成してみよう

INDEX

はじめに

こんにちは。モノリスソフト テクニカルアーティストの広森です。

リギングの工程において、ジョイントの配置はとても重要な作業の一つです。ですが、昨今はハードウェアスペックの向上により扱えるジョイントの数が増え、手動でのジョイントの配置は多くのリガーにとって精神的な苦痛を伴う作業になりかねません。

また、手動でのジョイント配置はコストが大きく、リガーの人員が不足がちなことも相まってボトルネックになりやすい内容の1つです。アセット数が多くなるほど、この問題は顕著になります。

そこで今回は、ある程度の精度で自動でジョイントを生成する手法を2つほどご紹介します。

自動化することで作業の効率化と心の平穏を手に入れましょう。

なお、今回は作業フローの一部と機能の紹介に留め、ジョイントの向き合わせなどは省略します。

効率化するために達成したいゴール

ジョイントを効率良く配置すると考えた際、最初に浮かびそうな手法は、「ジョイントの階層をテンプレート化して手で調整する」ではないでしょうか。

この手法は効率化の1つではありますが、ジョイントの位置調整は手作業で行うため、キャラクターの体形・身長の幅が大きいと調整のコストも大きくなってしまいます。

昨今ではキャラクターデザインの幅が広がり、肥満体型からスレンダーなモデル体型や、身長も子供(1.3m)から巨人(50m)まで、幅広い体格に対応する必要があります。

そこで今回はこの手作業で調整する時間を削減するために、以下のようなゴールを設定しました。

  • メッシュの形状と大きさを読み取って、ある程度自動でジョイントを配置する

手法の紹介

今回はメッシュの指定した表面から取得した情報をもとに座標を生成し、それらの座標からジョイントを作成してみましょう。

メッシュの表面から取得する情報とは頂点情報UV値です。2つの情報のメリット、デメリットを以下にまとめました。

頂点からジョイントを生成する場合

  • メリット
    • ローモデルからジョイントを作るワークフローを作ってしまえば、ジョイントを作るために使用したローモデルをそのままスキニング作業にケージモデルとして使うことが出来ます。
    • 頂点番号でスキンウェイトを流し込んで転送出来るので比較的精度が高いです。
  • デメリット
    • 形状合わせが少し手間です。デフォーマーや別のツールで自動的に合わせる事を推奨しています。
    • 頂点座標から生成しているのでローモデルのクオリティがそのままジョイントの精度に直結します。
    • 関節の分割箇所に頂点が無いと精度が落ちます。

UV座標からジョイントを生成する場合

  • メリット
    • 頂点番号に全く制限がなく自由なトポロジー、ポリゴン数で作成出来ます。
  • デメリット
    • UVの割り方が決まってしまうのでモデラーさんによっては負担になってしまいます。
    • 1メッシュ内で重なったUVを利用できません。髪の毛などのジョイントを自動化する際は注意してください。
    • ウェイトマップに焼き付けてからスキンウェイトを転送出来るが少し手間がかかります。

どちらの手法を用いるかは、後工程の内容やモデラーさんの都合に合わせて決めていただければと思いますが、筆者は人型や4足歩行の生き物に関しては、以下の理由から前者を推奨しています。

  • トポロジーが決まっていることが多い(ユニバーサルトポロジー)
  • スキニング作業が行いやすいようにローモデルを用意するケースが多い

また、スカートなどもプリセットのモデルを作っておけば、自動でジョイントを作ってスキンを転送するところまで持っていけるので対応力もあります。

ジョイント位置を決める為の座標の取得方法

では実際に、頂点番号もしくはUV座標からジョイントを生成してみましょう。その為のスクリプトを簡単に作ってきたので動かしてみましょう。

用意したスクリプトは、視認しやすいようロケーターを生成するようにしておきました。

頂点座標から作成する

pointPositionは頂点番号を渡すとその頂点の座標を返す関数です。これを使って複数の頂点からジョイントを生成します。

pointPosition

#頂点を渡すと位置座標が返ってくる。オプションでワールド座標かローカル座標で取得するかを指定できます。
pm.pointPosition()

選択している頂点からロケーターを生成するスクリプトです。

getPos

# -*- coding: utf-8 -*-
import pymel.core as pm
# 選択しているオブジェクトのリストを展開して返します。
selNodes = pm.selected(fl=True)
 
def getVertexPos(selNodes):
    for node in selNodes:
        #選択している頂点からワールド座標を取得します。
        pointPos = pm.pointPosition( node, world=True )
        # 視認しやすいようにロケーターを作成します。
        locatorShape = pm.createNode("locator")
        trans = locatorShape.getParent()
        # 取得してきたワールド座標を設定します。
        pm.xform(trans,translation=pointPos,worldSpace=True)
getVertexPos(selNodes)

UV座標から作成する

pointOnPolyConstraintの機能を使ってUV座標からワールド座標に変換します。

  • この機能は頂点と頂点にくっつけたいオブジェクトを選択してくっつける機能ですが、仕組み的にはUV座標を参照して接続されています。

UがUVエディタ上の横、縦がVの位置になるので、3D空間上だと画像の①の位置にロケーターが位置合わせされています。

  • 今回はUが0.5、Vが1で位置合わせを行っています。

tech_39_02.jpg

メッシュ上に割り当てられていないUV座標を指定すると、該当するメッシュが存在せず座標が取得出来ないので原点である②に配置されます。

tech_39_03.jpg

後は位置合わせに利用したロケーターからワールド座標を取得して、ジョイントに設定するだけでOKです。

以下のスクリプトで事前に決めたUV座標にロケーターを作成します。

pointPosition

# -*- coding: utf-8 -*-
import pymel.core as pm
#選択したターゲットのメッシュにくっつけたいUV座標の定数です。
Ufloatval = 0.5
Vfloatval = 1
 
selNodes = pm.selected(fl=True)
 
locatorShape = pm.createNode("locator")
trans = locatorShape.getParent()
 
def setUVPosNode(souceMesh,targetMesh,Uval,Vval):
    # pointOnPolyConstraintを作成します。
    PopC = pm.pointOnPolyConstraint(souceMesh,targetMesh)
    targetName = souceMesh.name()
    # コマンドからウェイトリストが取れないので、名前を指定してUV値を設定します。
    PopC.attr(targetName + "U0").set(Uval)
    PopC.attr(targetName + "V0").set(Vval)
 
#実際に実行
setUVPosNode(selNodes[0],trans,Ufloatval,Vfloatval)
値をワールド座標で取得するには

以下のように記述するとワールド座標で取得する事が出来ます。

対応するコマンドであればq=Trueと記載することで値を参照することが出来ます。

e=Trueと記載すれば、シーンなどにすでに配置したオブジェクトの値を編集することも出来るので、場合によっては都度オブジェクトを生成する必要はないです。

pointPosition

# -*- coding: utf-8 -*-
#ワールド座標での位置座標を取得します。
selNodes = pm.selected(fl=True)
#リストの最初のオブジェクトを指定します。
selNode = selNodes[0]
print (selNode)
# オブジェクトのワールド座標を取得します。
pos = pm.xform(selNode,q=True,translation=True,worldSpace=True)
print (pos)

これでジョイントを生成するための座標はロケーターから取得出来る状態になりました。

一連の内容を自動で行う場合、ジョイント生成後にロケーターは不要になるので、ロケーターは生成せず座標として取ってきてしまう方が都合が良いと思います。

基準となりそうな点を決める

ここまでで、必要な値の取得方法が分かりました。ここでは、実際のモデルに合わせて値を取得してみましょう。ジョイントが生成される位置や基準にした座標はあくまでも参考程度にしてください。

今回はローモデルを使用せず直接完成モデルの頂点から取得していますが、作業やモデルを使いまわすことを考えると少ないポリゴンで作成する事を推奨します。

自分の欲しい位置に頂点を配置しやすいので、完成モデルよりエッジの位置が都合をつけやすいです。

今回は複数選択した頂点からジョイントを生成するスクリプトも合わせて用意したので、こちらを使って生成したいと思います。

createVertexJoint

# -*- coding: utf-8 -*-
import pymel.core as pm
# 選択しているオブジェクトのリストを展開して返します。
selNodes = pm.selected(fl=True)
def createVertexPosJoint(selNodes):
    posLocators = []
    for node in selNodes:
        #選択している頂点からワールド座標を取得します。
        pointPos = pm.pointPosition( node, world=True )
        # 視認しやすいようにロケーターを作成します。
        locatorShape = pm.createNode("locator")
        trans = locatorShape.getParent()
        posLocators.append(trans)
        # 取得してきたワールド座標を設定します。
        pm.xform(trans,translation=pointPos,worldSpace=True)
    #ジョイントを作成して位置合わせする処理です。
    jointNode = pm.createNode("joint")
    pm.pointConstraint(posLocators, jointNode)
    pm.delete(posLocators)
createVertexPosJoint(selNodes)

生成サンプル

画像の赤い丸で囲まれている複数の頂点を取得してみます。

肘は形状維持の骨を入れたり、ツイストジョイントの都合上中心を通ってほしい意図があるのでこの点で取っていますが、肘側に寄せても良いと思います。

tech_39_04.jpg tech_39_05.jpg

実行後、肘の中心に生成されていそうです。

tech_39_06.jpgのサムネイル画像

膝は肘と同じ形でよいと思います。

tech_39_07.jpg tech_39_08.jpg

実行後

tech_39_09.jpgのサムネイル画像

腰は骨盤の中心に配置したいので、腰の位置と股下で点を取得し生成しています。

tech_39_10.jpg tech_39_11.jpg

実行後

tech_39_12.jpgのサムネイル画像

太もも

股関節の境目にあるエッジループに沿って生成します。

tech_39_13.jpg tech_39_14.jpg

実行後

tech_39_15.jpg tech_39_16.jpg

4パターンほど紹介例で生成してみました。基本的には分かりやすいトポロジーや、関節の境目から取得していくのがやりやすいと思います。

これでジョイントがモデルの形状に合わせて自動で生成出来るようになりました。

ジョイント作成時に名前もセットでつけておけば、親子関係も自動で構築出来そうですね。

厳密に位置調整するなら

今回の手法だとメッシュの表面から座標を取得して生成しますが、デザインの都合上でメッシュの形状を左右非対称に作成した場合、ジョイントを自動で作成した際に体の中心から少しずれて作成されるケースがまれに発生します。

その場合は階層をくっつける前に、ジョイントのtransrateXのアトリビュートを0にする処理を挟むようにして対応することで、メッシュの表面から高さと前後を算出した、きちんと値がきれいなジョイントを作成することが出来ます。


おまけ

スクリプトで自動化した場合、作り方を工夫しないと、ジョイントを決め打ちしてしまうのでジョイントの数を完全に固定してしまいます。UE4などジョイントの階層を固定して運用する場合ならよいのですが、尻尾などはジョイントの分割数を自由に変更できる構造にしたいと思いますので、その際のケースも説明しておきます。

結論から述べると、スプラインカーブを作成し、カーブを基準にジョイントを作成すれば自由に分割数を変えられます。

コントロールポイントの位置だけ今回求めた座標に位置合わせをしてあげる事で、モデルの意図した位置にジョイントを置くことが出来るようになります。

ぜひ挑戦してみてください。

まとめ

頂点座標やUV値からジョイントを生成することによって、キャラのジョイントの配置を自動で行えるようになりました。

これでプロポーション変更によるジョイント位置の調整に怯える日々は無くなりましたが、ジョイント以外にもリグの再生成など作業があるので、リガーに無断でプロポーションを調整しないように気をつけてください。

執筆者:広森

映像業界を経てモノリスソフトへ入社。 以来、テクニカルアーティストとして主にリギング・セットアップ関連の業務を担当。 好きな飲み物は赤ワイン。

ABOUT

モノリスソフト開発スタッフが日々取り組んでいる技術研究やノウハウをご紹介

RECRUIT採用情報