モチベーション
PixarUSD は極めて拡張性の高いライブラリです。ツール間のインターチェンジを目的としていることもあり、プラグイン機構を通して OBJ, FBX, Alembic はじめ様々なフォーマットを透過的に扱うことができますが、標準で用意されているフォーマットが以下の3つであり、中でも重要なものが usda, usdc になります。
フォーマット | 拡張子 | 特徴 |
---|---|---|
usda | .usd または .usda | テキストエディタで書けるアスキー |
usdc | .usd または .usdc | ファイルサイズが小さく読み書きの速いバイナリ |
usdz | .usdz | 複数の usd ファイルやテクスチャ、音声などをまとめた非圧縮 zip |
ファイルの拡張子が .usd だと、実際に usda/usdc どちらになっているかはわかりません。むしろリファレンスが絡んでもアスキーとバイナリを簡単にスイッチできるように、どちらも .usd を使うのが推奨されています。
多くの DCC ツールが USD 入出力に対応してきていますが、通常は読み書きの効率のため USDC が使われていると思います。オブジェクトをインポートしたりレイアウトしてレンダリングするといった一般的な用途で USD を使いたい、という向きにはこの程度の知識でも十分ですが、USD を真に理解したいという世界の探究者の皆さまには、まずは USDA を手で書くことから始めるのを強くお勧めします。usda を制するものはパイプラインを制す、と「リングにかけろ」で志那虎もたしか言っていました。
usda/usdc の更なる違い
ここでいきなりディープな話題を差し込んでしまいますが、読み書きの速度以外にもこの二つには決定的な差があります。usda を読み込むときは、ファイルの全てを読み、解析し、メモリ中に展開します。一方 usdc は、オープンしただけでは必要なヘッダ部分だけを読み、実際にデータが必要なところだけを [メモリマップ] (https://ja.wikipedia.org/wiki/%E3%83%A1%E3%83%A2%E3%83%AA%E3%83%9E%E3%83%83%E3%83%97%E3%83%88%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB) 経由で読み取ります。また頂点データなど同一のデータが繰り返される場所では暗黙的に(自動的に)重複排除されるようになっています。たとえば巨大なアニメーションデータがある場合、USDC では全体をメモリに読み込まなくてもストリーミングしながら再生を行ってくれます。一方 usda では全てをメモリに読み込んでからアニメーションの解決が行われます。このため一般的には usdc の方が効率が良いのですが、メモリが大量にあり、ロード後に何度も再生したい、というようなユースケースでは usda の方がオープン後のファイルアクセスが発生しないため、プレイバックは高速になるケースもあります。
python で生成するようなケースではエクスポート時の指定でどちらも簡単に出しわけできますが、この記事ではテキストエディタでゼロから usda を書いていきます。
環境の準備
usda を書くだけならテキストエディタがあれば十分なのですが、正しい書式で書けていることを確認するためにもまず USD 実行環境を用意しましょう。USD は python なしでも使えますが、学習用にも実務用にも python がほぼ必須ですので python 環境を用意してください。どのバージョンがいいか悩むかもしれません。そんな時は https://vfxplatform.com を見ます。ここは VFX・アニメーション業界各社がなるべく環境を統一するべくバージョンをすり合わせている場所で、DCC ツールも最近は原則としてこのバージョンを尊重するようになってきました。
この記事は 2021 のアドベントカレンダーですので、カレンダーイヤー2019 (CY2019)をみると 3.7.x とあります。そこで https://www.python.org/downloads/windows/ をみると 3.7.9 のバイナリリリースがありますので、この x86-64 をインストールします。USD は 64bit 環境で使います。USD 環境はこの python を使って自力ビルドでも、あるいは maya や houdini で提供されている python 環境でも、なんなら mac の標準環境でもよいのですが、自分で用意するなら pip でインストールするのが楽でしょう。
python -m pip install usd-core
で USD のインストールは完了します。文字だけで USD 世界を堪能するのが我々の究極の目的ですから、グラフィックス機能は不要です。ビルドで苦労していた時代、ありましたね...
つぎにテキストエディタです。Animal Logic が VSCode 向けに usd 言語拡張を提供してくれていますので、これをインストールしておきます。
はじめての usda
ではいつものように「光あれ・・・!」とつぶやきながら以下の5行を VS Code に叩き込んでください。
#usda 1.0
def "World"
{
}
おっと初手からいきなり世界を爆誕させてしまいました。前回世界を作ったのはいつだったでしょう... 前世紀に X11 をリビルドしたときだったか。
何にせよあなたはまさにこの瞬間ルビコン川を渡り、 DCC に頼らずその手で USD を記述したのです。もうこちら側の人です。5行でワールド、それどころかユニバースも記述できてしまうのが USD。Autodesk にも SideEffect にも Epic にも頼らずに、10秒たらずで世界も創造し放題なことがお分かりいただけたでしょうか。
しかしちょっと待ってください。USD では厳密に言うと、usda ファイルとは「私はこうしようと思うんだけどな」という 計画の記述 なのです。従ってこの時点ではまだ誕生はしていません。そうなんです。実はあなたの他にも神がいるのです。八百万の神々が集まり本当に世界を生み出すところは出雲(Stage)と呼ばれますが、その話はこのアドカレの別の記事 で @fereria さんが明らかにしてくれるでしょう。
最初の一行、#usda 1.0 はこのファイルがアスキー形式の USD ファイルであることを宣言する重要な文です。二行目であってはいけないし、usda と 1.0 の間にスペースが必要です。それ以外のことを書いてもいけません。二行目は空行をいれることが多いです。作文のときに段落の初めは一マスあける、と小学校で教わったかと思いますが、似たようなものですね。ここで差し込んだ空行に無と無限の宇宙を感じながら瞑想してください。空即是色。USD は仏教思想に強く影響されていることもわかります。自分が神なのか仏なのかわからなくなってきました。なお二行目以降には、# で始めれば好きな場所にコメントを書けます。神の仕事は多くいろいろ忘れがちですので、メモはしておきましょう。コメントは日本語でも大丈夫ですが、UTF-8 が良いかと思います。
#usda 1.0
# TODO: 2日目:水を分けて天を作る
def "World" # TODO: 3日目:大地を作って植物を植える
{
# あと月とか星とか作ろうかな
}
# 7日目は有休頂きます
def とは define の略です。"World" という名の Prim(プリム)を作ろうと思う=PrimSpec、というのがこの usda の言わんとすることです。def の代わりにここで使えるのは over, class があります。Prim と PrimSpec は厳密には区別されますが、ひとまず神があなたしかいない世界では Prim=PrimSpec と思ってもらって構いませんし、現実ほとんどの実務でそうコミュニケーションして問題ありません。現時点で USD を使っている人の大多数はその違いを気にしていませんし、ピクサーのエンジニアですら「world.usda の中の world prim」と言ってそれで通じます。ですのでこの記事でも以後 Prim と呼びましょう。Prim と PrimSpec の違いを理解するとき、それはあなたが USD の奥義であるコンポジションの本当の姿を知るときなのです。ところで Prim につける "名前" の部分はダブルクオートでくくります。
こうして記述した usda ファイルのことを「レイヤー」と呼びます。
文法チェック
慎重な神であるあなたは、この usda を出雲に持って行く前に正しく書けたかどうかチェックしたいでしょう。良い心がけです。usdcat, usdview がそんなあなたの心強い味方です。usdview には USD イメージングコンポーネント(画像処理機能)が必要なため、あなたの環境にはないかもしれません。さらに非常に細かいことを言うと usdcat と usdview では引数に与えた usda レイヤーの扱い方がことなり、usdview は内部的に必ず出雲に連れていかれ他の神々とのバトルが始まってしまいますが、usdcat のデフォルトではあなたの計画のチェックだけが行われます。ですのでまずは usdcat で読み込み、表示してみる、のがよいでしょう。これらのツールの使い方は @fereria さんの後日のアドカレ記事にまかせます。
Prim には型をつけられる
コロナ下でテレビ観戦が中心でしたが、2020東京オリンピックの新種目、空手・形、良かったですね。全く知らない競技でしたがじっくり見てしまいました。チャタンヤラクーサンクー。最初の例では、Prim には "World" という名前を付けただけでした。ですので我らの Prim にも型をつけていきます。名前の前にその Prim がどんな種類のものか、宣言してから演舞を行います。
#usda 1.0
def Xform "World"
{
}
こうすると World は UsdGeomXform という共通の機能・特性(スキーマと呼びます)をもつ Prim であることが明示的になり、見る人も安心するし、間違いがありません。またうっかり型と違う技を出してしまったときに警告や減点を行って早期にレンダリングを止めることも出来ます。一晩かけたレンダリングを翌日チェックしたときの絶望感、皆さんご存じですよね。
Type がなくても同等の働きをする Prim を定義することはできますが、ツールによっては Type がないと正しく解釈してくれないことが多く、野良 Prim と呼ばれ def では一般に忌避されています。Houdini の USD Import でもトラバースが打ち切られてしまったりと使いにくかったりします。ただし over では型を書かないことで、USD のコンポジションの威力が発揮されます。ポケモン特性のかたやぶりと同じで、相手の特性を無視して攻撃を仕掛けることができるようになります。何を言ってるのかわからないかもしれませんが、千利休の教えに従い、今は型をつけておきましょう。
型には継承関係があります。空手の型の流派・系統と同様に、元になる原始的な型から様々な型が派生しています。オブジェクト指向設計で同様の概念になじみのある人も多いと思いますが、USD の仕様はもちろん空手・型から着想を得ています(主観です)。
親子を作る
なかなか話が進みませんので巻いていきます。
やり方はつかみました。いよいよこれから様々な usda を VS Code で記述していくわけですが、USD Prim に使える文字には残念ながら制約があります。スマホネイティブな我らとしては日本語だけでなく def Mesh "😄" とか def Xform "🤸2" とか書きたい、しかしぐっと堪えて、今は英数字と _ くらいで我慢してください。数字は先頭には置けません。ピリオドやコロンも別の用途で予約されているのであきらめてください。
それでは森羅万象を生み出すために バブリング創世記 に従って書いていきます。とりあえず最初の4行くらいを翻訳する例を示します。
ドンドンはドンドコの父なり。
ドンドンの子ドンドコ、ドンドコドンを生み、ドンドコドン、ドコドンドンとドンタカタを生む。
ドンタカタ、ドカタンタンを生めり。
ドンタカタ、ドカタンタンを生みしのち四百六年生きながらえて多くの子を生めり。
#usda 1.0
def Xform "DonDon"
{
def Xform "DonDoko"
{
def Xform "DonDokoDon"
{
def Xform "DokoDonDon"
{
}
def Xform "DonTakaTa"
{
int yearsLiveAfterDokaTanTanBirth = 406
def Xform "DokaTanTan"
{
}
def PointInstancer "instancer"
{
}
}
}
}
}
素晴らしいですね。子供は {} の中に、兄弟は同列に書く、というのが理解頂けたかと思います。見やすいようにインデントを4文字でつけてみました。
DonTakaTa 自体を PointInstancer にすることも考えましたが、スキーマ的によろしくないので分けておきました。
USD にはファイルフォーマットプラグインといって、様々な形式の入力を usda に透過的に変換できるようにする機構があります。今回は手書きしましたが、筒井康隆プラグインを作ってやれば上の例もテキストを開くだけで usd 化することができるようになるわけです。
アトリビュート
創世記には Prim の関係を記述しましたが、一つだけ DonTakaTa に int yearsLiveAfterDokaTanTanBirth というアトリビュートを書きました。USD Prim は親子関係以外にプロパティというものを持ちます。プロパティにはアトリビュート(シンプルな値)とリレーションシップ(他の Prim やプロパティへの関係)がある、ということだけ覚えておきます。ここではアトリビュートのみ簡単に説明します。
型名
アトリビュートに使える型は、ドキュメントの [DataType] (https://graphics.pixar.com/usd/release/api/_usd__page__datatypes.html) を参照してください。ほとんどが CG でなじみのあるものですが、見慣れない二つだけ解説しておきましょう。
- asset
- 他のアセット (USDファイル、テクスチャ等)を指すためのパスを表す
- token
- 文字列だが、スキーマで定義済みの定型語など、高速に大量に扱うためのもの
また、同じデータ型ではあるけれど使い方(セマンティクス)を付与した型名もあります。
- point3f
- float3 だが、座標として扱われる
- normal3d
- double3 だが、法線として扱われる
これらを使って Prim の中にアトリビュートを記述できます。これらは基本的には <型名> <アトリビュート名> = <値> といった形式で使われます。アトリビュート名は namespace: によって修飾されることがあります。
def Xform "World"
{
float xformOp:rotateX = 30
float xformOp:rotateZ:spin.timeSamples = {
1: 0,
100: 360,
}
double3 xformOp:translate = (100, 200, 300)
custom int myGameEngine:specialValue = 12345
uniform token[] xformOpOrder = ["xformOp:rotateX", "xformOp:rotateZ:spin", "xformOp:translate"]
}
突然真面目なサンプルになってしまいましたが、これが型にならう、ということに他なりません。
可変長リスト(配列)は [] で、2次元・3次元ベクトルは () で記述されます。ベクトルの配列も同様に[(1,2),(3,4),(5,6)] となります。
ではこのレイヤー中で設定される値とはいったい何を表しているのか?というのが USD のポイントの一つです。コンポジション、フォールバック、デフォルトバリュー、タイムサンプル、バリュークリップ、といったいくつものコンストラクトが関係してきます。文頭の custom とはなにか。余談が多すぎてだいぶ長くなりましたので、その話はまたいずれ。
おわりに
今はテキスト形式のデータフォーマットといえば XML や JSON が中心ですが、3D フォーマットは古来より様々な形式で記述されてきました。皆さんもなじみのある Maya や Houdini も、その源流をたどっていくと wavefront OBJ や Prisms poly ファイルといったテキストエディタで読み書きでき、プログラム的にも処理しやすい形式のアスキーフォーマットがあり、Collada や wrl など XML ベースのフォーマットもありました。一方で Softimage がバイナリの HRC フォーマットを作りその流れを FBX が汲んで(主観です)という流れもあり、常に明快さと効率との間で弁証法的に発展してきたのが 3D フォーマットの歴史とも言えます。過去はテックスタッフと呼ばれることが多かったパイプラインエンジニアの主要な役目は、次々に現れる様々なツールをアーティストのワークフローに取り込むためにこれらのフォーマットを相互変換するコンバータ開発を行うことでもありました。それはあたかも、大航海時代に洋の東西の全く異なる文化を結びつける通訳であり、水先案内人でもありました。
USD のテキストフォーマットである usda の記述形式は、ピクサーの社内ツールであった Presto のネイティブフォーマット menva を踏襲しています。それと同時に高効率なバイナリフォーマット USDC も備えており、こちらは USD のために新規開発されました。どちらのフォーマットでも機能は同じです。世界中の VFX/アニメーションスタジオ、ゲーム制作会社における、数十年にわたる数えきれないパイプラインエンジニアたちの試行錯誤が結実したものが USD であり、満を持してユニバーサルと名付けられました。世界を記述したい、という人類の夢は古典物理学や量子力学を生み、そしていま USD を生みました。この先 USD を超えるフォーマットは出てくるのか?もちろん出てくるでしょう。USD は1次元に限定された時間軸など、まだまだ映像制作に傾斜している部分はあり、ゲームやメタバースのデータ記述の相性に改善の余地は大きくあります。数学が一般化を重ねて適用分野を広げ発展していったように、USD も、あるいはその後継たちも、今まで同様に試行錯誤を繰り返しながら世界をつなげていくに違いありません。
このしょうもない記事を最後まで読んでくれたあなたが、その主役です。