Houdiniを使って衣装の位置合わせを自動化したい
この記事はHoudini Apprentice Advent Calendar 2023の23日目の記事です。
初めまして、ねむり木です。普段VRChatで色々な創作をしています。
最近は別のアバター向けに作成された衣装を自分のアバターに半自動で着せ替えるHDAをちまちま開発しています。今日はこのHDAについて書こうと思います。
はじめに
現在、BoothではVRChat向け衣装が多く販売されています。これらのVRChat向け衣装は特定のアバターの体型に合わせて制作されており、自分の使っているアバターに対応していない衣装を着たい(着せたい)と思う場合がよくあります。
多くのユーザーが衣装を着せるためにUnityやBlenderを使って衣装の形状の調整を行いますし、制作者側で複数のアバターの対応作業を行ってから衣装を販売するケースも一般的です。このどちらの場合でも作った衣装を別のアバターの体型に合わせて形状を調整するという作業が発生しています。
今回はHoudiniでこの制作済みの衣装を別のアバターの体型に合わせて調整する作業の自動化を試みました。
注意事項として、キャラクターの出力に使用するROP FBX Character Output SOPはHoudini Apprenticeでは使用できずIndie以上のライセンスが必要となります。また記事の内容上、アバターの身体に対してブーリアン処理を行う場面があります。苦手な方は閲覧の際にご注意ください。
概要
今回の手法の概要を説明します。
この記事では元々の衣装が対応しているアバターの素体をソース素体、衣装を対応させたいアバターの素体をターゲット素体、対応させたい衣装を対象衣装と呼びます。
今回の手法は
- ソース素体とターゲット素体のそれぞれからアバターの体格を反映したラティスを作成する
- 上記のふたつのラティスを使用して、Point Deform SOPで対象衣装を変形させる
という流れで衣装の位置合わせをします。
1・2のそれぞれの処理に対応して、ラティス生成用HDACostume Fitting Lattice Generaterと衣装変形HDACostume Fitting Deformerのふたつを作成しました。以下ではそれぞれのHDAの面白い部分を紹介します。hipファイルを公開するので詳細を知りたい方はそちらをご確認ください。
Point Deform SOPとは
Point Deform SOPはメッシュ変形系のノードのひとつで、2番目の入力と3番目の入力の差分で1番目の入力を変形させます。BlenderでいうところのMesh Deformモディファイアに近いです。
HoudiniだとLattice SOPやCloth Deform SOPなど似たようなノードがいくつかありますが、Point Deformは変形される側がどんな形状でも良く、ラティス側も普通のメッシュが使用できるのでこれらの中でも癖なく使用できる印象です。これらのノードに共通する特徴として2番目の入力と3番目の入力の形状の接続性は一致している必要があります。
今回はソース素体とターゲット素体からそれぞれ2番目の入力(変形前のラティス)と3番目の入力(変形後のラティス)を作成し、その差分で対象衣装を変形させています。
Costume Fitting Lattice Generater
素体のメッシュとボーンを入力に取り、大まかな体型を表したラティスを出力するHDAです。
Point Deform SOPの入力に使用するため常に同じトポロジーを出力します。
ボーンの種類の判定
このHDAではまず各ボーンがUnityHumanoidAvatarのどのボーンに対応しているかを表すhumanoid_bone_type
アトリビュートを作成しています。
UnityHumanoidAvatarはUnityがキャラクターモデルを標準化するために定めているボーン構造で、VRChatで使用されるアバター・衣装はこの規格に沿って作成されています。UnityHumanoidAvatarについては以下の記事を参照ください
しかし個々のボーンは好きな名前を付けることができるため、ボーンのname
アトリビュートからそのボーンがUnityHumanoidAvatarのどのボーンに当てはまるのかを簡単に判定することができません。
そこで、アバターのそれぞれのボーンがUnityHumanoidAvatarのどのボーンに対応するかを判定するために、keyがUnityHumanoidAvatarのボーン名であり、valueが各ボーンの慣習的に使われる命名規則を網羅的に集めた配列であるdict型の変数bone_mapper
を用意しました。各ボーンでbone_mapper
の中からボーンのname
と一致する要素を持つ配列を探し、その配列と対応するkeyをhumanoid_bone_type
アトリビュートとして各ボーンに格納しています。(bone_mapper
の中身はModular AvatarのHeuristicBoneMapper.csを参考にしました)
dict get_bone_mapper(){ dict bone_mapper; bone_mapper["Hips"] = {"Hips", "Hip"}; bone_mapper["LeftUpperLeg"] = { "LeftUpperLeg", "UpperLeg_Left", "UpperLeg_L", "Leg_Left", "Leg_L", "ULeg_L", "Left leg","LeftUpLeg","UpLeg.L" }; ...... return bone_mapper; } string humanoid_bone_name = "None"; string target_bone_name = s@name; target_bone_name = normalize_name(target_bone_name); dict bone_mapper = get_bone_mapper(); string bone_keys[] = keys(bone_mapper); foreach(string bone_key; bone_keys){ string bone_names[] = bone_mapper[bone_key]; foreach(string bone_name; bone_names){ bone_name = normalize_name(bone_name); if(target_bone_name == bone_name){ humanoid_bone_name = bone_key; } } } s@humanoid_bone_type = humanoid_bone_name;
このセットアップを行うことでボーンの名前揺れに左右されずに素体のリグやウェイトを取得できるようになります。
体型の測定
このHDAでは、ラティスの形状を胴体の起伏に沿わせるために等高線を使ってアバターの3サイズを測定しています。
流れとしてはまず素体のメッシュからHip・Spine・Chestのウェイトが塗られているメッシュを切り出し、それぞれのメッシュからY軸方向の等高線を生成します。等高線の生成は堀川淳一郎さんのチュートリアルを参考にしました。
これらの等高線のそれぞれに対して、get_bbox
系の関数を使ってZ方向の最小値(min)・最大値(max)・厚み(size)を計測しアトリビュートとして格納します。
このアトリビュートを参考に、Hipメッシュの中で最小値が最も小さい(=一番後ろに突き出している)等高線をヒップ、Spineメッシュの中で一番厚みが薄い(=腰回りが細い)等高線をウェスト、Chestメッシュの中で一番厚みがある(=胸囲が長い)等高線をバストとして抜き出し、それぞれのBoundsを取りだします。
そしてこのBoundsの間に面を貼ることで胴体の大まかな起伏を表現したラティスの完成です。(以下では3サイズに加えて脇の下の位置に頂点を追加しました。)
Costume Fitting Deformer
衣装を変形させるためのHDAです。Character Pack SOPでPackされた対象衣装とふたつのラティスを入力に取ります。
基本はPoint Deform SOPのラッパーですが、キャラモデルの変形を行うための処理がいくつかあるので紹介します。
コントロールリグの設定(WIP)
こちらはまだ制作途中ですが、ラティスの形状を操作するためのリグコントローラーを組みました。
ラティスの持つ頂点からスケルトンを作り各部位ごとにコントローラーをアタッチすることで、腕や胴回りの位置を手動で調整できるようなセットアップになっています。また、ラティスの全てのポイントがスケルトンに含まれているので、変形の適応は(Bone Deformではなく)直接ボーンの位置をpoint wrangleで読み取って反映させます。
Blend Shapeの再構築
Point Deform SOPを使用して衣装の位置合わせをしても、衣装に付与されているブレンドシェイプは元の形状のままです。そのため変形した後にBlend Shapeの再構築が必要になります。
Blast SOPで@blend_shape_name!=""
を指定してBlend Shapeのプリミティブを選び出し、ForLoopを使用してBlend Shapeそれぞれに対して処理を行っていきます。
Blend ShapeはPacked PrimitiveなのでそのままではPoint Deform SOPによる変形はできません。ここでは各Blend Shapeごとに@blend_shape_name
を取り出して元のメッシュのdetailに格納し、その値を参照してCharacter Blend Shapes Extract SOPを行いBlend Shapeが適用された状態のメッシュを作ります。このメッシュに対してPoint Deform SOPによる変形を行い、Pack SOPでpacked primitiveに戻します。
最後にCharacter Blend Shapes Add SOPを使用して、変形させたBlend Shapeをまとめて衣装に設定しなおしてあげています。
Houdiniを使用してアバター改変を行おうとするとアバターのブレンドシェイプが破壊されるケースがよくあります。そのような場合に、私はブレンドシェイプに修正を加えるというよりも改変後のモデルにすべてのブレンドシェイプ作り直す方針で実装を行うことが多いです。今回もその一例でした。
おわりに
今回は素体の大ざっぱな形状を表現したラティスを自力構築する方針で作ってみましたが、最近はTopo Transfer SOPで素体の対応関係を構築する方法に興味があります。
Houdiniを使用したアバター改変はとても楽しい体験です。KineFXでweightやrigやblend shapeを操作する感覚を覚えるとBlenderでアバターの細かい調整をする気がまるで起きなくなります。
みなさんもプロシージャルアバター改変、やってみませんか。