Unityシェーダ入門 #002 サーフェイスシェーダ

Unityシェーダ入門2回目。
前回は大まかなグラフィックス処理の流れを説明しましたので、今回からソースコードを出して、具体的な内容に入っていきます。

プログラマブルシェーダ

 前回紹介した頂点シェーダ、ラスタライズ、ピクセルシェーダですが、この中でプログラム可能な処理は、頂点シェーダとピクセルシェーダの二つです。プログラミング出来るシェーダ、という意味で「プログラマブルシェーダ」と呼ばれています。
 プログラマブルシェーダはDirectX8から導入されたもので、それ以前はハードウェアの固定機能として用意されたものしか利用出来ませんでした。コンシューマ向けゲーム機ではXboxで初めて搭載されたようです。(最近のハードウェアでも、Wiiや3DSはシェーダが搭載されていないようです。Xbox360、PS3は当然搭載しています。)
 DirectX9では固定機能のシェーダも利用可能ですが、Unityでは全てのマテリアルがプログラマブルシェーダを用いて実現されています。そのため標準で用意されていないマテリアルを作成したい場合、必ずシェーダを書かなければいけません。

サーフェイスシェーダ

 それでは早速Unityでシェーダを作成してみます。
 Projectウィンドウを開き、Assetディレクトリ以下フォルダ内の適当な所を右クリック、Create->Shaderで新しいシェーダを作成します。適当に名前を付けておきます。(ここではFirstShaderとつけました。)
 作成したシェーダをダブルクリックして開きます。

