2011-11-30

メモ 反転選択

for n in aln:

    if n.knob('selected').value() == True:
        n.knob('selected').setValue(0)
    else:
        n.knob('selected').setValue('True')


追記:
ああああ!
alnを定義してない!!
nuke.allNodes()で全ノードを表せるので

正しくは

aln = nuke.allNodes()
for n in aln:

    if n.knob('selected').value() == True:
        n.knob('selected').setValue(0)
    else:
        n.knob('selected').setValue('True')

2011-11-27

ToolSets と python の組み合わせ その1

ver6.3 よりToolSetsという便利なものが搭載された。
要するに、ノードの組み合わせ、もしくは単体でもいいのであるが、パラメーター等をあらかじめ組んでおいたものを呼び出せるというツールだ。スタジオやもしくは各アーティストごとのカスタムツールが容易に組めるというモノである。


ツール化したいノード(群)を選択して、ToolSets → Create ってやると名前をつけるように求められるので、適当に名前をつけるとその後からはこの ToolSets にその名前で、その選択したノード(群)をワンアクションで呼び出せるようになります。Photoshopの アクション に近い感覚です。

実は、選んだノードだけが、「.nuke」ディレクトリにある「ToolSets」フォルダに.nk形式保存されているだけだ。


なので、後からでも、修正したり、ノードを追加したりも割りと簡単に出来る。

コレを用いてたとえば、Writeノードを作るツールを作ってみる。
Writeノードをつくるなんて簡単じゃないか! って思われるだろうけど、Writeノードは割りとメンドイ。フォルダ切って、名前付けて、フレーム数指定して・・・ など。
そのあたりを勝手に都合よくやってくれればトライアンドエラーを億劫にならずに出来るかもしれない。それがコンプのクオリティーアップにつながる可能性もある。なによりも、「どこにレンダーしたっけ?」みたいなケアレスミスも防げる。
といっても、それをやるためには各個人なのか各スタジオでなのか、ともかく法則性が、つまりは明確なルール付けが必要である、とくにディレクトリー構造においては。決まってさえいればそれに則ってやるだけなので、スクリプト等でツール化が可能だ。

nukeではノードのプロパティー上で右クリックをすると、ユーザー定義の変数を設けることが出来る。



その中に「python scripts button」というのがあり、ボタンを押すと指定したpythonスクリプトを実行するというなんとも便利なモノがある。


とりあえず適当なノード(ココではBackdropノード)にこの「python scripts button」を設けて、そのボタンを押すとWriteノードが出来るというものを作る。

単純にWriteノードを作るpythonスクリプトは・・・

nuke.createNode('Write')

である。なので、 「python scripts button」を選んで、出てくるエディットウィンドウのScript欄に上記のスクリプトを入力して

 
として、OKでボタンを作成する。


するとこんな感じでボタンができて、押すとWriteノードができる。
と、これでは出来るだけなので、このWriteノードにある各種の値を変えたいのだけど、できるWriteノードの名前は任意(というか同一スクリプト(シーン)内のWriteノードの数に応じて番号が付く)なので、名前を指定して値を設定するってことが出来ない。
こういうときは下記のようにできる

cw = nuke.createNode('Write')
cw.knob('file').setValue('hogehoge')

コレを同じく、「python scripts button」の設定ウィンドウのScript欄に入れて、


ボタンを実行すると・・・


となり、「hogehoge」がファイルパスに入っている。

ボクの場合、.nk を 各プロジェクトディレクトリにある「nukeScripts」というディレクトリに保存して、nukeのレンダー結果は「nukeRender」というディレクトリに保存している。
なので、たとえば、.nkの保存場所から判断して、Writeノードからのレンダー先をある程度決め込むことが出来る。とりあえず、「nukeRender」ディレクトリンに.nkと同じ名前のフォルダを切ってその中に連番をレンダリングするとする。
.nk の保存先は「Project Settings」の「name」というknobから取得できる。


