ShaderForgeを使ったImageEffectの作り方

今回はShaderForgeを使ってImageEffectを作っていきたいと思います。
ShaderForgeはUnityでShaderをプロシージャルにいじれるAssetです。
ノードで組んだあとにShaderの中身も見れるのでShader勉強ツールとしてもかなりオススメです。
https://www.assetstore.unity3d.com/jp/#!/content/14147


同じようにShaderをプロシージャルにいじれるAssetに
「AmplifyShaderEditor」というAssetがあります。
https://www.assetstore.unity3d.com/jp/#!/content/68570


ShaderForgeとAmplifyShaderEditorの違い
この二つの大きな違いは下記になります。
・ShaderForgeはVertexShaderとFragmentShaderをベースにShaderを書いてくれるのに対して
・AmplifyShaderEditorはSurfaceShaderを使って書いてくれます。

SurfaceShaderはUnity独自のShaderライブラリですが
基本的にSurfaceという名前のとおり、表面の質感を比較的ラクに書けるライブラリです。

ImageEffectはFragmentShaderで書くのでSurfaceShaderでは書けません。
よってAmplifyShaderEditorではImageEffectが作れません。

その代わりAmplifyShaderEditorには便利なノードが結構ありました(Unity道場のAmplifyShaderEditor講座で一通り触ってみた程度の感想ですが)
という感じの違いがあります。


個人的にはVertexShaderとFragmentShaderで書いてくれるShaderForgeのほうが好きです。

しかしSurfaceShaderをどうやって書くのか勉強する時にはAmplifyShaderEditorを使うと便利かと思います。

若干ややこしい説明になってしまいましたが今回はImageEffectということでShaderForgeを使っていきます。


ImageEffectの原理
その前にImageEffectの解説から入りたいと思います。
ImageEffectを簡単に説明すると「一回レンダリングした絵にエフェクトをかけて弄ること」です。PostEffectとも呼んだりもします。


基本的なレンダリングパイプラインを超簡略化して書くと


VertexShader

FragmentShader


ですがImageEffectは一度FragmentShaderでレンダリングした結果をコンピュータの内部に保存しておいて、その保存しておいたデータを描画する画面の大きさと同じ一枚の板ポリゴンにテクスチャとして貼り付け
そこでもう一回FragmentShaderで加工した結果を最終的に描画する。という感じの原理になっています。


もうちょい詳しく説明すると
一回目のレンダリングは画面に表示させないでコンピュータの内部でバッファとして保存しておきます。
これを「オフスクリーンレンダリング」と呼びます。
オフスクリーンレンダリングで一度レンダリングして
もう一度VertexShaderに戻って画面の大きさと同じ一枚の板ポリゴンを作って
その板ポリゴンにオフスクリーンレンダリングレンダリングした結果をテクスチャとして貼り付けて
最後のFragmentShaderでImageEffectをかける
という流れです。


VertexShader

FragmentShader

VertexShader

FragmentShader


という感じですね。
若干ややこしいですがこれがイメージ出来るとこれから先
画面の中で何が行われているのか?という部分がイメージしやすいと思います。


カメラにアタッチするスクリプト
それでは実際にShaderForgeを使ったImageEffectを作成していきます。
まずはc#を用意します。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

[ExecuteInEditMode]
public class posteffect : MonoBehaviour {

    public Material effect;

    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        Graphics.Blit(source, destination, effect);
    }
}


一つずつ説明していきます。

[ExecuteInEditMode]

これは再生をしなくてもスクリプトを実行するmodeを指定しています。
再生をしている時だけにImageEffectをかけたい場合は外して良いです。

public Material effect;

マテリアルの変数を定義します。

private void OnRenderImage(RenderTexture source, RenderTexture destination)

OnRenderImage関数はレンダリングしたら呼ばれる関数です。
第一引数のRenderTexture sourceは一回目にオフスクリーンレンダリングした結果が入ります。
第二引数のRenderTexture destinationはImageEffectで加工された後のデータが入ります。

Graphics.Blit(source, destination, effect);

Graphics.Blit関数はimageEffectを使う時に使う関数です。
第一引数のsourceは元のテクスチャ
第二引数のdestinationはsourceをShaderに渡して加工した結果が入る。
第三引数はdestinationの結果を入れるマテリアルが入ってます。


ようはオフスクリーンレンダリングをしたら
OnRenderImageが呼び出されて
その中のGraphics.Blitでオフスクリーンレンダリングの結果をShaderでいじってdestinationにアウト
その値をマテリアルに入れて最終的な画面を描画するイメージです。


