執筆者:広森
映像業界を経てモノリスソフトへ入社。 以来、テクニカルアーティストとして主にリギング・セットアップ関連の業務を担当。 好きな飲み物は赤ワイン。
TECH BLOG
こんにちは。モノリスソフト テクニカルアーティストの広森です。
リギングの工程において、ジョイントの配置はとても重要な作業の一つです。ですが、昨今はハードウェアスペックの向上により扱えるジョイントの数が増え、手動でのジョイントの配置は多くのリガーにとって精神的な苦痛を伴う作業になりかねません。
また、手動でのジョイント配置はコストが大きく、リガーの人員が不足がちなことも相まってボトルネックになりやすい内容の1つです。アセット数が多くなるほど、この問題は顕著になります。
そこで今回は、ある程度の精度で自動でジョイントを生成する手法を2つほどご紹介します。
自動化することで作業の効率化と心の平穏を手に入れましょう。
なお、今回は作業フローの一部と機能の紹介に留め、ジョイントの向き合わせなどは省略します。
ジョイントを効率良く配置すると考えた際、最初に浮かびそうな手法は、「ジョイントの階層をテンプレート化して手で調整する」ではないでしょうか。
この手法は効率化の1つではありますが、ジョイントの位置調整は手作業で行うため、キャラクターの体形・身長の幅が大きいと調整のコストも大きくなってしまいます。
昨今ではキャラクターデザインの幅が広がり、肥満体型からスレンダーなモデル体型や、身長も子供(1.3m)から巨人(50m)まで、幅広い体格に対応する必要があります。
そこで今回はこの手作業で調整する時間を削減するために、以下のようなゴールを設定しました。
今回はメッシュの指定した表面から取得した情報をもとに座標を生成し、それらの座標からジョイントを作成してみましょう。
メッシュの表面から取得する情報とは頂点情報かUV値です。2つの情報のメリット、デメリットを以下にまとめました。
どちらの手法を用いるかは、後工程の内容やモデラーさんの都合に合わせて決めていただければと思いますが、筆者は人型や4足歩行の生き物に関しては、以下の理由から前者を推奨しています。
また、スカートなどもプリセットのモデルを作っておけば、自動でジョイントを作ってスキンを転送するところまで持っていけるので対応力もあります。
では実際に、頂点番号もしくはUV座標からジョイントを生成してみましょう。その為のスクリプトを簡単に作ってきたので動かしてみましょう。
用意したスクリプトは、視認しやすいようロケーターを生成するようにしておきました。
pointPositionは頂点番号を渡すとその頂点の座標を返す関数です。これを使って複数の頂点からジョイントを生成します。
#頂点を渡すと位置座標が返ってくる。オプションでワールド座標かローカル座標で取得するかを指定できます。 pm.pointPosition()
選択している頂点からロケーターを生成するスクリプトです。
# -*- 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)
pointOnPolyConstraintの機能を使ってUV座標からワールド座標に変換します。
UがUVエディタ上の横、縦がVの位置になるので、3D空間上だと画像の①の位置にロケーターが位置合わせされています。
メッシュ上に割り当てられていないUV座標を指定すると、該当するメッシュが存在せず座標が取得出来ないので原点である②に配置されます。
後は位置合わせに利用したロケーターからワールド座標を取得して、ジョイントに設定するだけでOKです。
以下のスクリプトで事前に決めたUV座標にロケーターを作成します。
# -*- 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と記載すれば、シーンなどにすでに配置したオブジェクトの値を編集することも出来るので、場合によっては都度オブジェクトを生成する必要はないです。
# -*- 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)
これでジョイントを生成するための座標はロケーターから取得出来る状態になりました。
一連の内容を自動で行う場合、ジョイント生成後にロケーターは不要になるので、ロケーターは生成せず座標として取ってきてしまう方が都合が良いと思います。
ここまでで、必要な値の取得方法が分かりました。ここでは、実際のモデルに合わせて値を取得してみましょう。ジョイントが生成される位置や基準にした座標はあくまでも参考程度にしてください。
今回はローモデルを使用せず直接完成モデルの頂点から取得していますが、作業やモデルを使いまわすことを考えると少ないポリゴンで作成する事を推奨します。
自分の欲しい位置に頂点を配置しやすいので、完成モデルよりエッジの位置が都合をつけやすいです。
今回は複数選択した頂点からジョイントを生成するスクリプトも合わせて用意したので、こちらを使って生成したいと思います。
# -*- 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)
画像の赤い丸で囲まれている複数の頂点を取得してみます。
肘は形状維持の骨を入れたり、ツイストジョイントの都合上中心を通ってほしい意図があるのでこの点で取っていますが、肘側に寄せても良いと思います。
実行後、肘の中心に生成されていそうです。
膝は肘と同じ形でよいと思います。
実行後
腰は骨盤の中心に配置したいので、腰の位置と股下で点を取得し生成しています。
実行後
股関節の境目にあるエッジループに沿って生成します。
実行後
4パターンほど紹介例で生成してみました。基本的には分かりやすいトポロジーや、関節の境目から取得していくのがやりやすいと思います。
これでジョイントがモデルの形状に合わせて自動で生成出来るようになりました。
ジョイント作成時に名前もセットでつけておけば、親子関係も自動で構築出来そうですね。
今回の手法だとメッシュの表面から座標を取得して生成しますが、デザインの都合上でメッシュの形状を左右非対称に作成した場合、ジョイントを自動で作成した際に体の中心から少しずれて作成されるケースがまれに発生します。
その場合は階層をくっつける前に、ジョイントのtransrateXのアトリビュートを0にする処理を挟むようにして対応することで、メッシュの表面から高さと前後を算出した、きちんと値がきれいなジョイントを作成することが出来ます。
スクリプトで自動化した場合、作り方を工夫しないと、ジョイントを決め打ちしてしまうのでジョイントの数を完全に固定してしまいます。UE4などジョイントの階層を固定して運用する場合ならよいのですが、尻尾などはジョイントの分割数を自由に変更できる構造にしたいと思いますので、その際のケースも説明しておきます。
結論から述べると、スプラインカーブを作成し、カーブを基準にジョイントを作成すれば自由に分割数を変えられます。
コントロールポイントの位置だけ今回求めた座標に位置合わせをしてあげる事で、モデルの意図した位置にジョイントを置くことが出来るようになります。
ぜひ挑戦してみてください。
頂点座標やUV値からジョイントを生成することによって、キャラのジョイントの配置を自動で行えるようになりました。
これでプロポーション変更によるジョイント位置の調整に怯える日々は無くなりましたが、ジョイント以外にもリグの再生成など作業があるので、リガーに無断でプロポーションを調整しないように気をつけてください。
執筆者:広森
映像業界を経てモノリスソフトへ入社。 以来、テクニカルアーティストとして主にリギング・セットアップ関連の業務を担当。 好きな飲み物は赤ワイン。