😊

BlenderUSDHydraAddonを使おう(3) Nodeを作ろう そして総括

2021/09/05に公開

前々回 前回に続いてBlender+USD第三回。
今回は、BlenderUSDHydraAddonの実装がどうなっているのか確認しつつ
カスタムノードを作ってみます。

実装確認

まず、インストールしたBlenderのAddonフォルダを確認します。

C:/Users/<Version>/AppData\Roaming/Blender Foundation/Blender/2.93/scripts/addons/hdusd

インストール先はこちら。

ざっくりとした構成は

bl_nodes -> Blender側のネットワーク用のNode
mx_nodes -> MaterialX のシェーディングネットワーク
usd_nodes -> USDのノードネットワーク
export -> Blender→USDの変換
utils -> 共通して使う関数

で、
libsがProRender本体、Usd、MaterialXなどのライブラリで
それ以外のBlenderのAddon部分はすべてPythonで書かれていることがわかります。

USD NodeTree

ということで、今回はUSDのネットワークを追加してみます。

追加方法は、
usd_nodes/nodes
以下に.pyファイルを追加します。

NodeTreeは、Blenderのシェーディングネットワークと同じ構造を使用しています。
USD用の構造は base_node.py の USDNode で定義がされているので、
新しく追加したい場合はこのクラスを継承することで追加できます。

まずは、簡単な cubeを指定のSdfPathに作るノードを作ります。

cube.py
import bpy

from pxr import Usd, UsdGeom

from .base_node import USDNode


class CubeNode(USDNode):
    """Merges two USD streams"""
    bl_idname = 'usd.CubeNode'
    bl_label = "Cube"

    def update_data(self, context):
        self.reset()

    sdf_path: bpy.props.StringProperty(
        name="SdfPath",
        default="",
        update=update_data
    )

    def draw_buttons(self, context, layout):
        layout.prop(self, 'sdf_path')

    def compute(self, **kwargs):

        input_stage = self.get_input_link('Input', **kwargs)

        if not self.sdf_path:
            return None

        stage = self.cached_stage.create()

        if input_stage:
            layer = input_stage.GetRootLayer()
            stage.GetRootLayer().subLayerPaths = [layer.realPath]

        UsdGeom.Cube.Define(stage, self.sdf_path)

        return stage

構造はシンプルで、
bl_idname で、呼び出し用のIDを指定 bl_label でNodeの名前を指定します。

sdf_path: bpy.props.StringProperty(~~~) で、Nodeで指定できるプロパティを追加。
その編集UIを draw_buttons 関数で指定します。
今回はSdfPathをStringで指定したかったので、プロパティはStringProperty です。

最後に、メインの処理を compute 関数に追加します。

get_input_link は、Inputで入力された前のノード前のcomputeで合成された結果のStageを
UsdStageオブジェクトで受け取ります。

注意点は、受け取ったStageを直接編集する場合正しくクリアできないので
cached_stage.create() で、いったん別のステージを用意してから
compute処理を書きます。

参考にしたほかのノードだと、MergeはCachedStageにInputされたステージのRootLayerを
リファレンスで読み込みして作られていたり、
Filterの場合は、指定された文字列以外のPrimをCachedStage側にコピーする事で
ノードが作成されていました。

今回のCubeの場合は、CachedStageにInputのRootLayerをサブレイヤーで合成し
CachedSdtageにたいして UsdGeom.Cube.Define でCubeを作成することにしました。

__init__.py

from . import (
   usd_file, blender_data, write_file, merge, print_file, filter, root, usd_to_blender,
   hydra_render, rpr_render_settings, cube
)

#...略
    
    USDNodeCategory('HdUSD_USD_CONVERTER', 'Converter', items=[
       NodeItem('usd.MergeNode'),
       NodeItem('usd.FilterNode'),
       NodeItem('usd.RootNode'),
       NodeItem('usd.CubeNode')
   ]),

register_classes, unregister_classes = bpy.utils.register_classes_factory([
   #...略
   cube.CubeNode
])    

ノードを実装したら、それを使えるように init.py で登録します。

USDNodeCategory が、

Addで追加するときの呼び出しメニューの位置で、この場合Converterに追加します。

NodeItemの引数で指定するのは、 bl_idname です。

最後に、 bpy.utils.register_classes_factory で、クラスを登録します。
以上で準備は完了です。

結果。
USDListに、CubePrimが追加されたのがわかります。

Blender側にも、Nodeができています。

そのほかの構造メモ

未実装のノードでファイルだけ存在しているものがいくつかあって
RprRenderSettingsNode おそらく RenderSettingのPrimを作るノード?
USDToBlenderNode おそらく、ノードからUSDをBlenderにインポートするノード。
現状のNodeTreeに生成されるのとは別に、指定のObjectに対して合成できるようにする?

ExportUSDノードは stage.Export(~~)なので、基本Flattenで出力される。

MaterialXはTempFolder以下に作成されるので、あくまでもBlender内でRPRが使用するためのもの。
UsdMtlxを使用するのではなく、RPR用にFileを指定しているので
UsdMtlxを使いたい場合は工夫が必要。

BlenderUSDHydraAddonをインストールすると、BlenderのPythonでUSDのモジュールを使用できるようになる。
これが良くも悪くもBlenderでUSDのAddonを作ろうとした結果こうなったんだろうな...という(後述

総括

というわけで、3回に分けてBlenderUSDHydraAddonを3回に分けて触ってみたわけですが
面白そうなものが出てきたなぁという気持ちと、いろいろと限界が見えてしまったことによってうーん...という気持ちが半々です。

どういうことかというと、BlenderはAddonを作ろうとした場合どうしてもPythonになってしまいます。
(そうでなければ本体をカスタマイズするしかない)
ので、BlenderのシーングラフからUSDに変換する部分や、カスタムノードがすべてPythonで書かれているため
処理が非常に遅いです(試していても、シンプルなシーンから少し複雑なことをしようとすると???と思うことが多々)

ノードの実装も、USDPythonを知っていればBlenderAPIをほとんど知らなくても簡単に書けるのですが
あまり複雑なことはしにくいかなというのが第一印象です。

USDでデータをうけとって、Blender上でLookdevしたりできないだろうか?
と、検証してみたもののまだまだ難しそうです。

ただ、大きいデータを作るのは難しそうですが
各種アセットのモデリングをBlenderで行い、USDノードで基本的なUSDセットアップをするノード(Payload/Variant)を作り
AddonからExportする Layout/Lookdev/LightingはHoudiniで
みたいなことはありかな?と思いました。

GitHubで編集を提案

Discussion