UnityでUSDを使ってみたい

# はじめに
この記事は[株式会社アプリボットのAdvent Calendar](https://qiita.com/advent-calendar/2024/applibot "株式会社アプリボットのAdvent Calendar")14日目の記事です

はじめまして、株式会社アプリボットのアセット制作支援組織、YojiGen所属TAのholyです

この記事では主に今話題の[OpenUSD (Universal Scene Description)](https://openusd.org/release/index.html "Open USD")の利用が可能なUnityパッケージについて軽く触れたいと思っています

# Open USDとは？
USDとは、Universal Scene Descriptionの略称です
※本記事では以降略称USDを用います

USDは映画等で有名なPixar Animation Studiosが開発した、
次世代のアセットパイプラインとワークフローを実現するためのフレームワークです

このフレームワークを用いることで、プロダクションワークフロー上今までは難しかったシーンの同時編集が可能になることや、FBXなど既存ファイルフォーマットでは取り扱いが難しかった拡張情報をかなり高い自由度で埋め込めるようになります

またUSDで利用可能なファイルフォーマット```.usd```は、```.fbx```や```.obj```等と同様に異なるアプリケーションをまたいでモデルやマテリアル、アニメーションを行き来させることができるだけでなく、異なるアプリケーションで同じファイルを編集できるようになります

さらに知りたいと言う方は[公式のドキュメント](https://openusd.org/release/index.html "Open USD")か[USD とは (2022 年改定版)](https://fereria.github.io/reincarnation_tech/usd/what_is_usd "USD とは (2022 年改定版)")をご覧いただければさらに詳しく知ることができます

https://openusd.org/release/index.html

https://fereria.github.io/reincarnation_tech/usd/what_is_usd

いままではC++によるSDKのビルドを行わないと拡張ができなかったのですが、
昨今は[pythonでバインドされたパッケージがpipでインストールできる](https://pypi.org/project/usd-core/ "usd-core")ようになったり、
参入のしやすさも格段に増してきています

USDの最新情は是非以下の世界的なUSDの標準化運動を行うコミュニティーを追いかけてみてください

https://aousd.org/

# 簡易的な紹介

## USDは同時編集が可能
同時編集とはワークフロー内で多職種が同時多発的に同じファイルを編集することを指します
なぜこれが可能なのかというと、それはUSDの特徴であるレイヤー管理にあります

## USDのレイヤー管理
USDで提供さているAPIには文字列でオブジェクトやメタデータの情報を管理する仕組みがあります

USDではオブジェクトやメタデータの最終合成処理を __Stage__ と呼ばれるシーングラフでおこなっているのですが、
このシーングラフ内の編集を __Layer__ という単位で個別に分割管理できるようになっています

__Layer__ のイメージ詳細は[こちら](https://fereria.github.io/reincarnation_tech/usd/stage_layer_spec "stage_layer_spec")が非常にわかりやすいため是非訪れていただくと詳細に理解できるかと思います

上記から抜粋してシンプルな部分のみを解釈するなら、
MayaのシーンやUnityのシーンやFBXのシーン＝これはシーングラフ= __Stage__ です

__Layer__ はそれらシーングラフに反映するグラフ要素の編集状態の保存機能です

また __Layer__ はRootLayer（大抵はそのusdのメインStageです）の下にネストして __SubLayer__ を持つことができるためシーングラフ内に異なるシーングラフをネストできます

このため __Layerを利用したシーン編集__ はUnityのプレハブ編集やMayaのリファレンス編集に近しいと思います

そういった編集をFBXでは行うことができませんが、USDでは可能になるというのが強みです

## レイヤー管理と作業者ごとの作業構造例

```
AnimationStage (subLayer=animation.usd)
┗ skelAnimation(prim) ＝＞　アニメーターはここだけいじる
┗ ModelStage (subLayer=model.usd)
  ┗ geometry(prim)　＝＞　モデラーはここだけいじる

// プレハブにおきかえると
AnimationPrefab
┗ PlayablePrefab (PlayableDirector)
    ┗ Playable(animationCLip)  => アニメーターはここだけいじる
┗ ModelPrefab
    ┗ SkinnedMeshRender や Transform   => モデラーはここだけいじる
 ```

※prim こちらは後ほど軽く触れます

上記のような構造で作業が可能です
それぞれの担当者がこのレイヤー単位(usd単位）で編集を行うことで作業分担が効率化できます 

USD自体はここまでにして、さっそくUnityで扱っていきます

# UnityのUSDパッケージ
Unreal EngineではすでにUSDを取り扱うためのプラグインが公式より公開されています
ではUnityではどうかというと、もちろん利用することが可能です！

https://docs.unity3d.com/Packages/com.unity.usd.core@1.0/manual/index.html

__ただし！__

まだUnityではUnity2023以降のバージョンかつ、
Preバージョンということで正式なリリースにはなっていないのが現状です
※ 2024年現在

とはいえUnity6でも使えるということで、さっそくパッケージ導入方法を見ていきましょう

## この記事で取り扱うアプリケーションのバージョン情報
Unity 6 (6000.0.24f1)
Maya 2025.3

:::note warn
Maya2023などからUSDPluginが利用できますが、マテリアルを操作できるのはLookDevX正式導入後のMaya2025からになります
:::

# Unity USD パッケージについて
ではさっそくパッケージのインストールを行っていきたいですが、その前になにをインストールすればよいのか見ていきます

2024年現在、UnityのUSDパッケージは3種類あります
* USD Core
* USD Importer
* USD Exporter


[Unity USD Core](https://docs.unity3d.com/Packages/com.unity.usd.core@1.0/manual/index.html "Unity USD Core")によると、ImporterやExporterはCoreに依存しているためImporterかExporterのパッケージをインストールすれば自動的にCoreがインストールされるようです

# USD Importerのインストール
今回はMayaで作成したUSDファイルをUnityで読み込ませて見ようと思いますので、
公式の手順にしたがいImporterをいれていきます

上部メニューからWindow>Package ManagerでPackageManagerを起動します

PackageManagerの+ボタンから```install package by name```を押下して、
出てきたダイアログに```com.unity.importer.usd```と入力します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/b6969a66-0a54-3305-3a1e-74600cedd975.png)

インストールが完了しました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/a1688286-692c-0468-6387-10d6789e1765.png)

# なにか読み込ませてみる
ということで、弊社が公開しているアニメーション用フリーモデルをUSDに変換して利用します

https://www.applibot.co.jp/recruit/3dadopt/3drig/

MayaでUSD化を行います
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/3859d7ad-1690-dec5-5341-0f118b282209.png)

Unityのプロジェクト内に保存します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/7468477b-569d-c539-6744-418fc4f65b9f.png)

なにやらおかしいです...

## 原因を特定する
原因はすぐ判明しました
が原因を追求するためにまずはインポーターを見ていきましょう

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/e946784c-a089-22b2-707d-b8aebf401ba1.png)

画像のようにFBXのインポート時のInspectorとはだいぶかけ離れた見た目をしていますが、
よく見てみると怖いものはありません

しいていうならモデル、モーション、テクスチャのタブが統合されたメニューになっただけといった風貌です

見慣れたオプションに着目しすると

* Recalculate mesh normals

とあるではないですか、シェーディングがおかしいのなら間違いなくこれでしょう
チェックをいれて再インポートをかけます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/fbf21467-902e-97b2-ba8a-905fd77530b8.png)

やりました

# なんで起きた？
USDのドキュメントに目を通すと標準では __座標系を右手座標のものとして__ 情報を保存しているとありました
Mayaはタンジェントスペースをいじらないかぎりは右手座標系なので特に変換を掛ける必要はありませんが、
Unityはワールドスペースが左手座標系になるため変換が必要そうでした

ということで基本的にUnityでは```Recalculate mesh normals```のチェックを有効にしましょう

ただしUSDは手軽に拡張が可能なので左手座標の情報を拡張して保存しておき、
UnityのFBXSDKを用いるかのようにUSD Coreを用いてインポーターを作成しておけば、
好きなようにインポートも可能なようです（今回は触れません）

もう一つ問題があります

# テクスチャを読み込みたい
そういえばテクスチャがきてません

FBXを扱っていればfbmでないかぎり、MayaでUnity側との相対的なテクスチャパスが一致していないとインポート時にテクスチャがマテリアルに反映されていないのは必然です

USDではどうかというと、もちろん相対パスの保存が可能です
それに加えてfbmと異なりusdzというフォーマットを用いることでなんとサブアセットにテクスチャ込でインポートすることも可能です

## USDのファイルフォーマット
USDで扱えるファイルフォーマットは4種類あります

* .usd
    * この形式でもASCIIとBinary保存の2種類を選択できます
* .usda
    * 明示的にASCIIデータであるフォーマットです
* .usdc
    * 明示的にBinaryデータであるフォーマットです
* .usdz
    * fbmのようにusd内にテクスチャなどのリソースをそのまま保存できます

# テクスチャを持ってきたいぞ

Mayaではテクスチャをsourceimagesに保存しておいて相対パス保存しておけば共同作業時便利になります

とはいえ、じゃあUnity側もsourceimagesを参照しにいけるかというとUnityではプロジェクトのAssets以下に読み込める状態でなければ参照することができません

私が過去構築したパイプラインでは大抵sourceimagesにあるテクスチャをモデルのFBXと一緒に、Unity側で構築したフォルダ階層へ一緒に出力またはコピーをおこないUnity側で解決を行っていました

fbmを使えば埋め込みが可能でしたが、fbmは同じ階層に結局リソースが展開され不便になります

ところが、UnityのUSD Importerではusdzフォーマットであればなんと、読み込んだusdファイルの __サブアセットとして埋め込みリソースを読み込む__ ことができます

https://docs.unity3d.com/Packages/com.unity.importer.usd@1.0/manual/Importing.html
 ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/12d4d9c5-bbf7-b76a-0eca-ff2105216fca.png)

