ポリゴン面積を考慮したテクスチャアトラスに関して

##テクスチャアトラスとは？
テクスチャアトラス（又はアトラステクスチャ）とは、下図のように複数の画像を1枚にまとめた画像をテクスチャアトラスと言います。おもにドローコールの軽減に使用します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/42990428-c04b-bd5a-60bd-dc12ec9b23b5.png)
このように均等に配置する場合はMosaic COPを使用すること作成することができます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/ab6a9dcb-e2a9-f635-bfe9-2549c02fde25.png)
ですが、使用する画像は3Dモデルにテクスチャマッピングする事を想定していますので、適切な画像解像度はモデルに依存します。そこで今回はモデルの面積を考慮したテクスチャアトラスの生成方法に関して紹介します。

##ポリゴン面積を考慮したテクスチャアトラスとは？

今回使用するモデルはこのように大きさの異なる複数のモデルです（1モデルあたりテクスチャは1枚、UVは0~1とします）。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/3e195f32-2137-d682-2224-b1b84ceb5d5e.png)
これらのモデルの面積を考慮したテクスチャアトラスを作成するのが目的です。モデルの大きさが考慮された結果になっているのがわかると思います。ライトマップをアトラス化するなどを想定しています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/6dd1f0ea-3110-ee49-a997-d3f24c90e17e.png)


##実現にあたって

* できるだけ小さい実装
* できるだけコードを使用しない

を心がけて実装を行いたいと思います。

###ノードネットワーク
完成版のノードネットワークはこのようになっています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/e4c79eed-3577-43b8-dd2e-9d6429afabd9.png)
※グレーのネットボックスはビジュアライズ用なので解説は省略します。

ノードネットワークは5つに分類されています。

* メッシュのインポート
* メッシュへアトラス処理のための情報を追加
* アトラス化の配置情報の算出
* パラメータ管理
* アトラステクスチャ生成COPネットワーク

順追って解説します。

#### メッシュのインポート
今回は内臓さているテストジオメトリーを3種、それぞれサイズを2種の合計6モデルを用意しました。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/cb360c85-f98a-2627-4ffd-0ce6e27576da.png)
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/3e195f32-2137-d682-2224-b1b84ceb5d5e.png)

### メッシュへアトラス処理のための情報を追加
アトラス処理向けにアトリビュートを追加します。今回はテクスチャパスを追加し、処理しやすいようにパックします。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/75c0dfb8-93b1-b258-30af-714db1e753fb.png)

Wrangleを使用してDetailへテクスチャパスを追加します。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/21f94b1a-b9f2-433f-0942-d70843a00ec1.png)

次にPack Primitiveへ変換し、その後マージして一つのノードにまとめます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/077d5cd7-cbb6-b2ea-84c7-48ffab567d5c.png)

### アトラス化の配置情報の算出
今回の重要な部分はここに集約されています。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/68b00f5b-2118-9e38-feb1-d729ac68d381.png)
面積を考慮したアトラステクスチャのレイアウトはUV Layoutを使用しています(※できるだけコードを使用しないで機能を実現するのが目的の一つですので、肝心な部分は既存のノードを使用します)。

まず普通にUV Layoutを実行するとこのような結果になります。これはこれで素晴らしい結果ですがUVが変更されているためアトラス化できません。 ※UV LayoutのCorrect Area Proportionsを有効にするとポリゴン面積を考慮してくれます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/a65080e0-e780-5f61-7f2f-8df3ea5b7d55.png)

で考えた結果、各モデルの面積を反映した四角に置き換える事にします。手順は以下になります。

1. モデルを一つ取り出す
2. Measure SOPで個々のポリゴンの面積を取得
3. Attribute Promote SOPでポリゴンの総面積を算出
4. 総面積を反映した四角をGrid SOPを使用して作成
5. 1~4をメッシュ分繰り返してマージする
6. UV Layoutでレイアウトする

結果、以下のUVが生成されます。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/69eea4e4-70b4-6a8f-639b-dc06a1c981ab.png)

これでアトラス化のレイアウトは完了です。

