nuke14でUSDをそこそこ使えるようになったいうことでまぁまぁ気になったのでちょっと覗いてみた話

※この投稿はnuke14.0.1をもとにして書いてます。

nukeは以前からUSDファイルの読み込みは可能だったが限定的であり、今回、nuke14がリリースされてUSDベースのあたらしい3Dシステム(New USD-Based 3D Architecture)が実装されたとのことなのでちょっと覗いてみました。
twitter等でnukeにUSDが来て何が嬉しいの？みたいな意見も散見されてたけれども、その辺りの私見も含めて少し書いていこうかと思います。

さていきなりではあるが誤解を恐れずにいうならばこのUSD「ベース」の新しい3Dシステムっていうのがかなりの曲者である。まず...

<B>USDのpython bindingがない！</B>

python libraryなのかpython moduleなのかpython apiなのか、とにかくあれができないんです！！

`from pxr import Usd`

が・・・(ディスってるわけではないですよ:)
そもそもcgのシーンの構成などを記述されているUSDがnukeから利用できるようになって何が嬉しいかというと、例えばnukeでcgレンダーを扱っててそのcgの連番をレンダーしたカメラとかロケーターとかが欲しいとかってなることがまぁまぁあって、そんな時いちいちcgのソフトウェアからカメラを何かnukeから読める形式にしてexportするなんて無粋なことしなくても、その連番をレンダーした.usdファイルからカメラを抽出するなんてことが可能なんです。USDワークフローでレンダーしていれば.usdファイルはすでに存在しているはずなので新しく何かを出すって必要もなく。　
もう少し踏み込むと(これはUSDとは少しずれますが)、nukeでcgのレンダー結果の連番を扱ってる人なら大体がexrを使ってるはずで、exrだとmetadata入れ放題(?)なのでレンダーした.usdのファイルへのパスをそのexrのmetadataに記述しておけばもっと便利です。(カメラデータぐらいならmetadataに全部入れてそこから引っこ抜くってのもできるけど・・・)。そしてnukeからそのmetadataから.usdファイルを見つけて、そこからカメラを抽出するみたいなツールを書けばもっともっと便利でしょ?
これはあくまで一例ではあるけどつまりはshotを作る一番下流側から上流のデータにアクセスできるってことです。上流で用いていたソフトウェアを開く必要もなく。カメラに限らず、例えば頂点データにアクセスすることもできる。例えばシェーダーのパラメーターにアクセスすることもできる。頂点が取れたら何が嬉しいの？そりゃそのキャラクター(ジオメトリ)までのカメラからの距離がきっちりわかるってことですよ。カメラが動いててコンプでカッコよく浅めのエモい被写界深度与えたいときにちゃんとfocusをそのキャラクターにロックできるってことですよ。そのためにnullやlocatorを書き出す必要がないんです。しかもうまくやればスクリーンスペースの座標からその近傍の頂点を割り出すことだってできる(これはUSDの機能ではないけど、でも頂点にアクセスするのはUSD的に)、なのでユーザーは画面クリックするだけですよ。シェーダーのパラメーター取れたら何が嬉しいの？そりゃ(同様に)クリックしたピクセルからそのオブジェクトにアサインされてるシェーダーがわかってそのシェーダーのパラメーターをoverrideして再度レンダー投げたりできるんですよ。nukeから。めちゃ便利やない？これ？レンダー投げ直すまでは行かなくてもデバグしやすいわけです。なんせコンプに入れて初めてわかる問題なんてごまんとあるわけやし。とまぁ、ここまでやろうとおもうと色々と整備する必要はあるかもだけどUSD使えるならそこを割とズバッとショートカットさせてもらえるんですよね・・・(これをするためにはnukeから.usdファイルにアクセスしてUsdモジュールの関数使ってそれらの値を取ってくるのが一番手っ取り早いんだけどなぁ)なんですよね・・・もちろん、今回のバージョンアップはファーストステップと言ってるので、おそらく実装されてくるとは思うのですが(期待を込めて)。

と前段が長くなってしまいましたが、じゃあ今のUSDベースの新しい3Dシステムでそれはできんのかね？ってことですが・・・

<B>大丈夫。できるます!</B>

(ちょっとスマートではないけど・・・)
ざっくり紹介していきましょう。
今回はレンダーのexrのmetadataからカメラを実際にnukeにインポートする流れを紹介したいと思います。
まずHoudiniでexrのmetadataに自分の欲しい値をブッ込むところから紹介していきます。例に洩れずボクも「USDアドカレ」、結果「Houdiniアドカレ」になりがちあるあるを擦っていきます。

適当にLopでKarmaでレンダーするシーンを作りました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3057501/87424f3e-6a76-94e5-8c7e-d8b7d0f1d298.png)