# とりあえずusdzフォーマットで読み込みなおしたい
さっそくMayaでusdzを書き出したいですが...
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/08a2409c-98d0-2074-b18a-b2109bd4d01d.png)

どうやら現時点では書き出しオプションがないようです

それと便利ではあるがテクスチャのみ修正したい場合にusdz内のテクスチャを毎回弄りにいくのか？
と、言われるとちょっと違う気がしてきました

であれば別の手を考えます

# 相対パス保存フローでいこう

USDは参照のあるファイルをUSD自身を基点とした相対パスで保存できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/7be806f9-10ff-f923-3ab3-987c3ab73e93.png)
ですのでMayaでモデルをUSD出力するときにMaterialsの設定でテクスチャパスをRelative保存します

Mayaのシーンではこのような相対パスでシーンを保存していました
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/87e58a2d-13c9-ce39-b8ef-d7ae4f06bf49.png)

よし！
ということで、ちゃんと相対パス保存されたかusdのStageを一度Mayaでロードしてみます

Maya2025であれば、LookDevXでUSDのマテリアルグラフをBifrostのグラフライクに見ることができるので確認します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/173926fc-1654-66c9-f30c-451445966226.png)
んーおかしいですね

他にもStageツリー内のTextureFileノードのアトリビュートを直接見るか、
.usdをASCIIで保存すればUSD内のデータをそのまま目視で確認することもできます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/cc7fb1bf-f68d-4e75-3852-cdbcde7cb1dc.png)