### パラメータ管理
アトラス化した画像を生成するまえにパラメータ管理を解説します。
パラメータはNullに必要なパラメータをまとめています。HDAであればパラメータを一元して扱う機能がありますが、いったんNullにまとめておけば、作業も早くなりますし、HDA化する際もUIの構築が楽になります。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/144656/5986b9f1-8d5f-4c0d-ba3c-30c13ca80fa0.png)

### アトラステクスチャ生成COPネットワーク
最後にアトラス情報を元にテクスチャを一枚にまとめる方法を解説します。HoudiniにはCOPと呼ばれる画像合成機能が搭載されていますので、COPを使用して複数画像を一枚にまとめます。
ここだけはPythonを使用して画像を一枚にまとめるネットワークを構築します。コードはできるだけシンプルにしたつもりですが、流れを解説します。アトラス化の配置情報は画像枚数分の四角メッシュで構成されていて、それぞれUVをもっていま。このUVがそのまま配置情報になっています。

1. アトラス化の配置情報からUVを取得
2. UV値からオフセット、スケール値を算出
3. file、scale、compositeノードをcreateNode関数で作成し、parmでパラメータを設定し、setInputでノード間をつなぎます
4. 2~3を繰り返す
5. 合成画像完成
6. ROPなどでファイルに書き出す（今回は追加していません）
7. ついでにアトラス化情報をJSONに変換

````
import json

def CreateAtlas(parameters='/obj/ATLAS/PARAMETERS'):
    parms = hou.node(parameters)
    cop = hou.node(parms.parm('cop').eval())
    geo = hou.node(parms.parm('geom').eval())
    image_path = parms.parm('image_base').eval()
    canvas_size = parms.parm('size').eval()
    # delete exists cop nodes
    cop.deleteItems(cop.allItems())
    
    # build atlas
    
    canvas = cop.createNode('color')
    canvas.parm('overridesize').set(True)
    canvas.parm('size1').set(canvas_size)
    canvas.parm('size2').set(canvas_size)
    canvas.parm('colorr').set(0)
    canvas.parm('colorg').set(0)
    canvas.parm('colorb').set(0)
    
    src = canvas
    
    atlas_uv = []
    
    for p in geo.geometry().iterPrims():
        uvmin = [1.0, 1.0]
        uvmax = [0.0, 0.0]
        for v in p.vertices():
            uv = v.attribValue('uv')
            uvmin[0] = min(uvmin[0], uv[0])
            uvmin[1] = min(uvmin[1], uv[1])
            uvmax[0] = max(uvmax[0], uv[0])
            uvmax[1] = max(uvmax[1], uv[1])
        file = cop.createNode('file')
        file.parm('filename1').set(os.path.join(image_path, p.attribValue('tex')))
        w = file.parm('size1').eval()
        h = file.parm('size2').eval()
        scale = cop.createNode('scale')
        scale.setInput(0, file)
        sw = (canvas_size*(uvmax[0]-uvmin[0]))/w
        sh = (canvas_size*(uvmax[1]-uvmin[1]))/h
        scale.parm('imagefract1').set(sw)
        scale.parm('imagefract2').set(sh)
        comp = cop.createNode('composite')
        comp.parm('op').set(6)
        comp.parm('tx').set(uvmin[0])
        comp.parm('ty').set(uvmin[1])
        comp.setInput(0, scale)
        comp.setInput(1, src)
        src = comp
        # display uv scaling info
        atlas_uv.append({"offset":uvmin, "scale":[uvmax[0]-uvmin[0],uvmax[1]-uvmin[1]]})

    parms.parm('atlas_uv').set(json.dumps(atlas_uv, indent=2))
    src.setCurrent(True)
    src.setDisplayFlag(True)
    src.setRenderFlag(True)
    src.cook(force=True)    # for just in case.
    cop.layoutChildren()
````

Pythonコードはhou.sessionに設定しています。

## おわり
もしかするとUV Layoutだけで実現できる方法があるのでは？、という疑念を拭いきれませんが、この機能でできそうなんだけど惜しいなぁという時は、工夫することで実現できてしまう事が多いのも、Houdiniの強みの一つだと思います。


HIPファイルはこちらから
https://www.dropbox.com/s/de672kg1u17wkh8/texture_atlas.zip?dl=0