SideFX:PROJECT GREYLIGHT イントロダクション
まずは「USDの専門用語」について少し話しておこうと思います。
なぜUSDはこのような仕組みになっているのか?
──それにはいくつか理由がありますが、一番大事なのは、「USDはとにかく効率的で、安全な運用を前提として設計されている」という点です。つまり、動作が一貫していて、誰がどこで読み込んでも同じ結果が得られるということです。
画面の左側には、Pixarが作成したUSDの階層構造を説明する図があります。これが、USDの仕組みを理解するうえで最も重要なポイントだと思います。
というのも、従来のワークフローでは、各部門がそれぞれ同じアセットを個別に読み込んでいました。
たとえば、レイアウト担当者がカメラを読み込み、アニメーターもカメラを読み込み、ライティングアーティストも同じくカメラを読み込む、といった具合です。
これでは非効率的だということにPixarは気づきました。「みんなが同じものを個別に読み込む必要ってあるの?」という疑問から、階層構造にまとめて一括で管理するという発想が生まれました。
たとえば、右側の図にあるように、誰かが車のアセットを作ってエクスポートすれば、それは自動的に「駐車場のショット」に反映されます。つまり、個々の部署が毎回アセットを手動で読み込む必要がなくなったのです。
この「すべてが自動的に読み込まれる」という考え方に抵抗し、従来のやり方にこだわると、おそらく何度も壁にぶつかることになります。
右側にあるのは「Layers」と呼ばれる概念で、Car_1レイヤー、Car_2レイヤー、そしてParkingLotもまたレイヤーの一つです。では「Layers」とは何かというと、これは単なるUSDファイルです。Layersという言葉は、つまり「USDファイルそのもの」を指しているのです。
さらに「Sublayers 」という用語もあります。たとえばParkingLotの中にCar_1やCar_2が含まれている場合、それらはSublayers になります。ただしこれも、単に「別のUSDファイルへのリンクが張られている」というだけです。
実際、SolarisでRubber Toyのテストジオメトリを読み込むと、それはディスク上のUSDファイルを参照しているだけです。つまり、それも技術的にはSublayers です。
USDには「Layer stack」という考え方もあります。これは、USDファイルの読み込み順を意味しており、たとえば最初にレイアウト、次にアニメーション、次にエフェクト、最後にライティング……というように積み重ねられていきます。この順番が「スタック」と呼ばれるものです。
USDでは「opinion」という概念も重要です。これは、レイヤースタックにおいて後から読み込まれたものの方が「強いopinion」を持つ、というルールに基づいています。たとえば、最初にキューブを読み込み、後でアニメーション付きのキューブを読み込めば、アニメーション付きの方が上書きされて表示される。これが「強いopinion」です。
「Layer Breaks」という少し厄介なものもあります。USDではデータを細かく分割してエクスポートすることを想定しており、Mayaのように「アニメーション付きのキューブをエクスポートすると中身が全部焼き込まれている」といった形式ではありません。
USDでは「Layer Breakノード」を使って、上の階層を無視し、その下だけをエクスポートします。たとえばキューブにLayer Breakを設定して移動し、それをエクスポートすると、読み込んでもキューブそのものは見えず、「どこに動かしたか」という位置情報だけが記録されている状態になります。
一見「なんだこれ」と思うかもしれませんが、こうした仕組みによってファイルが軽量になり、高速に読み込めるようになっているのです。しかし当然デメリットもあります。例えば、アニメーションデータとモデルデータが分かれているため、どちらか一方だけでは意味を成さないことがあります。結果として、バージョン管理が非常に難しくなります。
たとえば、最初にエクスポートされたキューブを後でスフィアに差し替えた場合、頂点の構造が変わり、アニメーションが正しく再生されなくなるかもしれません。
Pixar的な思想では、「選んで読み込む」ではなく、「常に最新版を読み込み、必要に応じてロールバックする」という運用が想定されています。つまり、「使いたいバージョンを明示的に選ぶ」のではなく、「使いたくないバージョンだけを除外する」スタイルです。
ただし、これには「依存性の管理」という課題も出てきます。あるモデルにだけ使えるマテリアル、あるアニメーションにだけ合うメッシュ構造──こういった関係性が壊れてしまう可能性が常にあるからです。
また、USDは基本的に「読み取り専用」です。たとえば、レイアウト部門が配置したキューブを、ライティング部門が使っていたとします。もし別の部署がそのキューブを削除したら、ライティング側のシーンが壊れてしまう恐れがあります。だから、原則として「削除もリネームもできない」のです。
代わりに「オーバーライド(上書き)」という仕組みを使います。特定のノードを非表示にしたい場合も、Pruneノードを使って非表示にするだけで、削除はしません。こうすることで、必要なときに元に戻せるのです。
次に、「Prim Path(プリムパス)」の話に移ります。すべてのノードには基本的にPrim Pathが設定されており、デフォルトでは「$OS」となっています。これは「ノード名をそのまま使う」という意味です。
たとえば、カメラを作って「camera1」という名前で使用し、レンダリング設定にもそれを使ったとします。しかし、そのカメラを複製して「camera2」になったものを使い始めると、レンダリング設定をすべて「camera2」に変更しないといけません。これは非常に面倒です。
そこで「Prim Path」に「renderCam」のような共通名を設定すれば、ノード名が変わっても、USDの中では一貫して「renderCam」として扱うことができるのです。このように、Prim Pathを適切に使うことで、共有や差し替えがずっとスムーズになります。
たとえば駐車場ショットに「car_1」という名前の車を配置していたとして、TurboSquidからダウンロードした「Lamborghini.usd」を使いたいとなったとき、Prim Pathを「car_1」に設定すれば、従来のアセットと互換性を保ったまま使用できます。
このような工夫は、大手スタジオでは独自のツールやルールとしてすでに実装されていますが、個人アーティストにはまだ一般的ではありません。
さて、ここでは、私たちが解決したかった「実務的な課題」についても触れていきましょう。
まず、多くの人が表に出さない問題として「ネットワークの複雑化」があります。最初はきれいに整理されていても、次第にノードが増え、ネットワークが巨大化して制御不能になります。ノートやネットワークボックスで整理しようとしても、限界があります。
これこそが、我々が取り組むべき大きな課題の一つでした。「なぜネットワークがこうなるのか?」「どうやったらこの問題を防げるのか?」——それを解明しようとしたのです。
次に見せるのは、もう少し整理された例です。これは、他の課題を説明するためのものです。よくあるケースとして、こういった水平のラインを作って、ショットごとにノード列を分けていくスタイルがあります。実際これは、4年前に制作したコマーシャルのネットワークで、かなりシンプルな構成の案件でした。説明にはちょうどよい例です。
最初のうちは、「この2つのショットは同じアセットを共有するから、ラインを繋げよう」などと考えて、論理的に接続していくわけです。でも、すぐに気づくんです——「あれ、ショット1とショット5も同じものを共有したいな」となると、構造を再構築しなきゃいけない。でもショットの構成を後から変更するのって、現実的じゃないんですよ。だから、結局は横並びのラインを引くしかなくなってしまいます。
また、各ショットにはフレームレンジやプレート読み込み、カメラ設定など、共通で必要な初期設定が複数あります。USDの仕様では、カメラにイメージプレーン(参考映像)は標準で含まれていません。SideFXではHoudini用にその回避策が用意されていますが、各自が個別に設定しなければなりません。つまり、アニメーターも、レイアウトも、FXも、みんなが似たようなことを手動で行っているのです。
この「すべて手動」という点に多くの人的ミスが生まれる余地があります。
ここで、ちょっと笑い話のような本当の話をひとつ。私たちはあるコマーシャルで「リス(squirrel)」を扱ったのですが、私たちはスウェーデン人だったので、チーム全員が「squirrel」のスペルを知らなかったんです。結果、出力ファイルのたびに「squarrel」「squrrel」「squirel」など5パターンくらいのスペルが出てきて、各部署がバラバラのスペルでファイルを書き出してしまいました。で、当然のようにファイルが壊れて、すべてがぐちゃぐちゃになった、というわけです。
バージョン管理も大きな課題です。SideFX Labsのいくつかのツールには簡易的なバージョニング機能がありますが、多くは手動でファイル名のバージョン番号を変えるだけで、非常に不安定です。レンダリング中に「テスト用の立方体でフルHDの本番ファイルを上書きしてしまった」といった事故も起こりがちです。
でも、最大の問題は、こうした横並びのショットラインが「それぞれ独立している」という点です。これは、出力ノード(ROP)がネットワークの一番下に置かれるためです。すべてのショットが独自にレンダーを持っているので、横に並べるしかなくなるんですね。
さきほども話しましたが、「ショット1と2は夜のライティング、他は昼のライティング」というような初期設計なら、分岐して整理するのも理にかなっています。でも、しばらくして「ショット1と3には同じアセットが必要」とか「ショット7と14は同じ内容」なんてことに気づくと、もう分岐では収拾がつかなくなります。
結局どうなるかというと、「Mergeノード」で他のブランチから必要なデータを吸い上げたり、「Fetchノード」で直接引っ張ってきたり、あるいはこの例のように「整理整頓のために」ノードをあるいはノードをコピペして複製する──という方法に陥ってしまいます。結果として、すべてのショットに共通のノードが5つくらいずつあるという、非常に非効率な構造になってしまうんです。
本来は「共通要素は共有し、必要な部分だけオーバーライドする」というUSDの思想に基づいて設計されているはずなのに、実際には「全部コピーしてバラバラに管理する」という真逆の運用がされているのです。
そして、もうひとつ深刻な問題が「ROPノードの氾濫(ROP Overload)」です。このプロジェクトでは、25ショットで25個のROPノードがあります。各ショットには通常1つのBeautyパスがありますが、それに加えて、前景レンダー、背景レンダー、分解パス、プレビューパス、テクニカルパス……と、5つ以上のROPが必要な場合もあります。
つまり、25ショット × 5パスで、あっという間に100個以上のROPノードになります。そしてそれぞれにオーバーライド設定があるので、「どれが何用の設定だったか」すぐにわからなくなります。
「このパスはもうレンダリング済みだっけ?」と不安になって、ノードに色を付けて管理し始めるものの、次にシーンを開いたときにその色を戻すのを忘れて、さらに混乱する──そういった事態に陥っていくのです。
こうした混乱を解決すること、これが私たちの目指した大きな目標でした。
USDが抱える構造的な課題、そして実制作で日々直面する実務的な煩雑さ。その両方に対応することが、私たちのプロジェクトの出発点でした。
それでは、実際のソリューションの中身に入っていきましょう。