土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

ユニティちゃんシェーダーを読む(輪郭線1)

 ようやく精神的に余裕が生まれはじめまして、シェーダー本2の執筆を始めようと思ったものの、今から初めても技術書典4への新刊はどうにも間に合いそうにありません。

 そこで、前からやろうと思っていた「ユニティちゃんシェーダーのコードを全文読む」を不定期にブログにアップし、技術書典4前にまとまった分量になったら、これをコピー本にしようかと思っています。

 ちなみに、シェーダー本1は同人での増刷予定はなかったのですが、自分のポートフォリオとして最適なのに手元に在庫がなくなってしまったので、技術書典4合わせで再販するつもりです。紙版を入手するのは最後の機会かもしれませんので、もし欲しい方がいましたら是非どうぞ。

 さてまずはユニティちゃんシェーダーの中でも比較的短いUCTS_Outline.cgincを読んでいきたいと思います。UCTS_Outline.cgincは、文字通り輪郭線を描画する処理で、Pass(Name "Outline")内で#includeされています。

 UCTS_Outline.cgincはシンプルなファイルで、変数宣言の他は頂点シェーダーメソッドvertと、フラグメントシェーダーflagがあるだけです。まずはvertを見て行きます。

 なお、ユニティちゃんシェーダーはShaderForgeで書かれている部分があり、そのままでは読みづらいので、本連載中は、土屋が独自にリライトしたコードを元に解説しています。ユニティちゃんシェーダーのバージョンは2.0.3のつもりですが、もしかしたら古いコードをリライトしている可能性もあります。ご了承下さい。

            VertexOutput vert (VertexInput v) {
                VertexOutput o = (VertexOutput)0;

 返し値をゼロ初期化してます、これ、コンパイル環境によって必要になる処理とどこかで読みましたけど、Unityではなくても大丈夫なんじゃないかな(GLSL系だけの話とかかな? 良く知らない)。

                o.uv0 = v.texcoord0;

 UV座標を取得。

                float4 objPos = mul(unity_ObjectToWorld, float4(0, 0, 0, 1));

 キャラの原点(float4(0, 0, 0, 1))をワールド座標に変換した値を取得。

                float4 _Outline_Sampler_var = tex2Dlod(
                    _Outline_Sampler,
                    float4(TRANSFORM_TEX(o.uv0, _Outline_Sampler), 0.0, 0)
                );

 Outline_Samplerテクスチャの対応するuv座標を取得します。Outline_Samplerテクスチャは、ドキュメントによると「入り抜きを入れたい」「特定のパーツにアウトラインを乗せたくない」時にそれを指定するテクスチャとのこと。
 TRANSFORM_TEXは、テクスチャスケールのオフセット値を正しく適用させるためのマクロで、Unityで推奨される書き方です。第4要素い0が設定されているので、tex2Dlodである意味はないですが(tex2Dで良い筈)、現在は基本tex2Dlodを使うようです(速度に差がないのだと思われる)。

                float Set_Outline_Width = (
                    _Outline_Width * 0.001 * smoothstep( 
                        _Farthest_Distance, 
                        _Nearest_Distance, 
                        distance(
                            objPos.rgb, 
                            _WorldSpaceCameraPos
                        )
                    ) * _Outline_Sampler_var.rgb
                ).r;

 InspectorのOutline_widthを基準値とし、それに係数(0.001)、smoothstep(後述)、_Outline_Sampler_varの値を掛け、この頂点における輪郭線の幅(Set_Outline_Width)を算出します。
 smoothstepはHLSL組み込み関数で、以下のように記述します。

smoothstep(min, max, x)
//x<minの時、0を返す
//max>xの時、1を返す
//min<=x<maxの時、0から1の間の滑らかな値を返す

 わかりにくいのですが、詰まるところこんな感じの値を返します(エルミート補間と言うようです)。
(省略)
(画像は http://www.fundza.com/rman_shaders/smoothstep/ から流用しています)。
 Farthest_Distance/Nearest_Distanceはそれぞれカメラからオブジェクトまでの距離で、アウトラインの幅が変化する最遠/最近距離になります。
 distanceメソッドでは、原点のワールド座標(obj.Pos.rgb)とカメラのワールド座標(_WorldSpaceCameraPos)からカメラからオブジェクトまでの距離を算出し、0〜1の範囲の値を出しています。
 _Outline_Sampler_var.rgbを掛けてから").r"でr要素だけ取り出している理由はよくわかりません(なにかしらあるのかも)。ここはより簡略化できるかもしれません(いつか試します)。