NPRのためのCyclesマテリアルの基礎知識
これはBlenderアドベントカレンダー2016の12/11の記事です。
もう12月も中盤に差し掛かりつつ今日この頃。 みなさんBlenderでクリスマスカードやら年賀状やらを作っていたり、作ろうと計画してたり、出来るんじゃないかなとタカをくくっていたりしている頃ではないでしょうか。
そんな中、ここはひとつとても基礎的なところからCyclesのマテリアルを眺めてみようというものです。
とても長いポエムに仕上がってしまったので結論だけ見たい方はこのあたりからどうぞ。
ちなみにNPRのためというのは若干釣りですが少しはヒントになるかもしれません。
Cyclesとは
マテリアルの話をする前にまずはCyclesとは何かのおさらいです。 BlenderのユーザーマニュアルのCyclesのIntroductionにはこんな風に書かれています。
Cycles is Blender’s ray-tracing production render engine.
折角なので訳をつけてみましたが日本語でOKという感じですね。 プロダクションレンダーエンジンとはなんだか謎ですが、Blenderで作ったものをちゃんとレンダリングできる、くらいの意味でしょうか。
しかしここで重要なのはレイトレーシングという部分です。
レイトレーシング
日本語では『光線追跡法』と呼ばれたりします。
Wikipediaの日本語ページにはこんな感じのことが書かれています。
正直ちょっと微妙なので必要なところだけを引用すると
仮想的に逆方向に追跡し、その方向に何が見えるかを判定する
これだけです。
現実では光源を発した光はシーン中のオブジェクトに当たって反射や屈折を繰り返します。 そしてカメラに到達し、画素に光のエネルギーが蓄積されて『見える』状態になります。
しかし素直に光源から光線をばらまいてレンダリングしようとすると、ほとんどの光線はカメラに到達せず無駄になります。
そこで、逆にカメラの方から仮想的な光を発射することで効率良く計算しようというのがレイトレーシングという手法です。
向こうから光線が届くならこちらからでも届くはずという、深淵を覗く時、深淵もまたこちらを覗いているのだ理論です。
そして、Cyclesではレイトレーシングを利用したパストレーシングという手法でレンダリングを行います。
パストレーシング
日本語では『径路探索法』と呼ばれたりします。
パス(径路)とは、カメラと光源を結ぶ径路です。
こんな感じの径路がシーンの中には無数に存在します。 パストレーシングではまずレイトレーシングを使ってシーンの中からこの光の経路を見つけ出します。 そしてその経路を通った時にどのくらいのエネルギーが光源からカメラに届くかを計算します。 1つのピクセルに入ってくる経路は無数にあるので、設定したサンプル数だけ同様の計算を行い、積分することで最終的なピクセルの色を求めます。
いきなり積分するとか難しそうなことを言っていますが、ここでは『全部合わせる』程度の意味です。 無数にある経路は単純に数えられないのでこんな言い方をします。
そして、マテリアルはこの計算の過程で使われます。 径路上のレイがオブジェクトと交差し、方向を変える(屈折率1の場合など変わらないこともありますが)ときに、入ってきたエネルギーと出ていくエネルギーの関係を決めるのがCyclesにおけるマテリアルの役目です。 
レンダリング方程式
世の中には数式を書かない方がわかりやすいという風潮がありますが、数式を書いてしまった方がわかりやすいこともあります。 というわけでレンダリング方程式です。
はいそこウィンドウを閉じようとしない。これは反射の場合だけの式です。
とあるオブジェクト上の点xからある方向w_oへ出ていく光のエネルギーL_oは、そのオブジェクト自身の発しているエネルギーL_eと外から来た光w_iが反射してw_oの方向へ向かうことになったエネルギーを全部合わせたもの、という方程式です。
方程式と言うと身構えてしまうかもしれませんが、よくよく考えれば当然のことを言っているだけです。 なお、最上段右端のスザンヌからオレンジのものが放出されているように見えますが、これは入射ベクトルw_iです。 このベクトルは『エネルギーが来る方向』を指すのでこの向きになります。
さて、パストレーシングではこの方程式に基づいた計算を径路の各頂点で行って、カメラにたどり着くエネルギーを求めます。 そして、実際にCyclesがこの方程式をどのように料理しているかというと、こんな感じになります。 
積分の中の部分に注目してみましょう。 CyclesのマテリアルではShaderという項目からBSDFというノードを追加します。 Bidirectional scattering distribution function(双方向散乱分布関数?)の略で、反射や透過の特性を表す関数です。
外からかかっているcosはランベルトの余弦則という法則に基づいた項です。 これは光の当て方による陰影の変化を表す項になります。 ちなみにランベルトをアルファベットで書くとLambert。お馴染みの名前です。
この光の当て方による陰影の変化を外に出してある、ということは、マテリアルの中では陰影計算は行われないということです。
余談になりますが、ランベルトの余弦則を分離したLambert BSDFには一体何が残っているのかと思う方もいるかもしれません。 実際のところ何も残っていなくてただの定数です。
これだけだとピンとこないかもしれないので、参考までにスキャンラインレンダラーのInternalの場合です。
きっちり分離されていないのでむしろよく判らないかもしれません(笑)
Internalでは光は光源の方向からしか来ないということにして、即座に積分した結果を計算します。 またマテリアルのDiffuseやSpecularは陰影を含んだ結果を出力します。
ちなみにノードマテリアルに出てくるのは紫の部分になります。
PBR?
物理ベースレンダリング(PBR)というものがちょっと前から流行っています。 大雑把にいえば物理的にそれっぽい設定のマテリアルを使おうという感じのことです。
たまに誤解を見かけるのですが、Cyclesは物理ベースレンダラーとはうたっていません。 とはいえ大域照明を扱えるので物理的に破綻していないマテリアルを設定すれば、もっともらしい結果を得られます。
しかし一方でいくらでも物理的にあり得ないマテリアルを作ることもできます。 addノードがあるので反射するエネルギーを入射より増やすことだってお茶の子さいさいです。 エネルギー保存則なんてどこ吹く風です。
ただ、エネルギーは可視光の範囲の外の光にもあるということは覚えておいて損はありません。 青空の下の真っ白なシャツなどありがちな絵ですが、洗剤や染料に紫外線のエネルギーを吸収して可視光を放出する蛍光剤が入っていたりします。 そんな時はdiffuseあたりを少しaddするのが実は物理的に正しい、なんてこともあるかもしれません。
NPR_
だいたい書きたいことは書いたのでこれで終わりでも良いのですが、それでは本当にタイトル詐欺になるので最後にCyclesでNPRをやるのにどんな方針が良いかを考察しておこうと思います。 と言いつつ実はNPRはほとんど実践していないので基本的にただの妄想です。
とりあえず踏まえておくことは以下の通りです。
- Cyclesのマテリアルの出力するものはライティング結果の陰影ではない
- Cyclesは物理的に正しくないマテリアルも作ることができる
ここから考えてみましょう。
コンポジット
ところでいわゆるライティングの結果はどこにあるのか、といえばそれはレンダリング結果です。 Cyclesのレンダーレイヤーには非常に細かく分解されたレンダーパスがあります。
マニュアルにはどのパスをどう合成すれば元の画像が得られるかも書かれています。 これらとコンポジットノードを組み合わせれば結構いろいろできる気がします。
最近流行りのディファードレンダリングのゲームエンジンのTipsなどが参考になるかもしれません。 ゲームはトゥーン調の需要が多いようで、ポストエフェクトで頑張る例が散見されます。 出力されるバッファが制限されるゲームエンジンよりも楽にできる部分も多いでしょう。
またポストエフェクトをかける前提でモデルに細工しておくのもいいでしょう。 ノーマルマップに絵筆っぽいタッチを入れていた格闘ゲームもありました。
マテリアル
マテリアルにできる細工もあります。 ライトパスノードがあり、今計算しようとしているのがどのような条件のパスなのかを知ることができます。 ライトパス自体の説明はマニュアルにまとまっています (日本語) 間接光を強調するために特定のパスの時は強めの色に差し替えるなんてこともできます。
それからインプットのジオメトリーノードにIncoming(入射)というベクトルがあります。 入射ベクトルなので1つ前のオブジェクトと当たった点の方を向いたベクトルなのですが、レイトレーシングなのでよりカメラに近い方向を指すベクトルです。 シャボン玉のような視線に依存する色を出すのに使われたりします。 単純なリムライトのようなものならフレネルを使った方が楽な気もしますが、なんとかとハサミは使いようでしょう(丸投げ
Internal
アニメ調のはっきりした陰影が欲しい。しかも単純な明暗では物足りない、という要求もあると思います。 Internalでよくある方法がLambertの出力を直接カラーランプにつないで色を指定するというものです。
しかしCyclesのマテリアルではそのような使い方をするのは不可能です。
そんな時には素直にInternalの助けを借りましょう。 Internalでレンダリングするシーンを用意すればレンダリング結果を取得できるので簡単です。
またInternalの方でもノードマテリアルを使うようにすると、1つのマテリアルで両方を調整できて便利です。
実際にはこんなに簡単なノードではないのでスパッゲティ大盛り2皿分になりますけどね! あとはコンポジットノードで必要なものを組み合わせるだけです。
最後に
とりとめなく書いてきましたが、実はレンダリング方程式を書いてみたかっただけです(オイ 長々とおつきあいありがとうございました。
明日はpronamaさん、プロ生ちゃんでしょうか。楽しみですね。