Blender Compositor ノードエディタでの小ネタ・小技集

> この記事は [Blender Advent Calender 2017](https://adventar.org/calendars/2391) 12/16 日目の記事です。

主にBlenderでコンポジットノードを弄る時用の細かいTips集です。

## いくつかのショートカット操作

### Ctrl+Shift+クリック でビューワ設定

![image](https://qiita-image-store.s3.amazonaws.com/0/152179/0d7b7a4a-612c-d6cb-8d97-52f07ce489cc.gif)

クリック先のノードにViewerノードを接続。`Backdrop`のチェックを入れておくことで各ノードの結果を素早くチェックできて便利。複数Viewerノードがある場合は最後に選択されたViewerノードが使用されるようです。

ちなみに標準で同梱されている`Node Wrangler`アドオンを有効化しておくと、Cyclesマテリアルノードでも同等の機能が使えるようになって便利です。また他にもノード操作を行う上で便利なショートカットが有効になるので事実上必須。有効にしておきましょう。

> 参考：
> Blender Addon Review: Node Wrangler（ノード使い必須） – CGrad Project
> http://www.cgradproject.com/archives/3553


### Ctrl+Shift+D でインプットへの接続を保ったままノード複製

![image](https://qiita-image-store.s3.amazonaws.com/0/152179/f4732954-2cfd-589c-9c6e-06686220961c.gif)


`Shift+D`で選択中のノードを複製できますが、更に Ctrl を加えて`Ctrl+Shift+D`を入力することでそのノードへの接続を保ったまま複製ができます。

### 接続の切断とRerouteの追加

![image](https://qiita-image-store.s3.amazonaws.com/0/152179/a49f0b91-7c49-a803-9e16-3edc8c5592bc.gif)


それぞれ`Ctrl+ドラッグ` `Shift+ドラッグ`でノード間の接続の接続やRerouteの追加ができます。


### 画像ドロップでImageノード追加

![image](https://qiita-image-store.s3.amazonaws.com/0/152179/9aca2682-6192-eb3b-acd8-d1bd4f071b14.gif)

UV/Imageエディタでの画像名横のアイコンをノードエディタ上にドラッグ＆ドロップすることでImageノードを追加できます。Windowsならエクスプローラからも同じ操作が可能。(MacやLinuxの場合は未確認)


### ノード追加用のショートカットキーを用意しておくと地味に便利

ノードを追加する時は`Shift+A`キーから目的のタイプを選択するのが普通ですが、よく使うノードはそれを追加する用のショートカットキーを用意しておくと便利です。

Shift+Aキーからノードを追加した時にインフォパネルに表示される情報を参考にしつつショートカットキーを追加します。

![hotkey1.jpg](https://qiita-image-store.s3.amazonaws.com/0/152179/5e5abc68-1e03-1553-9856-374b5af4d4ba.jpeg)

Blender設定の Input > Node Editor > Node Editor (Global) で Add ボタンを押し新規キー設定を追加、割り当てたいキーを入力、実行するオペレータの項目は`node.add_node`とし、Node Typeの項には例えば `CompositorNodeValue` を入れるとコンポジットノードでの`Value`ノードの追加になります。

![hotkey2.jpg](https://qiita-image-store.s3.amazonaws.com/0/152179/b3fc1ac2-5a4e-ff36-d9a7-460320eb5d99.jpeg)

上記設定では`Ctrl+Shift+V`キーで`Value`ノードが追加されるようになります。

ノードタイプは`CompositorNodeValue`の他にも`NodeReroute` (Reroute)、
`CompositorNodeMixRGB` (Mixノード)、`CompositorNodeMath` (Mathノード) 等もオススメ。

ただ上のノードタイプはReroute以外コンポジットノード専用のものなので、Cyclesノード上で設定したキーを押すとエラーが出るのが難点。共通のキーでコンポジット/Cyclesノードに対応するには他のアドオンに頼る他無さそうです。



## ノードエディタ上でGrease Pencilが使える

ノードエディタ上でも`Dキー + マウスドラッグ`でグリースペンシルが使えます。

![gp1.jpg](https://qiita-image-store.s3.amazonaws.com/0/152179/662f7b25-c220-cfcc-1978-b48f3ac0f054.jpeg)

`Dキーを押しながらTabキー`でグリースペンシルの編集モードに移行。3Dビューでの操作と同じですね。

![gp2.jpg](https://qiita-image-store.s3.amazonaws.com/0/152179/bcf146e0-05e3-42ec-1c83-aca966bd2375.jpeg)




## Maskノードを使ってノードの画像サイズを設定

Maskノード(とMixノード)を組み合わせることで画像バッファのサイズを指定することができます。典型的な利用法としてはTextureノードのサイズを自由に設定したい時など：

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/1fa2d37a-c590-e635-2817-d96370aa1d34.png" height="200">

Textureノードそのままだとその画像サイズはレンダリングパネルでのサイズに依存する形で設定されてしまいますが

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/0f599a1c-e66b-2a7d-4515-fa4942719c12.png" height="200">

MaskノードをMixノードの上ソケットに繋ぎ、Textureノードを下ソケットに繋げると、結果としてMaskノードで設定された画像サイズのテクスチャ画像が出力されます。

上のMixノードのFactor値は`1.0`に設定されてるので本来上ソケットに繋がれてる画像は無視されるだけの無意味な処理となる筈ですが、画像サイズは上ソケットに繋がれているものが継承されるという仕様になっているようです。
またTextureノードは接続先ノードの画像サイズに応じて最終的なサイズを変化させるという~~分かりにくい~~仕様になっているため、結果として上の手続きでサイズを自由に指定できるという流れになっています。

他の利用例として、画像の拡縮を行う際にScaleノードを通すだけでは画像サイズは変わりませんが、上の方法を活用することできちんと画像サイズも変化させる拡縮を行うことが可能になります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/b58a094a-4240-ffe5-da69-6529c70ad783.gif" height="200">


## Imageノードのアルファチャンネルのタイプに注意


<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/0f252683-d43b-1d74-93ea-6b2609d8d31a.jpeg" height="200">

このように半透明な箇所を含むレンダリング結果を画像ファイルに出力したとします。

コンポジットでAlpha Overノードを使って単色の上に重ねる場合、Render Layerから直接繋いだ場合は問題がありませんが


<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/719d34a3-c48e-f04f-fc2e-68544c7e7fbc.jpeg" height="200">

先程出力した画像ファイルをImageノードとして読み込み、それをAlpha Overで重ねると妙な結果になります

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/47bdc635-c6e0-15ee-2692-534739a3c9f9.jpeg" height="200">

画像のアルファチャンネルには`Straight`と`Premultiplied`の2種類の取り扱い方があり、どうやらBlenderは標準では`Premultiplied`で画像を扱うものの、Imageノードの出力は`Straight`として読み込まれるみたいです。なので透明部を含むImageノードは`Alpha Convert`ノードの`Straight to Premul`を通して変換してやることで、きちんと正しい結果が得られるようになります。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/9ad805db-1b9c-ab1b-5ee1-be53bc984032.jpeg" height="200">

またAlpha Convertノードを使わずともAlpha Overノードの`Convert Premul`チェックを入れれば、下ソケットの画像について`Straight to Premul`と同等の変換を施してくれるようです。
ただ`Convert Premul`チェックは若干仕様が妙なところがある(個人的な感想)のでなるべくAlpha Convertノードを通すようにしたほうが安全かもしれません。

> 参考：
> 文化ヒナゲシ制作所雑記帳 【Blender】アルファオーバーの扱い方について（ストレートアルファ・プレマルチプライドアルファ）
> http://matosus304.blog106.fc2.com/blog-entry-286.html





## Levelsノードで画像全体の平均値を取得

Add > Output > `Levels`ノードのインプットに画像を繋げると、その画像の平均値を取得できます。

![ave1.png](https://qiita-image-store.s3.amazonaws.com/0/152179/340be60d-4b91-37eb-76b5-f1ba753df6ac.png)

`Mean`は平均値で`Std Dev`は標準偏差を出力してくれます。
これを活用すれば、画面上に何かが写った時をトリガーとして何らかの効果を与える、といったことが可能になります。

例えば下のように元画像`A`とオブジェクトの一部のみを表示したマスク画像`B`とを用意したとして、

![ave2.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/68dd998f-8b21-2edb-ba53-273e16ea4be3.gif)


画像Bの平均値が少しでも0より大きくなったら1として見なすようにしつつMixノード(ここではAddノード)のFactorに繋ぎます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/b3ccc02f-6637-3649-d186-0b5ab07e092a.png" height="200">

Mixノードの下ソケットには適当な画像を繋いでおきます。すると――

![ave4.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/4c24b03d-5e23-ecd0-cfcb-1e755d8afb01.gif)

マスク画像の白い箇所が表示されたタイミングで別の画像が表示されるようになりました。画像内の一部の値を全体の効果に波及できるので色々使いみちがありそうです。


## Pixelateノードでモザイク

Filter > `Pixelate`ノードはモザイク処理を施してくれます。
適当な値`n`として、画像をScaleノードで一旦`1/n`倍に縮小してからPixelateノードを通し再びScaleノードで`n`倍にすると、全体にモザイク効果が出ます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/dd9339f8-2e12-28a4-05dc-1cfd35d042ff.png" height="200">

使いやすいようにグループ化しておきます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/21b5d0d2-df26-fd90-57d3-139d99dd6846.png" height="200">

Levelsノードの項でも使用した画像A,Bをまた用意

![ave2.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/68dd998f-8b21-2edb-ba53-273e16ea4be3.gif)

両方の画像にフィルタを通しつつ、画像Bの方はMathノードの`Greater Than`で2値化しておきます。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/6aaf29fa-09e8-8891-3a82-2b01c860e5c0.png" height="200">

そして画像Bの結果をMixノードのFactorとして利用しつつ画像Aの処理前と処理後とを振り分けると――

![mosaic4.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/7a8461c7-4be6-41ac-4d22-aea0d0694df8.gif)

マークした箇所に自動でモザイク処理を施してくれるようになりました。
今回の簡単な例のままではまだ若干見切れている箇所があるので、フィルタのかかる範囲をより広めにしたい場合は画像Bにブラーをかける等の前処理を入れると良いと思います。



## 直前のフレームのコンポジット出力を利用する

現在よりも前の出力結果を利用することで漸次的な画像フィルタ・エフェクトを実現できます。

### 下準備

`File Output`ノードで適当な場所にコンポジットの結果をファイルとして出力するようにしておきます

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/0d700000-97c9-a47c-fabf-54606f8c1690.jpeg" height="200">

一旦レンダリングをし、当該箇所に画像ファイルを生成しておきます。次にImageノードとしてそのファイルを読み込み、タイプを`Image Sequence`にします。`Frames`は適当に大きな値に設定し、`Start Frame`を`2`に設定します。これで準備完了です。

<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/517903d9-6d5f-dae9-0bb2-066d73b0c439.jpeg" height="200">

### 基本形

まず各フレームを普通にレンダリングした結果は次のようにしてみます。実際には背景は透過しています。

![grad3.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/c84c2985-e630-6a9b-84cd-f7bf3c3b2663.gif)

ただスザンヌが動いているだけです。
ここでコンポジットノードを次のように組んでみます


<img src="https://qiita-image-store.s3.amazonaws.com/0/152179/53dc024a-05d1-d7c9-94b6-ef54389982b4.jpeg" height="200">

結果は下のようになります

![grad5.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/c02e2001-b5cc-09d3-fbf7-e049bf6520c3.gif)

上のノードで何をしているのかというと、まずImage Sequenceノードは指定場所に存在する複数の連番画像を現在のフレームに対応して出力を変えてくれます (1フレーム目なら0001.png, 5フレーム目なら0005.png等) が、上で`Start Frame`をデフォルトの`1`から`2`に変えておいたため、現在のフレームに対して1つ前の画像が返されるようになっています(5フレーム目で0004.png等)。1フレーム目では対応する画像が無いので出力は空になっています。

またFile Outputノードはレンダリングしたフレーム毎に連番画像を指定した場所に生成するので、例えば1フレーム目なら0001.pngを、フレームを進める度に0002.png, 0003.png, ... といった具合にファイルを出力していきます。

上のImage SequenceノードはFile Outputで出力される場所を指定したのでフレームを進めてレンダリングする度に現在のフレームより1つ前の、つまり直前にFile Outputで出力したばかりの画像を参照していくことになります。

今回のメインの処理は「Image Sequenceで読み込んだ画像の上に現在のレンダリング結果をAlpha Overで重ねる」という風にしているため、フレームを進める度にそのフレームのスザンヌが重なっていき、最終的に無数に連なった出力になりました。

---

### 応用例


|残像||
|---|---|
| ![grad6_1.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/5d5af041-8af5-8dd1-53d4-75b900a549b9.gif) | <img src="https://qiita-image-store.s3.amazonaws.com/0/152179/5b693d30-b326-4623-a3a1-4fc9aadc77ac.jpeg" height="200"> |


基本形のノードのImage Sequenceノードのアルファ値を毎フレーム減衰させているだけです


|疑似流体||
|---|---|
| ![grad7_1.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/93c769f9-1b09-b086-3c71-8a484064a043.gif) | <img src="https://qiita-image-store.s3.amazonaws.com/0/152179/7478cfd4-b64d-28e6-9acc-c39e91dd1014.jpeg" height="200"> |

`Displace`ノードと組み合わせると流体のような表現になります。上のノードでは場所によってランダムな方向に変形させるために`Clouds`テクスチャを活用しています。


|疑似流体2||
|---|---|
| ![grad8_1.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/9997703b-64b1-b620-ab8d-3061c90c847b.gif) | <img src="https://qiita-image-store.s3.amazonaws.com/0/152179/d15f39d5-45b5-1920-e0ea-d0b6b78cfb1f.png" height="200"> |

`Texture`ノードのOffsetのベクトルを時間経過と共に変化させています。(ベクトルのXYZを一本化するノードはCombine RGBAで代用)
また残像の項目でやったものと同じくアルファ値の減衰もさせています。


|クリッピング||
|---|---|
| ![grad9_1.gif](https://qiita-image-store.s3.amazonaws.com/0/152179/59709689-8d95-f7a7-d049-e72296cbe195.gif) | <img src="https://qiita-image-store.s3.amazonaws.com/0/152179/e117ae51-9e2f-256e-e1e6-e16ff5e84ea0.jpeg" height="200"> |
File Outputで出力するのはオブジェクトのアルファ値のみにし、それをフレームごとに加算しています。加算していった値をMixノードのFactorに繋げれば2つの画像をオブジェクトの軌跡でクリッピングできます。


---

Image Sequenceの`Start Frame`の値を変えれば直前のフレームだけでなく2つ前やそれ以上過去のフレームも参照できます。またFile Outputを複数用意すれば複数の異なる過去の出力結果を参照できるので、やりようによってはより複雑なフィルタ・エフェクト処理が可能になると思います。





## 簡易ドライバ

コンポジットだけでなくBlender全般で使える話ですがメモがてら…。

ドライバの追加可能な数値入力欄で例えば`#frame`と入力すると、その項目にドライバが設定され、現在のフレームが自動的に入力されるようになります。
(Blender 設定で File > `Auto Run Python Scripts` を有効にしておく必要あり)

![driver1.png](https://qiita-image-store.s3.amazonaws.com/0/152179/4500c7d6-86ac-a419-2f2d-fe96a5acb992.png)

`#frame`のように簡易的な形でドライバの設定に使えるものとしては、`frame`の他にも`pi`や`e`といった定数、`sin`や`cos`等の関数が用意されてます。数値入力欄で`#cos(2*pi)`のように入力してみると確かに計算結果が返ってきます。

どのような変数や関数がこのような簡易的なドライバ設定の中で使えるのかといえば、どうやら`bpy.app.driver_namespace`の中に展開されているメンバがそれに該当するようです。スクリプトコンソールで`for i in bpy.app.driver_namespace: print(i)`と入力してみるとそのリストを見ることができます。

---

またユーザが独自に上のようなメンバを追加することもできます。コンソールで`bpy.app.driver_namespace["a"] = 123`を実行してからどこかの数値欄で`#a`と入れてみると、ドライバの返り値として`123`が設定されます。`bpy.app.driver_namespace["f"] = lambda x:x*2` のように関数を代入すれば `#f(100)` で計算結果の `200` が設定されるようになります。

![driver3.png](https://qiita-image-store.s3.amazonaws.com/0/152179/cc04b31b-7a10-9a0d-db60-8adf6bdbab18.png)

これでユーザ独自の簡易ドライバ用変数を用意することができましたが、このままだとファイルを読み込む度にリセットされてしまうので、ファイルを読み込むタイミングでスクリプトを実行するようにします。

BlenderのText Editorを開き下記コードを入力します。テキスト名を`.py`で終わるものにすると`Register`のチェックボックスが有効になるので、それをオンにするとファイル読み込み時にそのコードを実行してくれるようになります。

```py
import bpy

bpy.app.driver_namespace["a"] = 123
def f(x):
    return x*2
bpy.app.driver_namespace["f"] = f
bpy.app.driver_namespace["g"] = lambda x:x**2
bpy.app.driver_namespace["get_current_frame"] = lambda: bpy.context.scene.frame_current

def on_frame_change(scene):
    bpy.app.driver_namespace["time2"] = (scene.frame_current-scene.frame_start)/(scene.frame_end-scene.frame_start)
    bpy.app.driver_namespace["render_scale"] = scene.render.resolution_percentage / 100

bpy.app.handlers.frame_change_pre.append(on_frame_change)
on_frame_change(bpy.context.scene)
```

![driver2.png](https://qiita-image-store.s3.amazonaws.com/0/152179/adec2417-b212-9234-8891-4bb378f7de2c.png)

上記コード中では定数`a`、関数`f` `g` `get_current_frame`と、シーンのフレームを移動するごとに値を更新する`time2` `render_scale` を宣言しています。

それぞれ

- `f` `g` -- 関数の設定例( `#f(100)` `#g(f(100))` のようにして使用可 )
- `#get_current_frame()` -- `#frame`と同じ値
- `#time2` -- Timeノードにシーンの開始フレーム・終了フレームを入れた時と同じ挙動
- `#render_scale` -- レンダリング設定の解像度のパーセンテージを正規化した値

のようになっています。
`time2` `render_scale`に関してはフレーム移動のタイミングで値を更新するために`bpy.app.handlers.frame_change_pre`へのハンドラ登録を行っています。ともかくこれでユーザ独自の簡易ドライバ用変数・関数が定義できるようになりました。

ちなみに

- `#bpy.context.scene.frame_current` のようにして `bpy.~~` から始まるコードも簡易ドライバとして使えます。
  ただ記述が冗長になりがちで何をしているか分かりにくいのとメンテナンス性が落ちることから、上のように独自のメンバを定義して使うのが賢明だと思います。
- ドライバのScripted Expressionとして設定されるので、例えば`#1 if frame<10 else 0`のようにPythonの三項演算子等も利用できますね。

> 参考：
> python - Other possible expressions like #frame - Blender Stack Exchange
> https://blender.stackexchange.com/questions/2893/other-possible-expressions-like-frame

---

この記事は [Blender Advent Calender 2017](https://adventar.org/calendars/2391) 12/16 日目の記事でした。
次回は「Blenderのコンポジットノードでライフゲームする」です。