ShaderForgeの設定
次にShaderForgeの設定
ShaderForgeをImportしたら
window > ShaderForge > New Shader > Post EffectでShaderを作ります。

次にShaderを右クリック > create > materialでマテリアルを作ります。
ここで先ほど作成したc#スクリプトをカメラにアタッチ。
続けてカメラに当てたスクリプトに先ほど作成したマテリアルをアタッチ。


そしてShaderをShaderForgeで開きます。
まずUV Coordノードで現在の画面に対しての座標(UV)を定義してあげます。
その次にTexture2Dノードを繋げてノードの名前をMainTexにする(これをMainTexにしないとpostEffectとして使えない)
このMainTexが二回目のレンダリング結果を貼り付けるテクスチャと考えてください。

この時点ではまだgame画面が黒いままです。

次に左側のLighting > RenderPathを一回DeferredにしてからForwardに戻すとgame画面が見える。(これは原因不明ですが、やらないと画面が黒いままです)

これで基本のセッティングは完了です。
f:id:shinobigiken:20170707143412p:plain


ImageEffectでUVを弄る

画面のUV座標を定義しているのはUV Coordなので
画面を歪ませたかったらこのUVを弄れば良いです。

この「UVを弄る」という感覚を理解しづらい場合
FragmentShaderを勉強すると良いと思います。
触りの部分だけでも知っていると
どういう計算をすると、どういう結果になるのか?が想像しやすくなります。


UVを回転
それでは実際にUVを弄ってみたいと思います。
例えばUVCoodにRotatorを繋げてUVを回転させると
f:id:shinobigiken:20170707135459p:plain

下記のようになります。
f:id:shinobigiken:20170707135729g:plain

これはUVを回転させてるので画面が回っているように見えている。
ということですね。


UVにノイズを足す
続いてUVにノイズをかけてみます。
原理としてはシンプルです。
ノイズのテクスチャを用意して、CompnentMaskでRとGだけの値を取り出して
その値をUV座標に加算させてMainTexのUV値として定義しています。
f:id:shinobigiken:20170707151827p:plain

ShaderForgeの設定が終わったら
マテリアルのInspectorにノイズのテクスチャをアタッチします。
f:id:shinobigiken:20170707153053p:plain

こんな感じでUVが歪む事によって画面にノイズが入ります。
f:id:shinobigiken:20170707153402p:plain


ImageEffectで画像を加工する
次は二回目のレンダリング結果に対してFragmentShaderでエフェクトを足していきたいと思います。

二回目のレンダリング結果に対してFragmentShaderでエフェクトを足す場合は
MainTexの次に何かしらの処理を与えます。
下記はPosterizeで色の階調を調整出来るようにしてあります。
f:id:shinobigiken:20170707154828p:plain
Inspectorで値を弄ると下記のようになります。
f:id:shinobigiken:20170707155125g:plain


コントラストを調節

Powerでコントラストを調節しています。
f:id:shinobigiken:20170707155720p:plain
Inspectorで値を弄ると下記のようになります。
f:id:shinobigiken:20170707160150g:plain


Vignette(ビネット)の実装

Vignetteとは、周辺を黒く落として味のある絵にする手法で、よく雰囲気のある絵作りに使用されます。

それではShaderForgeの中身を見てみましょう。

f:id:shinobigiken:20170711104534p:plain

まずUV座標をRemapで(-1,1)に正規化します。
これはデフォルトでは左下に原点が設定されているUV座標を真ん中に原点を持っていく作業です。


次に繋げられているMultiplyですが
その次に繋がってるLengthと関係しているので一旦説明を飛ばします。


Lengthですがこれは簡単に説明すると
原点からの距離返してくれるノード(関数です)
現状原点は中央にあるので、Lengthから返ってくる原点の値は0になるので黒くなっています。原点から遠ざかるにつれ値が上がっていくので徐々に白くなっています。(1以上は真っ白になります)一個前に繋いでいたMultiplyですが、この値を大きくすることで円の大きさを調節していました。

次にOneMinusで値を反転させ、その次にPowerで値を累乗することでぼかしの距離を調節しています。

最後に元々描画されている絵に乗算させることでVignetteがかかります。

f:id:shinobigiken:20170711111016g:plain

以上がShaderForgeを使ったImageEffectの作り方の基本になります。

ちなみにwebGLでのImageEffectの記事を探すと資料が結構出てくるので
考え方を学ぶという意味で資料としてオススメします。


お勧めのリンク(webGLのエフェクトサンプルですがソースコード付きなので参考になります。)
glfx.js