[Unity] AssetStoreのファーシェーダをupdateしたので分かったことを書いてみる

![big_precise.jpg](https://qiita-image-store.s3.amazonaws.com/0/5616/b17cb3e5-25bc-9a30-b54d-ef1d97451a3e.jpeg "big_precise.jpg")

[以前書いたファーシェーダの記事](http://qiita.com/edo_m18/items/75db04f117355adcadbb)を元に、公開していたファーシェーダをライティング不具合の修正に加え、[プロシージャルテクスチャ生成法](http://qiita.com/edo_m18/items/644d470d4e1fd9c5b4bc)を用いたよりリアルに見えるファーの機能を実装しました。

現在[バージョン2.0がAssetStoreにて公開中](https://www.assetstore.unity3d.com/#!/content/22226)です。

*ちなみに[Maker Faire Tokyo 2014](http://makezine.jp/event/mft2014/)で地鶏で自撮り（ｗ）というコンテンツで実際に使いました。テクスチャをうまく調整すればこんな感じのファーが実現できます*
![cap.jpg](https://qiita-image-store.s3.amazonaws.com/0/5616/27cd3843-2f06-7b4e-5e61-c567ec872d15.jpeg "cap.jpg")


## 影の生成

影の生成にはいくつか手法がありますが、Unityではシャドウマップを用いた影生成を行っています。
影の情報はテクスチャに格納され、それを適宜取り出して計算してやることで影をレンダリングすることが出来ます。


## Shadow Caster, Shadow Collector

影を落とす処理（いわゆる「cast shadow」）はUnity側で自動的に行なってくれます。
詳細については **ShadowCaster** , **ShadowCollector** あたりで検索すると出てくると思います。
（ちなみに[こちらの記事](http://unityshader.hatenablog.com/entry/2013/12/14/015247)が分かりやすかったです）

ざっくり言うと、影を計算するためには何回かのパスに分けてオフスクリーンレンダリングを行う必要があります。
そのためのシェーダはUnityがすでに準備しており、通常はなにもしなくても **Fallbackをしっかりと指定しておけば** 自動的に計算してくれます。
*（逆にFallbackを書いておかないと適切に影が生成されません）*

要は、影をレンダリングするための事前情報をUnityが集めてくれる、ってわけですね。（なのでShadowCollectorって名前なんだと思う）

しかし情報を集めてくれても、影を受ける側（receive shadow）については、カスタムシェーダを記述する側が計算を行わないとなりません。
（例えば影を受けないオブジェクトを作ろうと思っているのに勝手に影が反映されては困りますね）

当然、今回のファーシェーダはカスタムシェーダなので影の部分は自分で書かないとなりません。
が、シャドウを正しく実装するにはそれなりの知識が必要になります。
（かくいう自分もまだしっかりと理解できていませんorz）
なので、 **ある程度の範囲と不確かな情報になるので、参考にする場合はご注意ください。**


## Unityで準備されている機能でシャドウを実装する

Unityの場合、基本的なシャドウについての便利なマクロや関数、変数が既存のインクルードファイルに定義されているので、それを組み合わせることである程度簡単に適用することができます。

ざっくりと手順を書くと以下のようになります。

1. Tagsの中に`"LightMode"="ForwardBase"`(※1)を入れる
2. CGPROGRAMの下に`#pragma`を追加。（`#pragma multi_compile_fwdbase`）
3. 	CGPROGRAM内でファイルをincludeする。（`#include "UnityCG.cginc"`、`#include "AutoLight.cginc"`）
4. vertex shaderからfragment shaderに渡す構造体の定義の中に`LIGHTING_COORDS(idx1, idx2)`を入れる(※2)
	
5. vertex shader内で`TRANSFER_VERTEX_TO_FRAGMENT(o);`（マクロ）を記述する。
	*(o)は出力される構造体名*
6. fragment shader内で`LIGHT_ATTENUATION(i)`マクロを使って影の度合いを得る
	*(i)は入力される構造体名*

※1...`ForwardBase`では平行光源によるライティングしか考慮されないようです。
※2...`idx1, idx2`は、未使用の`TEXCOORD#idx`を指定します。 **（文末にセミコロンは不要なので注意）**
例えば構造体内で`TEXCOORD3`まで使われていたら`LIGHTING_COORDS(4, 5)`と指定する。引数の数値を利用して自動的に`TEXCOORD`を割り当ててくれるマクロになっています。

※ ちなみに、冒頭で書いた通り、シェーダーコードの最後に`FallBack`を指定しないとシャドウが出ないので注意です。

[UnityのForum](http://forum.unity3d.com/threads/adding-shadows-to-custom-shader-vert-frag.108612/)で詳細が書かれていました。

[実際に動くレベルのコードはこちらを参考](http://qiita.com/edo_m18/items/ee4b9018e860c11199b0)に。


### マクロを紐解いてみる

いくつかのマクロを使っていますが、それぞれどんなことをやっているのでしょうか。
正直な所、すべてを書くのは非常にむずかしいのでポイントだけ書こうと思います。

#### LIGHTING_COORDS

`LIGHTING_COORDS`マクロは構造体内に適切にメンバを追加してくれます。
定義は「AutoLight.cginc」内でされており、いくつかのライトごとに内容を出し分けています。
（例えば平行光源の場合（`#ifdef DIRECTIONAL`）、上記マクロはたんに無視されます）

追加されるメンバは`_LightCoord`と`_ShadowCoord`のふたつです。（セマンティクスは`TEXCOORD#n`）
このふたつのメンバはフラグメントシェーダ内でライティングとシャドウの計算に利用されます。


#### TRANSFER_VERTEX_TO_FRAGMENT

`TRANSFER_VERTEX_TO_FRAGMENT`の定義はライトの種類に応じていくつかのパターンがあります。
が、どのパターンでもやっていることは「ライトの情報を適切にフラグメントシェーダに渡す」という処理です。マクロ名がまさにそうなっていますね。

以下はスポットライトの場合に計算される式を抜粋したものです。

```
#define TRANSFER_VERTEX_TO_FRAGMENT(a) a._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)); TRANSFER_SHADOW(a)
```

`LIGHTING_COORDS`で定義した`_LightCoord`に何かしらの値を入れています。
`_LightCoord`ではライトの位置や強さなどを、ライトテクスチャからlookupするための情報を格納します。
そのための変換行列が`_LightMatrix0`というわけですね。

ちなみに最後の`TRANSFER_SHADOW`がさらにマクロを呼び出していて、これまたいくつかのパターンが存在します。
その中の定義を抜粋したものが以下。

```
// パターン1
#define TRANSFER_SHADOW(a) a._ShadowCoord = mul( unity_World2Shadow[0], mul( _Object2World, v.vertex ) );

// パターン2
#define TRANSFER_SHADOW(a) a._ShadowCoord = ComputeScreenPos(a.pos);
```

パターンは上記ふたつ以外にもありますが、どれも`_ShadowCoord`に値を保持します。
`_shadowCoord`のレンダリングをするのに利用します。


#### LIGHT_ATTENUATION

ライトの減衰率を計算するマクロ。
こちらもライトによってマクロの内容が若干異なります。

以下はポイントライトの場合。

```cg
#define LIGHT_ATTENUATION(a) (tex2D(_LightTexture0, dot(a._LightCoord,a._LightCoord).rr).UNITY_ATTEN_CHANNEL * SHADOW_ATTENUATION(a))
```

前述の`_LightCoord`へ書き込まれた情報を元に、フェッチするテクセルの場所を計算します。
（ちなみに`rr`は、`float`型のスカラー値から`float2`型のベクトルを作り出す方法です）

そして`_LightTexture0`（ライトの諸々の情報を格納しているテクスチャ）から適切に値を取り出します。

`UNITY_ATTEN_CHANNEL`は、ドキュメントを引用すると以下のように説明されています。

>
UNITY_ATTEN_CHANNEL - which channel of light attenuation texture contains the data; used in per-pixel lighting code. Defined to either ‘r’ or ‘a’.

つまり、`r`か`a`どちらに目的の情報があるかが変わるため、それを適切に切り替えてくれているわけですね。


#### SHADOW_ATTENUATION

こちらは影の減衰率。
定義を見ると以下のように関数を呼び出しています。

```
#define SHADOW_ATTENUATION(a) unitySampleShadow(a._ShadowCoord)
```

関数では以下のように計算を行っています。

```
inline fixed unitySampleShadow (float4 shadowCoord)
{
	fixed shadow = tex2Dproj( _ShadowMapTexture, UNITY_PROJ_COORD(shadowCoord) ).r;
	return shadow;
}
```

ポイントは`tex2Dproj`関数を使ってテクセルを参照している点。
これはデプスバッファシャドウのためでしょう。
（[デプスバッファシャドウについては以前記事を書いた](http://qiita.com/edo_m18/items/4658357683ec253f38ff)のでそちらを参照）

**UNITY_PROJ_COORD**

`UNITY_PROJ_COORD`の定義は以下のようになっています。

```
#define UNITY_PROJ_COORD(a) (a).xyw

// or

#define UNITY_PROJ_COORD(a) a
```

環境によって値を出し分けしているようです。
基本的には受け取った`shadowCoord`を利用して影の減衰率をテクスチャからlookupしています。








## Forward Renderingとシャドウ

[こちらの記事](http://unityshader.hatenablog.com/)に、FowardRenderingでの影について書かれています。

上記記事から引用させていただくと影の処理の流れは以下になるようです。

>
1. シャドウマップを生成します。
QualitySettingのShadowCascadesによって、1,2,4枚のカスケードシャドウマップが生成されます。
(2,4枚の場合でも、1枚のテクスチャにまとめられる[=テクスチャアトラス]ようです)
ここではシェーダのShadowCasterパスが用いられます。

>
2. 深度を比較して、画面空間上での影を求めます。
ここではシェーダのShadowCollectorパスが用いられます。

>
3. 2で求めた影の画像に対してブラー処理が行われます。

>
4. FowardRenderingのbaseパスで、3で生成した影テクスチャから影の量を取得し、ライトをその分だけ減衰させます。

*※ ちなみにテクスチャやシャドウマップなど、影自体の仕組みは少し前にWebGLの記事（[[WebGL] デプスバッファシャドウ](http://qiita.com/edo_m18/items/4658357683ec253f38ff)）として書いたのでそちらが参考になるかもしれません。*


## LightModeタグ

`LightMode`タグは、「[ライティングパイプライン](http://docs-jp.unity3d.com/Documentation/Components/SL-RenderPipeline.html)の中での、 **パスの役割** を定義」します。

指定できるパラメータは以下のとおり。（Unityのドキュメントから抜粋）

| パラメータ | 意味 |
|:----------|:-------------------|
| Always |  常にレンダリングされ、ライティングは適用されません。 |
| ForwardBase |  Forward rendering で使用され、環境光、メイン指向性ライト、頂点／SHライトが適用されます。 |
| ForwardAdd |  Forward rendering で使用され、ライトごとに一つのパスで、Additive ピクセルライティングが適用されます。 |
| PrepassBase |  Deferred Lighting で使用され、法線や鏡面指数をレンダリングします。 |
| PrepassFinal |  Deferred Lighting で使用され、最終的なカラーについて、テクスチャ、ライト、および自己発光、を合成することでレンダリングします。 |
| Vertex |  Vertex Lit rendering でオブジェクトがライトマップされてない場合に使用され、全ての頂点ライトが適用されます。 |
| VertexLMRGBM |  Vertex Lit rendering でオブジェクトがライトマップされてる場合、ライトマップがRGBMエンコードされるプラットフォームにおいて使用されます。 |
| VertexLM |  Vertex Lit rendering でオブジェクトがライトマップされてる場合に、ライトマップがdoub-LDRエンコードされるプラットフォームにおいて(一般にモバイル向けプラットフォームや古いデスクトップ向けGPUにて)使用されます。 |
| ShadowCaster |  オブジェクトを、Shadow Casterとしてレンダリングします。 |
| ShadowCollector |  Forward Renderingのために、オブジェクトの影をScreen-Spaceバッファに集めます。 |


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


## ライティングに関するメモ

ライティングは色々理解がむずかしく、Unity側でよしなにやってくれる部分もあるのでだいぶ把握が大変です。
いまだにしっかりと把握できているわけではありませんが、ハマった点、注意する点などをメモしておきます。

なお、ライティングについては[以前の記事（[Unity] デフォルトのDiffuseライティングをカスタムシェーダで実装する）](http://qiita.com/edo_m18/items/1b90932a284fb8e89156)で解説しているのでそちらをご覧ください。


### Forward AddingにはBlendを指定する

ライティングを行う上で、複数ライトによるライティングは`Tags { "LightMode"="ForwardAdd" }`を指定して複数パスでレンダリングする必要があります。

複数パスが意味する通り、「ForwardBase」でレンダリングされたものに加算する形でライティングが施されます。
ただ、「ForwardAdd」は複数パスでのレンダリングを実行しているに過ぎないので、`Blend`をしっかり指定しないと、最後に計算されたライトの色だけが反映されてしまいます。

```cg:blend
Pass {
	Tags { "LightMode"="ForwardAdd" }
	Blend One One // Blendをしっかり指定する

	// 省略
}
```

上記のように`Blend`をしっかり指定する必要があることに注意です。
（最初、これをやらずに色々書いていてハマりました）


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


## CGIncludesに含まれるファイルのメモ

UnityのShader向けに、Unityが最初から準備してくれているファイルがあります。

* AutoLight.cginc
* HLSLSupport.cginc
* Lighting.cginc
* TerrainEngine.cginc
* Tessellation.cginc
* UnityCG.cginc
* UnityCG.glslinc
* UnityShaderVariables.cginc

HLSLSupport.cgincは自動的に読み込まれるようです。
それ以外のものについては任意で読み込みます。
*（AutoLight.cgincは、surface shaderの場合は自動的に利用されるようです）*

ライティングやシャドウを実装しようとする場合、これらのサポートファイルを使うと便利です。
が、中で定義されているものや、（おそらく）Unityが内部的に宣言しているマクロなど分からない部分もあるので、それらをメモしていきます。


### シャドウ関連

サポートファイルを見ているといくつかの定義が見つかります。

* SHADOWS_SCREEN
* SHADOWS_NATIVE

などですね。
ただ、これがどこで定義され、どう使われるのかがイマイチ分かりません。
色々探してみたところ、[こちらの記事](http://forum.unity3d.com/threads/acces-to-unity-final-pass-shader-deferred.221254/)のコメントが参考になるかもしれません。

それを引用させていただくと、

>
Just want to share what I found. After looking at the compiled code, I found that
>
SHADOWS_NATIVE seems to use shadow2DEXT to sample the shadow map, in which the calculation whether the current z is in front of the shadow map is done in the hardware directly.
>
Meanwhile, SHADOWS_SCREEN seems to be the usual shadow map technique, using texture2DProj to sample the shadow map, and then compare the current z to the result in the code.

コンパイル後のコードを覗いて確認されたようです。
`SHADOWS_SCREEN`は、いわゆる[デプスバッファシャドウ](http://qiita.com/edo_m18/items/4658357683ec253f38ff)によるシャドウイング？　をしているようです。

`SHADOWS_NATIVE`は、ハードウェアで直接計算を行っている？　具体的には分かりませんが、確かに「ネイティブ」と書かれていることからすると納得です。

ただどちらもシャドウの演算方法を決めているにすぎないようです。

#### 雑記

以下は、自分がこうかなーと考えたメモなので、参考にする場合は自己責任でお願いします。

**UNITY_DECLARE_SHADOWMAP**

`SHADOWS_NATIVE`が有効な場合に、適切にハードウェアごとにシャドウマップテクスチャを宣言するマクロ。

**ComputeScreenPos**

スクリーン座標を計算するインライン関数。いくつか分岐があるものの、最終的には引数の`pos`がスクリーン上のどこか、を計算してくれる関数ぽい。
コメントを引用すると

>Projected screen position helpers

ということのよう。

### 関連リンク

* [ShaderLab文法：Passタグ / ShaderLab syntax: Pass Tags](http://docs-jp.unity3d.com/Documentation/Components/SL-PassTags.html)
* [Unity のレンダリング パイプライン / Unity’s Rendering Pipeline](http://docs-jp.unity3d.com/Documentation/Components/SL-RenderPipeline.html)
* [Forward Renderingパスの詳細 / Forward Rendering Path Details](http://docs-jp.unity3d.com/Documentation/Components/RenderTech-ForwardRendering.html)
* [Deferred Lightingレンダリングパス / Deferred Lighting Rendering Path](http://docs-jp.unity3d.com/Documentation/Components/RenderTech-DeferredLighting.html)