Shader "Custom/FirstShader" {
// シェーダの名前を表記しています。
// この名前と階層構造はUnity上でマテリアルの
// シェーダを選択するとき表示されるものです。
// 標準ではCustom/さっき付けた名前 になりますが、
// ずっとCustomで作っているとCustomの中が
// ごちゃごちゃになって大変なので、
// Custom/Transparent/○○○のように階層を
// 増やしてまとめると分かりやすいです。
// 当然Customでなくてもいいです。

	Properties {
		_MainTex ("Base (RGB)", 2D) = "white" {}
	}
	// プロパティのブロックです。
	// Unity上でマテリアルを編集する際に、
	// このブロックにある値を編集してマテリアルの
	// 見た目を調整していきます。
	// マテリアルの設定項目を定義する、ということになります。
        // 2Dテクスチャ、キューブマップ、数値(float)、ベクトルなどがあります。
        // ここに全てのプロパティが載っています。
	// docs.unity3d.com/Documentation/Components/SL-Properties.html

	SubShader {
	// SubShaderブロックです。
	// このブロックは1つのシェーダに複数持たせる事ができます。
        // 上から順に搭載しているGPUで実行可能なシェーダを探して、
	// 最初に見つけたものを実行します。

		Tags { "RenderType"="Opaque" }
		// タグ付けです。
		// シェーダそのものの動作に影響するものではありませんが、
                // このタグに付いている値に従って描画順序が決まったり、
		// 特殊な状況で利用されるシェーダを特定したりします。
                // (単なるジャンル分けに過ぎないのですが、
		// 様々な効果に利用されるため、ここの値が間違っていてハマる事がよくあります)
                // ここでは"RenderType"は"Opaque"(不透明)ですよ、
		// と教えているわけです。

		LOD 200
                // オブジェクトとの距離に応じてシェーダを
		// 切り替えるための基準値です
		//(きっと。そうらしい。使ったことない。)

		CGPROGRAM
		// このキーワードから実際のシェーダプログラムが始まります。

		#pragma surface surf Lambert
		 // #pragma はコンパイラに対して命令(やヒント)を与えるキーワードです。
		 //surface surf
		 // surfという名前の関数をsurfaceシェーダとして使用します。
		 // (とコンパイラに教えます)
                 // 自分でsurfAとかsurfBとか関数を作った場合はsurface surfA、
		 // surface surfBとかになります。
                 // Lambert
		 // ライティング計算にLambert(ランバート拡散反射)を用います。
		 // ちなみにBlinnPhong(スペキュラ反射)もあります。

		sampler2D _MainTex;
		// 2Dテクスチャの定義です。
                // プロパティにも似たようなモノが出てきましたが、
		// アレはUnityのマテリアル上で設定する場合に必要なだけで、
                // ここに_MainTexがあるからといって、
		// プロパティにも必ず必要、というわけではありません。
                // プロパティに書かなかった場合は、
		// Unityのスクリプトからテクスチャを設定することになります。

		struct Input {
			float2 uv_MainTex;
		};
		// surfaceシェーダの入力として使われる構造体です。
                // http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaders.html
                // ここのSurface Shader input structure の項に、
		// 書くことの出来る(入力できる)項目の一覧が載っています。
        
		// ちなみにfloat2はfloatを2要素持ったベクトルという意味で、
		// _MainTexの手前にuvをつけると、
                // 自動的にマテリアルに設定した_MainTexの
		// テクスチャ座標設定(TilingとOffset)が適用された
		// UV座標が入力として入ってきます。
		// (uv_○○を増やしたからといって、モデルのUV座標が増えるわけではないです)

		void surf (Input IN, inout SurfaceOutput o) {
                // surfaceシェーダに使用される関数です。
		// 入力に先ほど定義したInputのIN
                // 入出力には、SurfaceOutputのoとなっています。
		// SurfaceOutputは
		// http://docs.unity3d.com/Documentation/Components/SL-SurfaceShaders.html
		// に全ての項目が載っています。

			half4 c = tex2D (_MainTex, IN.uv_MainTex);
			// half4はhalfを4要素含んだベクトルです。
			// 実数(小数点)を表現するにはfloat,half,fixedなんかがあるみたいです。

                        // tex2Dはテクスチャから色をフェッチ(取得)する関数です。
                        // 最初の_MainTexは先ほど定義したテクスチャです。
			// プロパティに設定してあるので、Unityのマテリアル上から
			// テクスチャがバインド(紐付け)されます。
			// (sampler2Dって名前だけど、今は気にしない)

                        // IN.uv_MainTexは先ほど定義したInput構造体の中にいるヤツです。
			// 名前の通りこいつはUV座標です。
                        // UV座標を使ってテクスチャのどの位置から色を取得するかを決めます。
			// この座標は縦、横の位置をそれぞれ(0.0~1.0)の値で特定します。
                        // テクスチャは解像度を持っているため、(100,150)のようにピクセルで
			// 位置を指定してもいい気がしますが、それだと
                        // テクスチャの解像度が変わるたびに座標を直さなくてはいけなくなってしまいます。
                        // UV座標なら128^2のテクスチャを256^2に変更した時にそれぞれ座標を2倍にする、
			// とかやらなくていいわけです。

			o.Albedo = c.rgb;
                        // アルベド http://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%AB%E3%83%99%E3%83%89
                        // ・・・大雑把に言えば"色"です。
			// surfが出力するSurfaceOutput oの中の色に、
			// 先ほど取得したテクスチャの色を入れています。
			// cはRGBAの4要素を持っており、AlbedoはRGBの3要素なので
			// cにrgbを指定して3要素を代入しています。
			// 注意したいのは、ここで指定するのはオリジナルの物体の色であって、
			// ライトが当たった後や、周りの環境によって変化した後の色ではありません。
			// りんごは赤です、と言っているだけです。
			// お絵描きならベタ塗りの色です。

			o.Alpha = c.a;
			// アルファ値にテクスチャのアルファ値を入力しています。
			// 不透明なオブジェクトなのにアルファ値は変だと思うのですが、
			// どうやらこの値は透明度としてのみ使われるわけでは無いようです。
			// 後で色々なエフェクトに使われたりするかもしれません。

		} 
		ENDCG // プログラムの終わりを示します
	} // SubShaderブロックの終了です。

	FallBack "Diffuse"
	// ここまで書いてきたSubShaderのうち、
	// どれもが搭載しているGPUで動作しなかった場合、
	// 色んな環境でも動作するであろう、負荷の軽い一般的なシェーダを、
	// 予備とか補欠、として設定したものがこれです。
	// 酷い言われようですが、実は補欠として以外にも役割があります。
	//(補欠というより縁の下の力持ち的かな?)
}

 全ての行に説明を入れてみました。
結構長いのですが、実際やってることは、テクスチャから色を拾ってきて「このモデルはこういう色してるから後のことは頼んだ!」ってだけです。座標変換、ライティングや影の計算など、面倒なことは全て裏で行われますし、逆に裏で行われるので、当然これらのことはまだカスタマイズ出来ない、ということです。

さて、前回紹介した頂点シェーダとピクセルシェーダですが、どこにも出てきていません。
出てきたのは何だかよくわからない「サーフェイスシェーダ」だけです。
一体どこへ行ってしまったのか。次回説明します。