読者です 読者をやめる 読者になる 読者になる

Unityシェーダ入門 #004 Replaced Shader

 通常ユーザが編集するSurface Shaderは、画面上に見える部分の色や形を変えることが出来ます。しかし、シェーダが使われるのは直接画面に見える部分だけではありません。

1.シャドウマップレンダリング用シェーダ(影を落とすためのシェーダ)
2.影かどうか判断するためのシェーダ(影を受けるためのシェーダ)
3.ディファードレンダリングやイメージエフェクトで使われる深度、法線を描画するためのシェーダ

などです。
なお、これらのシェーダは深度や法線など、出力が固定的です。
(色に比べ、深度や法線をカスタマイズすることは稀だからです)
そのため、シェーダのジャンルごとに一つにまとめられています。

 上記1のシェーダではシャドウマップに描きこむための深度を出力しますが、例えば
Diffuseシェーダのオブジェクトと、BumpedDiffuseシェーダのオブジェクトは色が違うだけで形は同じなので、同じ深度を出力するべきです。しかしCutoutシェーダの場合、テクスチャのα値によってモデルに穴が開くので、影の形もそれにならって穴が開くべきです。
 従ってDiffuse,BumpedDiffuseなど単純な不透明オブジェクトが影を落とすためのシェーダ、Cutoutにより形が変わるオブジェクトが影を落とすためのシェーダ、の二種類が存在することになります。

 しかしそんなシェーダはSurfaceShaderの中にはありません。実行できるシェーダが見つからない場合、UnityはFallbackに書かれているシェーダを探しに行き、そこでまた必要なシェーダ(パス)を探します。
 buitin-shaderのNormal-Diffuse.shaderを見てみると、Fallback "VertexLit"という記述があります。Unityはそれを見て、VertexLit(Normal-VertexLit.shader)を探しに行きます。すると、"ShadowCaster"や"ShadowCollector"という名前が付いたパスが見つかります。これがUnityが影を落としたり影を受けたりするのに探していたシェーダ(パス)です。

 Diffuseシェーダをベースにテクスチャのα値を使ってモデルに穴を開けた場合、FallbackをVertexLitのままにしていると、穴の開かない影を落とすことになります。穴が開いた影を落としたい場合は、Fallbckの先を"Transparent/Cutout/VertexLit"にします。このシェーダファイルの中にも当然"ShadowCaster"や"ShadowCollector"があり、Clipを使って穴を開けています。
 頂点モディファイアでモデルの形状を変えた場合(樹木の葉っぱの揺れ、ビルボードなど)は、カスタムされた"ShadowCaster"パスと"ShadowCollector"パスを作る必要があります。

 最後に上記3のシェーダは、DefaultResourcesの下にある、Camera-DepthNormalTexture,Camera-DepthTextureで見ることができます。
 これらのシェーダはUnityに埋め込まれていますが、このように別途手に入れたシェーダを"Resources"という名前を付けたフォルダ下に置いたうえで変更することで、カスタマイズすることが出来ます。
 
 このシェーダはカメラから見た深度、法線を描画するために使われます。
 深度や法線を描画するたびにすべてのオブジェクトのマテリアルを変更するのは面倒ですので、Replaced Shaderという仕組みが用意されています。Replace Shaderは画面に映るすべてのオブジェクトのマテリアルのシェーダを、指定したシェーダに置き換えます。
 
 Replaced Shaderの詳細は 
Unity - Unity Manualにあります。
 上記ページにあるように、RenderTypeタグを基準にして置き換えるシェーダが決まります。
新しく形状を変更するシェーダを作る場合、新しいRenderTypeを定義し、このファイルに同じRenderTypeタグを持つサブシェーダを作る必要があります。(そうしないとSSAOなどのイメージエフェクトで結果がおかしくなります)

 Replaced Shaderは上記ページにもある通り自分でカスタマイズして好きな結果(深度、法線、ワールド座標、フレネル係数など)をレンダリングできます。



追記(2013/12/28)

 実際にReplacedShaderを使うとき、マニュアルがイマイチというか全然わかりにくかったので少し例をあげます。

 Camera.RenderWithShader(Camera.SetReplacementShaderも同様)には引数が2つあります。最初の1つは置き換えるシェーダで、これは問題ないでしょう。
2つ目はタグ文字列ですが、これはタグの"値"ではなくタグそのものの文字列です。
(例."RenderType"="Opaque"の場合は"RenderType"です。"MyTag"="hogehoge"の場合は"MyTag"です。)
 
 本来のシェーダに"MyTag"="Value1"というタグが付いているとします。
置き換えるシェーダ側には"MyTag"="Value1","MyTag"="Value2"…と値だけが変わったタグが付いたサブシェーダが複数入っているとします。
ここでRenderWithShader(置き換えるシェーダ,"MyTag")とすると、本来のシェーダに付いているMyTagの値Value1を見て、置き換えシェーダのサブシェーダから
MyTagに同じ値が入っている物を探し出し、置き換えます。"MyTag"="Value1"のサブシェーダが無かったり、"MyTag"がそもそもない場合はレンダリングされないようです。

 マニュアルに書いてある例は、
 置き換えるシェーダに、"RenderType"="Opaque","RenderType"="Transparent"...というタグの入ったサブシェーダがある場合、
Unityの標準シェーダには必ず"RenderType"タグが付いているため、置き換えるシェーダの適切なサブシェーダに置き換えられる、ということを言っています。