想定していた相対パスではないですね
これは困ります

これはMayaでUSDを作成する場合に、USDの相対パス管理をMaya基準で行ったのが原因でした

Mayaの相対パスの解決ではProjetのルートに設定したフォルダを基準に相対パスを解決しますが、
通常USDは```.usd```ファイルを起点にパス解決が行われます

なのでLookdevXのグラフ編集を行いPathが解決できるようにします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/f4b4f5c5-8d15-13e3-14d1-82c220d88269.png)

:::note
今回は簡易にUnity側にも.usdファイルの親フォルダにsourceimagesを作成し、そこにテクスチャを配置しておきます
:::

Mayaでは2025をインストールしたばかりだとUSDをBinaryで保存してしまいます
プリファレンスでASCIIフォーマットに設定しておきます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/c3715063-f849-5841-7304-b4ae485d7293.png)

USD LayerEditorでUSDに編集結果を反映

Unity側
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/bd48894f-9009-2816-fab3-a3793c898489.png)

ただこれではマテリアルがきていないようでテクスチャを確認できませんでした
ここも簡単に修正できますが、その前に

# USDのデータ構造を理解する
USDはポリモーフィズムにデータの読み書きができるように、実にシンプルな構造でデータが保存されています

構造は以下の画像のような感じです
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/942a0941-f0ac-b6df-e6b0-735d2cb0e336.png)