(ちなみに、「Project Settings」はノードグラフ上で[s]キーを押すと呼び出せる)
これをpythonで取得するには

nuke.root().knob('name').value()

である。この.nkのファイルパス

C:/Users/Jean/Documents/test/nukeScripts/cut01_v001.nk

であるが、この文字列の

1.nukeScripts を nukeRender に変える。
2.「.nk」 を削除する。
3.最後尾に 「/(.nk名)_%04d.tif を足す。

とすると、4桁のtiff連番でレンダ先を指定できる。


1を行うにはpythonの機能 replace を用いる。
.nkのファイルパスが nuke.root().knob('name').value() なので、これを一旦 nkPath とかにして、

nkPath = nuke.root().knob('name').value()
nkPath.replace('nukeScripts', 'nukeRender')

とすると nkPath として C:/Users/Jean/Documents/test/nukeRender/cut01_v001.nk
を得ることが出来る。

(表記するためにprintしてます)

さらに、.nkを削除(空の文字列と入れ替える)して

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')



となります。ここで.nkのファイル名は /(スラッシュ) 区切りで文字列を小分けにした一番最後の文字列なので、split を用います。ここで

nkPath.split('/')

とすると


['C:', 'Users', 'Jean', 'Documents', 'test', 'nukeRender', 'cut01_v001']

となり、/(スラッシュ)区切りで小分けにされたタプル型のリストを得ることが出来ます。
リストの最後は

(リスト名)[-1]



で表せるので、



この場合結果として.nk名である「cut01_v001」を得ることが出来ました。
なので、このファイル名を使ってレンダー先をファイルパス(renderPathとしました)を作成します。

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')
list = nkPath.split('/')
renderPath = nkPath + '/' + list[-1] + '_%04d.tif'



C:/Users/Jean/Documents/test/nukeRender/cut01_v001/cut01_v001_%04d.tif
を取得できました。

でこれを、さっきのWriteノードに代入して(hogehogeの変わりにコレを入れる)

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')
list = nkPath.split('/')
renderPath = nkPath + '/' + list[-1] + '_%04d.tif'
cw = nuke.createNode('Write')
cw.knob('file').setValue(renderPath)


で、ボタンを実行すると


ファイルパスに狙い通りのモノが入ったWriteノードが出来上がる。
ただ、 このレンダ先である、

C:/Users/Jean/Documents/test/nukeRender/cut01_v001

というディレクトリがない場合もあるので、それがない場合はそのディレクトリ(フォルダ)を作るという処理を加えたいと思う。

C:/Users/Jean/Documents/test/nukeRender/cut01_v001

はさっきのスクリプトの途中の結果

nkPath = nuke.root().knob('name').value()
nkPath = nkPath.replace('nukeScripts', 'nukeRender')
nkPath = nkPath.replace('.nk', '')

とまでやったところの、nkPath である。
なので、上記3行のあとに、

if not os.path.isdir(nkPath):
    os.mkdir(nkPath)

を加える。


この

os.path.isdir(hoge)

ってのいうのは、hoge というディレクトリがあれば True を返す。

if not os.path.isdir(hoge)

なので、このディレクトリが無ければ

os.mkdir(hoge)

で hoge というディレクトリを作成しなさい ということである。pythonではこのように、

条件文:
 実行文

とし、条件文の最後に「:(コロン)」加え、改行してインデント(tabもしくはいくつかのスペース)の後に実行文をおく。

これで、ボタンを押せば.nkの保存先から nukeRender/hoge/hoge_%04d.tif と自動的に出力先をファイルパス、及びそのディレクトリが無ければ作成するというボタンが出来た。


で、このボタンをもっている、Backdropノードを選択して、ToolSets に登録すれば次回からはコレを簡単に呼び出せる。また「python scripts button」はこのBackdropノードに複数設定できるので、ある程度のツール群を入れておくとかなり便利だ。

ウチでは


こんな感じで、ディスパッチャーである「deadline」に投げるために、ファイルパスをローカル⇔ファイルサーバーと入れ替えれたりなど色々な組み合わせで使っている。

