HoudiniでのFBXの編集


# ■ はじめに
Houdini 18.5からKineFXが追加されましたが、このKineFXによりFBXが大幅に扱いやすくなりました。以前はobjファイルでHoudiniと他のDCCツールとのやり取りを行っていましたが、現在のバージョンであればFBXでの運用がよさそうです。ただ、HoudiniでFBXのモデル情報にアクセスする情報があまりないようでしたので、基本的なアクセスや編集の方法をまとめてみました。

# FBXの読み込みと書き出し
FBXの読み書きは多くの情報がネットでありますので、とくに迷うことはないかと思いますが、「FBX Character Import」ノードなどで読み込むことができます。objファイルと異なり、FBXに含まれる単位情報も利用できるため、インポート時にモデルのサイズがおかしくなることはありません。
![2021-11-23 10_30_52-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/700c7ce8-ed17-2b8a-af84-3041b20c4276.png)

FBXの書き出しは「ROP FBX Character Output」などで行います。「ROP FBX Character Output」ノードでは出力時にジオメトリ情報とレストポーズが必須となり、アニメーション情報は任意となります。
![2021-11-23 10_43_15-untitled.hip - Houdini Apprentice Non-Commercial 19.0.383 - Python 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/096e9ec6-ff2a-f983-5de0-d61a6f820c48.png)


# ■ マテリアルへのアクセス
FBXのマテリアル情報はPrimitivesのアトリビュートの「material_override」に格納されています。これをWrangleノードなどでjson形式でデコードすることでマテリアル情報にアクセスすることができます。

注意点としては、「material_override」に格納されている文字列はjson形式とはやや異なるフォーマットで、「"」であるべき箇所が「'」となっているため、置換を行う必要があります。また、WrangleではなくPythonでアクセスする場合は、最後の要素の後の「,」も削除する必要があります。

**マテリアルのテクスチャパスの取り出し例**
![2021-11-22 20_29_19-D__PerforceWork_Tools_Houdini_Reduction_Atlus.hipnc - Houdini Apprentice Non-Com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/c58aac4e-5f5f-4c27-9854-ec8f0b938686.png)


**マテリアルのテクスチャパスの書き換え例**
![2021-11-22 21_20_45-D__PerforceWork_Tools_Houdini_Reduction_Atlus.hipnc - Houdini Apprentice Non-Com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/04390fcb-dfd9-f7ed-df95-fb2e7cd0e86d.png)


# ■ マテリアルの割り当て
Kine FXでFBXをインポートしたモデルのマテリアルは、「FBX Character Import」などノード内のマテリアルネットワーク（名前はmatnet1）に対してPrimitivesのアトリビュートの「material_override」の情報で書き換えることで各マテリアルの情報を切り替えています。しかし、Labs Map Bakerなどを使用する際に、テクスチャごとに異なるマテリアルで分けたい場合があります。そのような複数のマテリアルを割り当てたい場合は、新たに割り当てる必要があります。

下記のコードはPythonでテクスチャごとにマテリアルを割り当てる例になります。

```
import os, json

node = hou.pwd()
geo = node.geometry()
contextNode = hou.node("/mat")
shopExists = geo.findPrimAttrib("shop_materialpath")
matOverrideExists = geo.findPrimAttrib("material_override")

nodeType = hou.nodeType(hou.vopNodeTypeCategory(), "principledshader::2.0")
mats = nodeType.instances()
matPaths = []

for mat in mats:
    matPaths.append(mat.path())

for prim in geo.iterPrims():
    matOverride = prim.attribValue('material_override')
    if not matOverride:
        continue
        
    matOverride = matOverride[:-2]+'}'
    matOverride = json.loads(matOverride.replace("\'", "\""))
    texPath = matOverride['basecolor_texture']

    fileName = os.path.basename(texPath)
    texName = os.path.splitext(fileName)[0]
    matPath = "/mat/" + texName
    prim.setAttribValue(matOverrideExists, "{}")
    
    if matPath not in matPaths:
        mat = contextNode.createNode("principledshader::2.0", texName)
        mat.parm("basecolor_useTexture").set(1)
        mat.parm("basecolor_texture").set(texPath)
        prim.setAttribValue(shopExists, mat.path())
        matPaths.append(mat.path())
    else:
        prim.setAttribValue(shopExists, matPath)
```

# ■ ボーンの編集
ボーンは「FBX Character Import」などでインポートした場合、「Points」にボーンの情報が格納され、「Vertices」で親子関係が定義されます。

また、モデルのボーンの移動や回転を試したい場合は、「Rig Pose」ノードでボーンの姿勢を変更し、「Bone Deform」でスキニング結果を確認することができます。
![2021-11-23 10_38_03-untitled.hip - Houdini Apprentice Non-Commercial 19.0.383 - Python 3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/b3fb19a2-62e7-12e9-4574-d44c7d5bf9e4.png)