USDのファイル内ではAPIで公開されたクラス情報などがコード認識可能な形に埋め込まれています

```def``` に続くあとの文字列がオブジェクトの宣言部分です
ここで宣言できるのはUSDのデータコンテナであるPrimとして定義されたオブジェクトの型です

::: note
__Primとは__（引用）
>PrimはUSD の主要なコンテナ オブジェクトです。Prim は他の Prim を含めることができ (順序付けも可能)、Stageに 「名前空間階層」を作成できます。また、Prim は意味のあるデータを保持するプロパティを含めることができ (順序付けも可能) ます 。Prim と、それに関連付けられた計算されたインデックスは、 Stage がメモリ内に保持する唯一の永続的なシーングラフ オブジェクトであり、Prim を操作するための API はUsdPrimクラスによって提供されます。Prim には常に、ステージでの Prim の一般的な役割を決定する解決済みのSpecifierがあり、Prim には、Prim に含まれるデータの種類を指示するスキーマtypeName がある場合があります。Prim は、シーン レベルのインスタンス化、ロード/アンロード動作、および非アクティブ化を適用する粒度も提供します。
:::

たとえば```def Material```とあればMaterialのPrimということになります

ちなみにMaterialPrim内の情報をMaya内で確認するならLookDevXグラフで見れば接続情報がわかります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/2e403916-8b6e-6a58-4577-0999543de0c0.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/49c37894-4575-2f8e-69bc-181199ccedae.png)

# シェーダー情報を追加する

現在USDのAPIで扱えるシェーダーエフェクトは

* MaterialXというライブラリを利用したサーフェスシェーダー
* USDのデータをプレビューするために標準的に同封されている```UsdPreviewSurface```(MaterialXを利用したもの）
* SdrShaderNodeスキーマでglslfxファイルなどを読み込んで構成したもの

これらを使わないと異なるアプリケーション間で情報を共通利用できません

元データを確認すると弊社のフリーモデルはrampshaderを主に利用しておりますが、すこし組み方が独特です
しかもこの規格はLookDevX(つまりMaterialX)で作成されたものではなくMaya専用のものになりますので自動変換がかかる場合にうまく解決できなくなっていました

※よくわかりませんが、なぜかテクスチャから直接surfaceが出ているというものになっています

というわけでUnity側のUSDでも最初から変換を用意してくれている```UsdPreviewSurface```を明示的に利用して保存しなおします

## Mayaでサーフェスシェーダーをつなげる

USD内のマテリアルを編集する場合、アウトライナーのUSDStageツリー内にあるマテリアルのコンテキストメニューから、Show in LookdevXでグラフビューを起動します

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/69b7d5ff-e5df-1471-42ec-390c4b9af1b1.png)

LoodevXでマテリアルが表示されているので、ダブルクリックしてグラフを表示したらタブキーを押下、
USD > USD Preview Surfaceを押してグラフノードを作成します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/5b745030-b9ce-e5a0-85ac-a8f0cff92f7e.png)

:::note
本来USDでマテリアル内にグラフノードを使う場合は明示的にそのスキーマを利用するパラメーターを追加する必要があるのですが、そこはMayaが保存時にオートで行ってくれます
:::

次にテクスチャノードのrgbとaをUsdPreviewSurfaceの各inputにつなぎます

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/e4669dd1-973d-cc09-0324-58b32f7a01ed.png)

:::note
UsdPreviewSurfaceのinputを見てなんとなく察した方は多いかもしれませんが、PreviewSurfaceのシェーダーはBxDFを利用したPBRシェーダーです
:::

すべてのマテリアルで編集をおこなったらUSDファイルに保存を行います

Unity側でも変更が反映されているのが確認できます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/b6909f9a-bc10-b1ff-aeeb-b8efb9acdf51.png)

Maya側の描画です
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/582ab75d-8284-f5d7-eb22-a1f501e31d47.png)

opacityの描画はデプスソートを行わないと前後関係がおかしくなりますが、そのあたりはそれぞれのアプリケーションで行いますので今回は無視します

あとはメッシュを編集するなりしてもUSDに保存をおこなうだけでUnity側に即反映されます
ただ2024年現在のパッケージではUnity内の編集と保存までは行えないようでした