次回はレンダ後プレビューファイルを作るまでを自動化させたり、そのプレビューファイルを外部から見れるように別のディレクトリにコピーしたり、それをメールで送ったりって言うことをやってみたいと思います。

2011-11-16

expression内での条件わけ(if)

簡単に説明


こんなグラデーションがあるとして、このグラデーションのピクセルに対して条件わけがしたい場合・・・(この場合rgbaのR値が0.5より大きい場合0にして、それ以外で1にするという例)

その下にExpressionノードをぶら下げ


Expressionノードで




r>0.5?0:1


の記述を入れる。

(条件) ? (結果) : (条件以外の場合の結果)

である。




こんな感じ。

2011-11-15

3Dコンポジット時のチャンネルについて

3Dコンポジットはとても有効。
たとえば演者がグリーンバック、ブルーバックで撮影された素材複数あるとして、それをキーアウトしてcardノードを用いて立体的に配置してやれば、ある程度のカメラワークにも耐えることもでき群集シーンにはとても適している。

それだけであれば、AfterEffectsでも可能だが、nukeの利点はカメラプロジェクション、プリミティブ以外のジオメトリの追加などいろいろと利点もある。

通常のrgbaだけならそんなに問題はないのだが、その他のエキストラのチャンネルの扱いには少し癖があるので、ちょっとした説明を。


こんな感じでアルファチャンネルを持ったcardノードが3枚ある。これらはすべて、通常のrgba以外に、[test]というチャンネルをRGBAで別に持っている。
ざっくりと説明して・・・

nodeA

nodeB

nodeC

それぞれ、上が[rgba]チャンネル 下が[test]チャンネル、左がRGA 右がA である。
nodeABCそれぞれ[rgba]として白色のマスクされた形状を持っており、nodeAは[test]チャンネルとして、同じ形状で赤でアルファありのもの。nodeBは[test]チャンネルとして何も持ってない。nodeCは[test]チャンネルとして青のアルファなしである。

これらをScanlineRenderノードを通してみると・・・

(ScanlineRender)

[rgba]チャンネルは上のようになる。おそらく「over」の計算式が適応されているようである。
[test]チャンネルはというと・・・
(ScanlineRender)


となり、

nodeAの円がnodeBの円によってくりぬかれてるのが確認できる。

nodeBの[test]チャンネルをもう一度見てみると・・・


下段[test]チャンネルにはRGBAともに何もないはずなのに、結果として、Bの円より後ろにあるAの円がくりぬかれている。
これは実は、「3Dコンプの合成計算式に用いるA(アルファ)はrgbaのAである」ということに起因している。
なので、nodeBのrgba.Aは真っ黒ではなく、ちゃんと塗り形状が存在し、overの計算式

output = A + B*(1-Aa)

ややこしいが、この場合で前景であるnodeBがA、背景であるnodeAがBである。
よって「Aa」の部分で

A が (nodeB.test.RGB)   a が (nodeB.rgba.A)

となる。
そのため、黒がアルファ値1によって上から塗られている状態である。
また、この3Dコンプのアルファチャンネルを見てみると

(ScanlineRender)

となり、どうやら、アルファの部分を別でさらに計算しているようで、

A が (nodeB.test.A)   a が (nodeB.rgba.A)

で計算しているようだ。
なので、nodeBのアルファを0にすると

nodeB

nodeBの[rgba]のアルファを0にした結果(ScanlineRender)
上記のアルファ(ScanlineRender)

となる。


nodeCの円弧のラインがAの円の上に透けているのが確認できる。



気をつけたいのはアルファは常に[rgba]のアルファを参照しているということである。
これをうまく使えば、グリーン素材とその背景の煙素材なども、普通にcardで配置して、煙のチャンネルだけを別に[smoke]チャンネルとかに格納して取り出せば、煙だけmerge operation(over, plus, screen等)を変えて乗せることが可能だ。



(ScanlineRender)
(ScanlineRender)

