Houdiniを使用したLottieアニメーション作成

# はじめに
以前、Houdiniで作成したアニメーションをLottieとして書き出す機会があったので検証したことを備忘も兼ねてまとめてみます。
# Houdiniとは
『Houdini』はカナダのSide Effects Software社によって開発された3DCGソフトウェアです。
ゲーム業界や映像業界で幅広く使用されているツールで、その用途の幅広さからwebサイトで使用されるようなシンプルなモーションの制作から大規模なエフェクト制作など様々なグラフィックやツールを制作できます。

# Lottieとは
『Lottie』とはAirbnbのエンジニアが開発したJSONベースのアニメーションファイル形式です。
JSONベースのベクターデータとして表示されるため、軽量かつどれだけ拡大しても画質が美しいといった特徴があります。

# HoudiniからLottieを作成
以下の手順で作成していきます。


## 1. Houdiniでのアニメーション作成
・この段階で、アニメーションがLottieの仕様に適しているかを考慮する必要があります。
参考(公式のサポートされるアニメーション表):
https://airbnb.io/lottie/#/supported-features
・今回は簡単に以下のようなアニメーションをHoudiniで制作しました。

![toy_turn_anim.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/860a5f05-3f26-caf1-d385-dc9060a9fc66.gif)
## 2. HoudiniからSVGファイルとして書き出し
・Houdiniには直接Lottie用のJSONファイルをエクスポートする機能がないため、ベクター形式のファイルをエクスポートする必要があります。
・今回はSVGファイルを連番でエクスポートします。

書き出しフローはこちらの記事を参考にさせていただきました。
https://procegen.konstantinmagnus.de/convert-meshes-to-vector-graphics

#### 1. 3Dで制作したものを2D化する
・今回はNDCを利用して、特定のカメラ視点の2D化を実行します。
・`Attribute Wrangle`ノードに以下を記述します。

```c:VEX point wrangle
string cam = chs('camera');

vector pos = toNDC(cam, v@P);
pos.y *= 9.0/16.0;
f@d = pos.z;
pos.z = 0.0;

v@P = pos;
```

![3dto2d.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/64ea9300-e5b1-969b-b437-0ff061d3e5de.png)
![attwrangle.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/6061f490-3a96-a2bc-0954-8e442507d65d.png)

#### 2. パーツごとにクラスアトリビュートを持たせる
・今回はここで用意したクラス毎にパスを書き出すような仕様にします

![class.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/c0f5819d-b505-f6c4-f8ee-2e0144e30a42.png)

#### 3. SVGエクスポート
・`Python`ノードにジオメトリデータからSVGファイルを生成するスクリプトを記述します。
・`Python`ノードをcook、再生スタートすることで書き出しが実行されます。

```python:export_SVG
node = hou.pwd()
geo = node.geometry()

#書き出しファイル名と解像度パラメータを用意
file = node.evalParm('file')
res = node.evalParmTuple('res')

#SVGのパスデータを作成
def write_path(fp, points, color):
  if not points:
    return
  data = ' '.join(['M{:.1f} {:.1f}'.format(points[0].x(), points[0].y())] + ['L{:.1f} {:.1f}'.format(p.x(), p.y()) for p in points[1:]])
  color_string = "rgb({},{},{})".format(color[0]*255, color[1]*255, color[2]*255)
  fp.write('<path d="{}Z" style="fill: {}; stroke: {}; stroke-width: 0.8px;"  class="s" />\n'.format(data, color_string, color_string))

#SVGファイルのヘッダ部分を記述
with open(file, 'w') as fp:
  fp.write('<?xml version="1.0" standalone="no"?>\n')
  fp.write('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
  fp.write('<svg width="{}px" height="{}px" version="1.1" xmlns="http://www.w3.org/2000/svg" >\n'.format(res[0], res[1]))
  
  classes = set([prim.attribValue('class') for prim in geo.iterPrims() if prim.type() == hou.primType.Polygon])

#クラス毎にパスを書き込み  
  for cls in classes:
    name = next((prim.attribValue('name') for prim in geo.iterPrims() if prim.attribValue('class') == cls), 'No name')
    fp.write('<g id="class_{}" title="{}">\n'.format(cls, name))
    for prim in geo.iterPrims():
      if prim.type() != hou.primType.Polygon or prim.attribValue('class') != cls:
        continue
      points = [v.point().position() for v in prim.vertices()]
      color = prim.attribValue('Cd')
      write_path(fp, points, color)
    fp.write('</g>\n')
  
  fp.write('</svg>')
```
![python.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/42175559-dbdc-5b03-1c60-5b6739904ac2.png)

## 3. AfterEffectでのインポート・エクスポート
Lottie用のJSONファイルは通常、Adobe After EffectsとBodymovinプラグインを使ってエクスポートします。
そのため事前に[Bodymovin](https://exchange.adobe.com/apps/cc/12557/bodymovin)プラグインをAfter Effectsにインストールしておきます。

#### 1. After Effectsにアニメーションをインポート
・出力したSVGをAfter Effectsに読み込みます。
・といきたいところなのですが、After Effectsは現在SVGファイル非対応のためSVGをAiファイルに変換する必要があります。
・Aiファイルに変換できたところで、After Effectsにインポートしましょう。
・コンポジションに連番で配置し、デュレーションを［1］に変更します。レイヤーを選択し、右クリック→［キーフレーム補助］→［シーケンスレイヤー］を適応すると簡単に連番に並べることができます。

![ae.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/1fb69321-3ad9-fd06-03e1-9092f6f07966.png)

・すべてのレイヤー選択して、右クリック→［作成］→［ベクトルレイヤーからシェイプを作成］
・Aiレイヤーはこの時点で不要になります。
![convert_.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/b067efe3-24d5-7a08-c29b-0720b048619e.png)

#### 2. Bodymovinを使用してLottie用のJSONファイルに書き出し
・拡張機能を使用できるように設定する必要があるので、メニュータブから、［After Effects］→［設定…］→［スクリプトとエクスプレッション］→「スクリプトによるファイルへの書き込みとネットワークへのアクセスを許可」にチェックします。

![script.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/b78fc689-3ef1-0eda-a91a-c0df87b4bd04.png)

・［ウィンドウ］→［エクステンション］→［Bodymovin］を選びます。
・Bodymovinのパネルが開いたら

　1「Selected」列の〇をクリックします。

　2 「…/Destination Folder」列の［…］をクリックし、書き出し先を選びます。

　3 ［Render］ボタンをクリックすると書き出しになります。
 
![body.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/89e32030-48da-0afe-d859-903b530f195e.png)


## 4. Lottieプレビューページで正しく再生されるか確認
公式のプレビューページで、書き出したLottie用のJSONファイルを確認できます。

https://app.lottiefiles.com/preview

![2024-12-10 07-46-17_.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3946930/c8a3eb77-2485-b483-05b7-91bb428966ea.gif)


# おわりに
web系の案件で度々耳にするLottieですが、3DCGツールからの書き出し方法があまり見当たらなかったので今回まとめてみました。
もっと良い方法がありそうですが、現状の検証だとこれでうまく動作したかなという検証結果です。(よりスマートな方法あればぜひシェアください！)
ほかの3DCGツールの場合でも後半のAfter Effectsからの流れは同じかと思うので、参考になれば幸いです。

最後までお読みいただきありがとうございます！