これが行えると双方向で編集が可能になりますね

# アニメーションはどうなる？
冒頭の構造を再び確認します
```
AnimationStage (subLayer=animation.usd)
┗ skelAnimation(prim) ＝＞　アニメーターはここだけいじる
┗ ModelStage (subLayer=model.usd)
  ┗ geometry(prim)　＝＞　モデラーはここだけいじる
```
いまはModelStageのみをUnityに保存して解説していました
アニメーションもusdで上記のように編集と保存ができます

:::note warn
fereria氏のドキュメントにもありますがModelStage自体に編集をおこなってしまうとモデルの構成とアニメーション編集が同時にかきこまれてしまいアニメーションの編集のみを管理しずらくなります
モデルのStageとアニメーションのStageはレイヤーでわけましょう
:::

上記を実現するためにアニメーション用のusdを作成し、その下にモデルのUSDをサブレイヤーとしてロードします
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/1688680c-2314-ba49-b492-000cb49121af.png)

そこで読み込まれているモデルの階層を __Edit As Maya Data__ でMayaで編集可能な状態にしたら
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/ccc63409-f90d-7887-0366-21d517cfda3c.png)

いつものMayaでアニメーションを編集するかのようにアニメーション付をおこないます

アニメーション付ができたらUSDに変更を保存するため __Merge Maya Edits to USD__ でアニメーションを書き込みます
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/a037b077-249c-8f50-3596-b6231a6f9877.png)

そうするとUnityでAnimationCLipがインポートされて利用可能になります
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3936013/14e6e555-712a-d737-21c5-432e83fea6cf.png)

# 課題もある

ただ、__現段階__ ではこのMaya用のデータに変換するというフローからアニメーションをつけるときにコントローラーをつけたままのシーンでアニメーション作業する、というフローはなかなか組みにくいように思います

例えばFBXではコンストレイント情報を書き出しておけばUnityでのインポート時に再現できました
しかしUSDではこの項目がまだ存在しないようです

::: note alert
USDのAPIドキュメントを軽く見る感じConstraintのワードはありませんでした
またMayaのUSDのExportオプションでconstraintの項目がありますが書き出されたconstraintノードはXFormになっておりなんのスキーマもありませんでした
:::

コンストレイントがMaya仕様のDependencyノードになるためこのあたりの互換性を成立させる拡張、つまりはUSD側に専用のスキーマを用意して、それぞれアプリケーションで変換機能を追加する必要があると推測しています

すごく大変そうなのでUSDとそれを扱えるアプリケーションごとにも標準搭載される日を切に願います（他力本願）

でも相当な需要だと思うので近い内にきそうですよね


# まとめ
USDをUnityを使った開発で利用するためにはまだいくつかUSD自体とUnityのパッケージの更新を待つ必要があるようですが、モデル編集のたびにFBXやテクスチャを再出力するといったいままでの弊社フローにおいては、そのあたり簡略化できるので移行したときのメリットが十分あるように感じます

しかしアニメーション周りはまだまだ改善される余地があるように感じます

Mayaのシーンが最終的なレンダリング結果になりえる環境ではない場合シーングラフの解釈を標準のUSDAPIにまかせっきりでは行けなさそうでそれなりに工夫が必要そうです

ただ世界的に標準化する動きのなかにはゲーム業界も含まれている以上、その点は今後なんらかのアプローチがでてきて一般化されていくと期待しています

ありがとうございました

15日目はref3000さんによる、[[GAS] スプレッドシートに限定公開のDrive画像を埋め込む](https://qiita.com/ref3000/items/3ac669620fac34bbab4f "[GAS] スプレッドシートに限定公開のDrive画像を埋め込む")です！
こちらもお見逃しなく！

# 関連リンク集
■ MaterialX DeveloperGuide

https://materialx.org/DeveloperReference.html#DeveloperGuide

■ USDドキュメント

https://openusd.org/release/api/index.html

■ Unity USD Core

https://docs.unity3d.com/Packages/com.unity.usd.core@1.0/manual/index.html

■ USD とは (2022 年改定版)

https://fereria.github.io/reincarnation_tech/usd/what_is_usd

■ Alliance for OpenUSD

https://aousd.org/