Houdiniアドカレになってもアレなんでここでの詳しい内容はザクっとだけ説明しておきます。Lopのノードストリーム中にpythonscript lopを追加します。これでそのストリーム中のUSDのstageに色々と操作を加えてます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3057501/2d703cb0-3491-5aa3-a2bb-b87d89ba7371.png)
以下一部ハードコードしてますがもちろん探索的にノードやprimを拾ってくることは可能。
``` import hou
from pxr import Sdf
import os

node = hou.pwd()
stage = node.editableStage()

targetLopnode = hou.node('/obj/lopnet1/usd_rop1')
usdpath = targetLopnode.parm('lopoutput').eval()

node = hou.pwd()
stage = node.editableStage()

render = stage.GetPrimAtPath('/Render')
for p in render.GetChildren():
    if p.GetTypeName() == 'RenderSettings':
        break
camerapath = p.GetRelationship('camera').GetForwardedTargets()[0].GetPrimPath()
camera = stage.GetPrimAtPath(camerapath)

renderProducts=[]
for p in render.GetChildren():
    for c in p.GetChildren():
        if c.GetTypeName() == 'RenderProduct':
            renderProducts.append(c)

for r in renderProducts:
    metadata_attribute = r.CreateAttribute("driver:parameters:OpenEXR:usdpath", Sdf.ValueTypeNames.String)
    metadata_attribute.Set(usdpath)
    metadata_attribute = r.CreateAttribute("driver:parameters:OpenEXR:camerapath", Sdf.ValueTypeNames.String)
    metadata_attribute.Set(camerapath.pathString)    
```

これでちゃんとrenderproductにmetadataが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3057501/da6b682f-2a7e-37f7-5077-87c15130ec48.png)

具体的にはレンダー結果であるexrのメタデータにそのexrをレンダーした.usdのファイルパスを```exr/usdpath```って名前のkeyで追加してます。あと同様にレンダーしたカメラのpathを```exr/camerapath```という名前で追加してます。これでexrからmetadataを介して.usdファイルのファイルパスとレンダーカメラのusd上でpathを知ることができます。



あとはこれをnuke14で新しく追加されたCameraノード(クラス名:Camera4)に教えてやります。Import Scene Primにチェックを入れて、File Pathで.usdファイルのファイルパスを指定します。あとはその.usdファイルからインポートしたいprim(この場合camera)のpathをImport Prim Pathで指定します。いずれもmetadataに仕込んであるので、それをコピペするだけです。

metadataはこんな感じで見えてます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3057501/e5f713c8-11a9-c286-1462-a10ce15ba228.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3057501/df253a6a-3221-93c5-6a52-bd3e8b55170a.png)
これらがあれば、これら二つをcameraに教えてやればOK。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3057501/95b5438a-8d20-6627-f110-1f78282d57a7.png)
こんな感じでプロパティーのFile PathとImport Prim Pathを埋めれば良いんですが・・・


ですが、ここまでくればツール化したいのが人の性(よね？)。選択ノードから実行すればいいって感じのものを書いてみます。


具体的なコードは
```
node = nuke.selectedNode()
usdpath = node.metadata('exr/usdpath')
camerapath = node.metadata('exr/camerapath')
camera = nuke.createNode('Camera4')
camera['import_enabled'].setValue(True)
camera['file'].setValue(usdpath)
camera['import_prim_path'].setValue(camerapath)
```



　
実行したらこんな感じ。

https://youtu.be/oKfsOUETrJ4


　
このままだとHoudiniアドカレ、Nukeアドカレ(これは多分存在すらしない)になってしまいがちなので、今一度フォローしておくとUSDはとても良いです。ちょっと熱っぽく語らせてもらえれば、コンプからの目線になってしまうけど、うまくやればと前置詞はつくものの任意のピクセルからシーンを構成しているモノたちにアクセスできて値を取得できたりまたはoverrideして再構築するとか、

<B>夢が広がります</B>。 

今回はそれなりに力技的なところも色々ありましたが。本来なら.usdのファイルパスさえ拾うことができればカメラのprim pathはUsdモジュールの関数から引っ張ることができる。また、前述の通り頂点などへもアクセスできるので可能性はまだまだあるかと。ということで　

<B>今後に期待！</B>

おしまい。　

　
　




追記:実はこのUSDベースのあたらしい3Dシステムに対してnuke内でpython的なアプローチが全くないわけではなくて今回usgという新しいモジュールが追加されてる。　
例えばGeoImport等のノードで.usdファイルを読み込んでそのノードから

```
node = nuke.selectedNode()
stage = node.getStage()
```
とかってやるとstageっぽいものがとれる。でこいつのクラスがusg.Stageである。
```
node = nuke.selectedNode()
stage = node.getStage()
print(stage)
# Result: <usg.Stage object at 0x7ff580742f70>
```
Usdではなく<B> usg </B>。このUSDベースのあたらしい3Dシステムを少し触って思ったんですが、今回の実装は割とジオメトリへの機能追加をメインとしていて、それでusgなのかなぁ・・・と思ったり。

また、ここで出力したこのusg.Stageにはいくつかの関数が準備はされているが本家Usd.Stageに比べるとかなり微々たるものでまだまだと言った印象だったので今回は紹介するほどでもないなぁ・・・　といった感じでした。

<B>~~やっぱり~~ますます今後に期待！</B>

かなり雑にまとめてしまいましたが、最後まで読んでくださってありがとうございます:)