UVスペースの位置からワールドの位置を取得しよう
2013/5/13
Tag: maya,openMaya,python,script,uv
みなさん、こんにちは。
TDの野澤(@lphing)です。
早いもので、もう4月が終わってしまいましたね・・・。
光陰矢のごとしですね。僕も一秒一秒大切に生きたいと思います(笑
さてまずは、最近のデジタル・フロンティアにまつわるトピックからご紹介します。
弊社も制作に加わった、村上隆氏初監督作品の「めめめのくらげ」が絶賛全国公開中です。
公式サイト
僕も、先日試写で見て監督ワールド全開のかわいいキャラクター達の活躍に大興奮しました!
弊社では、340ショットのVFXを担当させて頂きました。多くの方に劇場で鑑賞して頂けたら嬉しいです。
さて、最近僕の仕事がおおよそTDからかけ離れていて社内からも社外からも野澤さんの仕事ってなんですか?
と尋ねられることが多くなりました。
そういう時は、必ずテクニカル広報です!(キリ
と答えるようにしています。
・・・と、そんな冗談はさておき、たまにはMayaのTDらしいマジメな記事でも書かないと
僕のPCからMayaのライセンスを抜かれかねないので今日はMayaネタを書いてみることにします。
今日のお題は、タイトルにもある様に「UVスペースの位置からワールドの位置を取得しよう」です。
Mayaでキャラクター等を作っていて、複数のキャラクターの身体の決まった同じ位置に
共通のオブジェクトを配置したいと思った事はありませんか?
例えば、顔をJointでコントロールしたい時に、眉毛のJoint、口のJoint、頬のJointなどは、
モデルは違っていても大体同じ様な位置に配置したい所です。
なぜならキャラクターが10体いれば10体分の骨位置を合わせる作業をやらないといけないのは非常に手間が多いからです。
弊社では、フルCG劇場アニメーションなどの制作過程で大勢のキャラクターが登場するケースがよくあります。
そういうケースでは作業工数的にも簡易的に表情のコントロールをしたいという要望が出ます。
僕が、最初に思いついた方法は、トポロジー(ポリゴンの頂点数)を統一させる方法でした。
全てのキャラクターが同じトポロジーならば、頂点のワールド座標から配置すべきポイントを取得するのは容易です。
しかし、この方法には大きな欠点があります。
トポロジーを完全に統一させるという事は、モデリングする人にとってはとても大きな制約です。
モデルで顔の皺などのデティールを与える事が出来ないのは制作上非常に困難でした。
そこで僕は、全てのキャラクターが共通(似た様な)のUV座標を持っていさえすれば、
UV座標上の任意の点からワールド座標上の点を見つけ出せるのではないかと考えました。
(文章だと説明しにくいので図をご覧ください・・・)
これが出来れば、もしも頂点数があっていなくても頂点IDに依存しないので問題にはなりません。
そこで、まずはmelコマンドで目的のものを調べてみましたが中々それらしいものを発見できませんでした。
次に、OpenMayaのAPIを調べてみると、MItMeshPolygonの中にgetPointAtUV()というそれらしいものを発見しました。
この当時OpenMayaを使ってツール書いた事はほとんどありませんでしたが、ググったら色々サンプルコードもあったのでそれらを組み合わせていったら意外とすんなり出来ちゃいました。
ホントに便利な世の中ですねぇ。
では、サンプルコードです。
# -*- coding: sjis -*- from maya import OpenMaya def getPosFromUV(p=[ 0.5, 0.5 ]): selList = OpenMaya.MSelectionList() OpenMaya.MGlobal.getActiveSelectionList( selList ) nodeFn = OpenMaya.MFnDagNode() dagPath = OpenMaya.MDagPath() cmpn = OpenMaya.MObject() selList.getDagPath( 0, dagPath, cmpn ) iter = OpenMaya.MItSelectionList( selList ) ## 選択しているオブジェクトでイテレート while not iter.isDone(): mObj = OpenMaya.MObject() iter.getDagPath( dagPath, cmpn ) ## meshノードか判別 if dagPath.hasFn( OpenMaya.MFn.kMesh ): meshFn = OpenMaya.MFnMesh( dagPath ) point = OpenMaya.MPoint() faceIter = OpenMaya.MItMeshPolygon(dagPath, cmpn) ## faceでイテレート while not faceIter.isDone(): id = faceIter.index() util = OpenMaya.MScriptUtil() util.createFromList(p ,2) uv = util.asFloat2Ptr() ## Faceが重ならなければNoneを返すのでtryで例外処理 try: meshFn.getPointAtUV(id, point, uv, OpenMaya.MSpace.kWorld) return (point.x,point.y,point.z) except:pass faceIter.next() iter.next() #実行コマンドは、こちらです。 #Jointを配置させたいオブジェクトを選択してから実行してください。 #UVの領域外の値を指定するとJointは作られませんのでご注意ください。 for sel in cmds.ls(sl=1): for i in [[0.70,0.53],[0.55,0.77],[0.67,0.82]]: cmds.select(sel) cmds.joint( p=getPosFromUV(i) )
このスクリプトでやっている事は至ってシンプルです。
選択しているオブジェクト名を取る
↓
選択しているオブジェクトがmeshかどうか調べる
↓
そのオブジェクトが持っている全てのフェースを調べる
↓
指定したUV座標がフェース上に見つからなければNoneを返す
↓
指定したUV座標にフェース上に見つかったらワールド座標を返す
こうする事で、頂点の数に依存しないモデルであってもUVの位置からワールドの位置を取る事が出来るようになりました。
このスクリプトを利用すると以下の様な事が簡単に出来ます。
※重なり合ったフェースについては考慮しておりません。ご注意ください。
【例1】
共通のUVを持ったキャラクターの顔が複数あります。
左側のキャラクターは、わざとトポロジーを変更し、さらに大きさも変えてあります。
この顔のUV座標が同じ位置にJointを配置します。
共通リグを簡易的に作成する時に活用できます。
【例2】
PaintFXを使用して草を作りmeshに変換しました。
この葉っぱ一本ずつに対して等間隔に骨を配置させ、後でアニメーションに利用したいと思います。
PaintFXで作られたMeshには全て同じ様なUVを持っています。
しかし、最上部の高さがオブジェクトごとに異なります。
これでは、同じ位置を指定することが出来ませんので、一つずつV方向(縦方向)に対しての最大値を求め
長い葉っぱには多くの骨を、短い葉っぱには少ない骨を配置するようにします。
この方法を応用する事で大草原の草を一本ずつ揺らす事が出来ます。
弊社が制作し、昨年公開されて大ヒットした「おおかみこどもの雨と雪」
では、風に揺れる草花の動きを付ける為のツールに役立てています。
こういう風に、モデリングをしてもらう時にUVを似た様に作ってもらうという
共通点を持たせるだけで、後からアニメーション作業を支援できるツールが作れるという事例の紹介でした。
少しでも皆様のお役にたてば幸いです。
※免責事項※
本記事内で公開している全ての手法・コードの有用性、安全性について、当方は一切の保証を与えるものではありません。
これらのコードを使用したことによって引き起こる直接的、間接的な損害に対し、当方は一切責任を負うものではありません。
自己責任でご使用ください
コメント
コメントフォーム