### ボーンのライン化
ボーンの親子関係の構造は、一見PolyLineで繋がって作られているように見えますが、実際には１つ１つのラインは独立しています。そのため、連続したラインとして編集したい場合は、一度プリミティブを削除してPolyLineで繋げることで編集しやすい構造になります。

手始めにプリミティブはDeleteノードで削除しますが、この時に「Points」にあるボーン情報を維持させるために「Keep Points」にチェックを入れます。
![2021-11-23 08_41_19-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/290f3e61-c08d-a409-0f7a-ae5f0baada63.png)

次に連続したラインとして扱えるようPolyLineでプリミティブを再構築します。下図のWrangleはボーン名の末尾に親子関係を示す連番がある場合の構築例ですが、新しく追加したアトリビュートの「no」は次のソートの説明で使用します。
![2021-11-23 08_33_26-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/4ccc5634-b85a-78de-564a-29dbc7de416b.png)

「Vertices」に格納されているボーン情報ですが、親子関係順に格納されているとは限りません。Sortノードなどで親子順に並び替えると、ラインを編集するノードで問題が発生しにくくなります。下図の例では、1つ前のWrangleで追加した「no」のアトリビュートを使って並び替えを行っています。
![2021-11-23 08_34_25-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/ad1f9327-b4e0-137f-3304-84aa2a8386f8.png)

### ボーン数の制限
Klabさんの公演で紹介されていましたが、「Capture Correct」ノードでボーンの最大数を設定することができます。スマホなどの低スペック用にリダクションする場合などに使用します。下図のように「Limit Regions」にチェックを入れ、「Maximum Regions」で最大数を設定します。
![2021-11-23 10_21_51-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/204124f3-b51a-f924-5b97-0dca5206a05b.png)

### MayaのTransformはJointへ変換される
これは現在のKine FXでFBXを出力した場合の制約と考えてよいかと思いますが、MayaのTransformはHoudiniからの出力時にJointに強制的に変換されてしまいます(MotionBuilder上ではNullがSkeleton Nodeに変換されます)。Kine FXでFBXの読み込みと書き出しを行うだけでも、以前の情報が全く同じ状態では維持されないことに注意する必要があります。


# ■ ウェイトへのアクセス
スキニング用のボーンのウェイト情報は「Points」の「boneCapture」アトリビュートとして格納されています。「boneCapture regn」がボーンのインデックス、「boneCapture w」がウェイト値に該当します。
![2021-11-23 15_02_16-Geometry Spreadsheet_  enum_geo.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/9bba97c4-b57c-b5cd-f646-45231ae09a09.png)

これらの情報ですが、そのままではアクセスすることができず、「Capture Attribute Unpack」ノードでアンパックすることでアトリビュートにアクセスできるようになります。
![2021-11-23 10_55_08-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/82584156-54ba-6586-4c29-fc3cafb13acc.png)

また、アンパックすることで「Detail」に「boneCapture_pCaptData」と「boneCapture_pCaptPath」の2つのアトリビュートが追加されます。「boneCapture_regn」で使用されるボーンの配列インデックスは「boneCapture_pCapPath」のリストのインデックスで構成されています。
![2021-11-23 15_05_19-Geometry Spreadsheet_  attribwrangle_merge_bones.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/045747d2-7910-c90f-cb56-97f0371ea22c.png)

「boneCapture」の編集後は「Capture Attribute Pack」ノードでパックし直します。なお、foreach内でアンパックした場合は、foreachから抜けると自動的にパックされるようです。
![2021-11-23 15_13_44-D__PerforceWork_Tools_Houdini_Reduction_Bone.hipnc - Houdini Apprentice Non-Comm.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/fc98e08a-d31f-690e-dd07-371ac5c0d959.png)


# ■ ブレンドシェイプへのアクセス

ブレンドシェイプは「Primitives」にパックされた形で格納されています。まず、編集しやすいよう他のメッシュなどのデータと分離します。「@blendshape_name」が空であればそのデータはブレンドシェイプではありませんので、Splitノードなどで分離しておきます。
![2021-11-23 15_49_24-D__PerforceWork_Tools_Houdini_Reduction_Model.hipnc - Houdini Apprentice Non-Com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/44efa259-ca0a-e54c-9e83-1c24323984b4.png)

次にforeachで要素をPrimitivesに指定し、Unpack後に各ブレンドシェイプにアクセスできるようになります。下図ではUnpackノードとPackノードの中で必要な処理を行います。
![2021-11-23 15_57_37-D__PerforceWork_Tools_Houdini_Reduction_Model.hipnc - Houdini Apprentice Non-Com.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/78487/fe8b25a5-9fe5-521c-b3e6-4d5c1b06e1da.png)

なお、ウィエト(boneCapture)と違い、Packノードの後にいくつかやっておく処理があります。

+ GroupCreateノードで「_3d_hidden_primitives」の名前でグルーピングする
+ VisibilityノードでPrimitivesを非表示にする
+ Unpack/PackノードによるPrimitivesのアトリビュート（name/blendshape_channel/blendshape_name/shop_materialpath）が失われてしまうので復旧させる