元記事:
Generic Node system for Blender
Blender の汎用ノードシステム
by Ton Roosendaal
Blender のノードシステムは基本的には後で詳細を説明する、三つの大きな部品で構成されています。私は Blender 内の構造体の名前を、小文字の 'b' を接頭辞とする作法に従うことを選びました。これはシステムライブラリ使用時に、名前の衝突が起こるのを防ぎます。("struct Node" はすでに確実に存在しているでしょう)
struct bNodeTree
ノードのリストとノード間のリンクのリストはこの構造体に格納されます。ノードツリーはたとえば、シェーディングや Composite などのタイプを指定できます。現在、カーネルのコードは、ノードの追加や削除、開放を除き、ハードコードされたタイプ指定を処理しています。コード内には、これが関数ポインタを使用して「ハンドラ化」される候補であることがコメントされています。(話は変わりますが、私はさらに NodeTree タイプが追加される前に、これを「ハンドラ化」することを強く推奨します)。
ノードツリーは Blender のデータベース上のどの場所にも存在可能ですが、単純な構造にするため、Material(Sharding)や Scene(Composite)などの、現存するライブラリ構造への追加にとどめることを推奨します。
この方法を使用するノードツリーは、このライブラリデータの静的(static)な部分(
Blender Architecture ドキュメントでは "direct data" と呼ばれるもの)であり、読み書きや複製・解放を通常の direct data 同様に行わなければなりません(つまり、このようなノードツリーには、他のユーザが存在しないことを意味します)。
Group ノードは自身のIn/Out ソケットを持ち、それをライブラリから ノードツリーへマッピングします。
Groupsノードツリーをライブラリデータとして使用することも可能です。この場合、ノードツリーを "Group" として利用できるようになり、Blender のメインライブラリに格納され、再利用や複数のファイル間で共有してリンクすることができます。しかし、ノードツリーGroup を単一の Material などに直接リンクすることはできません。このような Group は、スタティックな Material ノードツリーの中に "Group ノード" を挿入することでのみ追加可能です。現在の実装では、Group を Group 内に持つことはできませんが、私はこのことについて考える前に、先にノードシステムを完全に安定させたいと思います。
このデザインの決定―スタティックなノードツリーとダイナミックなノードツリーに同じ構造体を持たせる―は、ノード操作の API をシンプルにします。
再リンク可能なノードツリー(つまり Groups)自身を、これが再利用される意味のあるノードに制限することを保証するいい習慣です。(訳注:原文は It is good practice to ensure that re-linkable Node Trees (i.e. Groups) restrict themselves to nodes where it makes sense that they be re-used.)
通常、スタティックノードツリーに追加されるノードが、Input と Output をどう取り扱うかを決めます。現在の Compositor 内ではこれはほぼ自明であり、ここではその Scene 自身が定める物を元にした "Render Result" ノードが必要になります。これらのノードは Group には挿入できません。
タイプの定義他のノードツリーの重要な機能に、ビルトインの「タイプ定義」があります。ツリー内に存在可能なそれぞれのノードタイプは汎用のタイプ定義配列を持っており、ここに Blender のバイナリ(ビルトインのノード)、もしくは Blender がオンザフライで生成できる物(たとえば、Group ノードや Python で定義したノード)のどちらかを格納しています。
このタイプ定義は特定のノードの機能の改良もしくは修正を、Blender ファイル内に保存されたデータとの衝突せずに行うことを保証します。これは Input ソケットの追加や削除を、さらにノードタイプ全体を完全に消去することさえ可能になる予定です。
Blender ファイルの読み込み(もしくはライブラリの Append/Link)毎に、タイプ定義のチェックが行われます。
ここはコードのやっかいな部分で、特に Group 内の変更されたノードの復活や、Group はまだ Group を含むことができないという最も重大な理由があります。
ノードツリーのマルチユーザ・マルチスレッドによる実行を可能にすべく、それぞれのノードツリーは自身のローカル「スタック」を所持しています。スタック内では、潜在的なすべてのデータの変更が、ツリー内の全ノードからコピーされます。ノードツリーが Group を含んでいる場合、Group 内のノードもまた、このスタックを使用し、複数の Group のインスタンスがツリー内で安全に使用できることを保証します。
struct bNode, bNodeSocket
Node にはできるだけ汎用化する試みが行われており、カーネルと UI の編集用関数のほとんどは、ノードタイプのチェックなしで適用できるようになっています。
一番重要なノード機能はもちろん、その「ソケット」です。すべてのノードは Input と Output を無制限数持つことができ、ひとつのリストに格納されます。
ノードのタイプ定義(static bNodeType 配列へのポインタ)で、Input と Output のソケット数とソケットタイプを定義します。ファイル読み込み毎に、このタイプ定義は格納されているソケットの検査に使用されます。
それぞれのソケットには、自身のローカル「スタックデータ」と、ノード実行中に使用されるデータのジェネリックな記述が格納されます。通常ノードの Input にはこの時、ユーザ定義もしくは前回の計算データが含まれており、その後評価され、Output ソケットへの書き込みが行われます。
Input ソケットがリンクされていない場合、ノードは自身のユーザ定義の入力データのみ評価します。ある Input ソケットがリンクを持っていた場合は、そのノードツリースタックシステムは、リンクされている Output エントリからのデータを確実に使用するようになっているため、データのコピーは必要ありません。
簡単にいうと、Input ソケットは読み込みのみで、Output ソケットは書き込みのみであり、そのすべてがどうリンクされているかを元に、適正な読み書きの結果を確保するため、一つのスタックが使用されます。
現在、このスタックデータ内では、Shader ノードは4つの Float Vector コンポーネントのみを使用しており、Composite ノードは同じ物を使用、もしくは汎用の "CompBuf" を、使用可能なデータポインタに格納しています。
実行後、Compositor は再びノードの結果のバッファ(群)をその(複数の)Outputに格納します。これにより、編集中にノードが一つだけ変更された場合、すばやく再計算することができます。
ノードは現在三つの方法でタイプに依存するデータを格納しています。
- 内蔵の Short 変数、'custom1' と 'custom2' の使用(これは簡単にさくっとコーディングするためのものです)
- (Material、Texture、Image、bNodeTree への)Library ID ポインタの使用。
- void *storage ポインタの使用(これは Blender DNA の一部で、その構造体の名前は Node タイプ定義の一部です)。
特定の場所にノードを描画するための、ノード内で唯一ユーザによりコントロール可能なデータが(locx, locy)座標です。ノードフラグ内のさまざまな描画の設定を元にし、ノード用バウンディングボックスの 'rects' が再描画毎に計算されます(注:これらの Rect はノードに格納されいるため、一度に編集可能な形で描画できるのは一つの Group ノードだけです。これは描画 API の一部となるべきでしょう)。
ノードの描画は src/drawnode.c で完全に一般的な方法で行われます。このプレビューオプションは、セット(かつレンダリングされた)時に追加されます。ノードが GUI(ボタン)を要求している場合、コールバックがノードタイプの定義内に設定されていなければなりません。これは src/drawnode.c の初期化関数内でも同様に起こります。
struct bNodeLink
すべてのノード間のリンクはノードツリーに格納されます。リンクが可能かどうかは、2・3のルールを元にします。
- ソケットの 'limit' 値がコネクト可能な最大数を決定します。これはタイプ定義の一部でもあり、"0" で無制限を意味します。現在の実装(Material、Composite)では、すべての Input について "limit = 1" のみが、すべての Output に "limit = 0" のみが使用されています。これは、スタック動作による実行方法のデフォルトも同様です。
私はまだ、LogicBrick のような複数のリンクを持つ Input ソケットを可能にすべきかどうかためらっています。このような要求がある場合、(OR ノードのような)Input 数の変数をノードに持たせることができる方がいいでしょう。
- ソケットの 'type' は、リンクの確立を強制的に同じ種類のソケット間に制限することができます。これは状況の混乱(Normal Output と Value Input との接続など)を防ぎます。しかし、これはユーザビリティのボトルネックとなることが証明されています。最初の実装ではそんな場合、自動的にコンバータのノードが挿入されていました。これでは、簡単にノードツリーが不必要に複雑に成長し、遅くなってしまいます。
Blender 内の Compositor には、すでに(論理的な初期値を元にした)自動コンバートが統合されており、いつか Shader(Material)にも追加することになるでしょう。
しかし、将来的な拡張(Python スクリプトノードなど)で、それが完全に互換性のないソケットタイプであった場合、やはりこれが必要になるでしょう。
それぞれのノードツリーのリンクの更新では、"ntreeSolveOrder()" 関数が呼ばれます。これはノードのための簡単な依存グラフで、ノードのリストを、順序正しく実行できるようソートします。
循環する状況を作り出すことはまだ可能で、この衝突状態はリンクを赤く描画することで示されます。この状況では、評価の結果が不定になります。
リンクがどのソケットを使用するかを決定するため、これらはどのソケットを Group ノード内から外部に公開できるかも決定します。デフォルトでは、Group 内の未使用のソケットのみ公開されます。Group 内部のリンクの変更は、Group の「タイプ定義」も変更するため、現存するリンクされた Group の使用が破棄されることもあります。
新しいノードの追加
そのノードツリータイプがすでに存在している場合、新しいノードタイプの追加は非常に素直に行われます。
source/blender/blenkernel/BKE_node.hここではノード固有の識別子を定義します。これはハードコードされた整数値の define で、(ファイル読み込み時の処理をコーディングしない限り)絶対に変更できません。未来・過去の互換性のため、新しいノードには新しい数字が常に使用されているため、もし、古い定義によるノードを削除した場合、再使用してはいけません。
現在、1-100の範囲が共通のノードに使用されており、100-200が Shader(Material)ノード用、201-300が Composite ノード用です。これらの範囲は、多分制限しすぎてる感がありますが…。
この一番上でインクルードしているファイル内で、ソケットとノードの構造体を定義しています。この定義は C ファイルでハードコーディングされている必要があります。
source/blender/blenkernel/intern/node_shaders.c
source/blender/blenkernel/intern/node_composite.c現在、これら二つの C ファイルにはノードの定義と実行コードが書かれています。それぞれのノードには三つのコードの塊があります。
1) ソケットの定義
- Input と Output ソケットにはユニーク(一意)な名前が必要です。この名前はタイプ定義が正しいかを確認するために使用されます。よって、ユーザによる定義や、簡単に変更することはできません。
- 当面は、常に Input ソケットを "limit = 1" で、Output ソケットを "limit = 0" で使用します。これは後の段階で消えるでしょう。
2) コールバックの実行
コールバックの実行は、ノードツリータイプとは独立しており、スタックデータの入力を、必要とするタイプにキャストする必要があります。
コールバックは完全にスレッド化できるようしておく必要があるため、関数内でデータを初期化したり、Static データを使用してはいけません。
二つの実行タイプがあります。すべてのシェーディング(Material)ノードがノードツリーの評価コール毎に評価されるタイプと、Composite ノードが一度に連続して(もしくはスレッド時には並列して)評価されるタイプです。
3) ノードの定義
- ノード名はユーザによって変更される可能性があり、タイプの定義名のみがデフォルトに提供されます。
- "node クラス" が描画タイプ(テーマ使用可能)の定義と、新しいノードが "Add" メニューのどの階層に挿入されるかを検出するのに使用されます。
ノードが「実在する」ようにするには、その定義へのポインタをグローバル変数の bNodeType 配列(C ファイルの最後)に追加する必要があります。これは後々に動的リストになり、Python でノードを定義可能にする予定です。
更に詳しい情報は Wiki 上のチュートリアルをご覧下さい。
http://mediawiki.blender.org/index.php/BlenderDev/AddingANode
Last update: Jul 02 2006.
This section is maintained by Ton Roosendaal.
元記事:
Generic Node system for Blender