となり、mergeで結合すると



mergeの結果

が得られる。

2011-11-11

追記:Beautyの再構築

先日の追記です。
コンプでBeautyを再構築したとして、前回問題であった
--------------------------------------------
RawReflection*ReflectionFillter = Reflection
--------------------------------------------
の部分をCG側で直接だしたとして(パスをRawReflectionとReflectionFillterに分けず、Reflectionとして出力)、その後コンプの調整でReflectionを抑えたいとなった場合・・・


となってしまう。
やはりダイナミックレンジの恩恵は大きく、本来であれば

exrからBeautyを再構築したものから調整
となってしかるべきであろう。

2011-11-10

Beautyの再構築

CGを用いたコンプの場合、各レンダーパスをもとにしてBeautyの再構築をしなければならないことがある。こんかいは、必ずしも一般的ではないが、vrayを用いてレンダーパスを出力し、それをnuke内で再構築したいと思う。

こんな感じでレンダーされているとする。



 各レンダーパスの計算式は以下で成り立つ

------------------------------------------------------
diffuse*RawGI = GI
diffuse*RawLight = Lighting
GI + Lighting = TotalLighting

reflectionFilter*RawReflection = Reflection
refractionFilter*RawRefraction = Refraction

TotalLighting + Reflection + Refraction + Specular = Beauty

------------------------------------------------------

レンダーパスはAOVsにしてexrを用いて作成した。
nukeでLayerContactSheetノードを用いて表すとこんな感じ。


まずは、
------------------------------------------------------
diffuse*RawGI = GI
diffuse*RawLight = Lighting

------------------------------------------------------
から、

diffuse
RawGI
RawLighting

で掛け算(multiply)して



ここでは、Multiplyノードの中のA、Bチャンネルでチャンネルを指定している


がShuffleノードをつかって、


とやっても可能。
できあがったGIとLightingを足して


TotalLighting
とする。
と同様の方法で、ReflectionとRefractionを得るのだが、実はこの最終的に再構築しようとしているBeautyは


viewerの右上にある「cliptest」で確認するとわかるのだけど、1以上の値がある。実際には、exrはダイナミックレンジを収録できるので、


とこんな感じで、1以上の値を確認できる。
これをtargaのような8bitの画像でレンダリングすると、


こんな風に1以上の値は自動的にクランプされ、「cliptest」をオンにしてもダイナミックレンジ領域(?)が確認されない。

これがどのように影響されるかというと・・・
たとえば、クランプされた画像(要するこれは8bitの画像と同じことであるが・・・)でBeautyを再構築すると


という具合にハイライトの部分がいまいち上がりきらない画像になる。
これは、 今回のケースの場合
------------------------------------------------------
reflectionFilter*RawReflection = Reflection
------------------------------------------------------
の部分でのRawReflectionが

exrを直接viewerで確認

exrをクランプしたものをviewerで確認(8bit画像を想定


という感じで、8bitの画像でも、ダイナミックレンジを持っている画像でも、見た目はハイライトで白く飛んでいる部分があるが、実際にはレンジの違いがある。

RawReflctcionをcliptestで見た場合
これに、ReflectionFilterを掛けると、この白く飛んでしまっている1以上の領域の階調が1以内に収まってる部分ができ、その部分にグラデーションを確認できる。クランプした画像(8bitを想定)だと、どうしてもそうはいかない・・・(下記例ではわかりやすくするため、Multiplyノード(math)で0.05を掛けてます)

ReflectionFilter
exrに掛け算をして暗くしてみた例。1以上で飛んでいたハイライトの中にグラデーションが生まれる。

exrをクランプしたもの(8bitを想定)に掛け算をして暗くしてみた例。1以上で飛んでいたハイライトがくすんでつぶれる。
結果として、

オリジナル
exrからBeautyを再構築
exrをクランプしたもの(8bitを想定)からBeautyを構築

という差が生じる。もちろん、ピクセルの値が1を超えないものであれば、問題が無いケースも多々あるであろうが・・・