第6回.初めてのSurface Shader…を徹底解剖

はじめに

 これからShaderプログラムについて解説していきます。
 今回作るのはSurface Shaderというもので、難しいライティングの計算やテッセレーションなどをUnity側で全て自動でやってくれるようお膳立てされたShaderになります。NPR(Non Photorealistic Rendering)など特殊なことを行わない限り、Surface Shaderで十分な場合がほとんどかと思います。

まず、Surface Shaderの作り方

  1. Projectウィンドウで右クリック->Create->Shader->Standard Surface Shaderで新規作成します。
  2. 名前をFirstShaderとします。
  3. Materialを作成し、FirstShaderMatと名前をつけます。
  4. MaterialのShaderから、Custom->FirstShaderを選択します。
  5. 新規のSceneを作成します。
  6. Planeを配置し、FirstShaderMatを割り当てます。
  7. 初めてのShaderが完成しました!


プログラム

 FirstShaderをダブルクリックして、ソースコードを見てみましょう。

Shader "Custom/FirstShader" {
	Properties {
		_Color ("Color", Color) = (1,1,1,1)
		_MainTex ("Albedo (RGB)", 2D) = "white" {}
		_Glossiness ("Smoothness", Range(0,1)) = 0.5
		_Metallic ("Metallic", Range(0,1)) = 0.0
	}
	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		// Physically based Standard lighting model, and enable shadows on all light types
		#pragma surface surf Standard fullforwardshadows

		// Use shader model 3.0 target, to get nicer looking lighting
		#pragma target 3.0

		sampler2D _MainTex;

		struct Input {
			float2 uv_MainTex;
		};

		half _Glossiness;
		half _Metallic;
		fixed4 _Color;

		void surf (Input IN, inout SurfaceOutputStandard o) {
			// Albedo comes from a texture tinted by color
			fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
			o.Albedo = c.rgb;
			// Metallic and smoothness come from slider variables
			o.Metallic = _Metallic;
			o.Smoothness = _Glossiness;
			o.Alpha = c.a;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

 結構な分量がありますので、少しずつ解説していきます。

名前と位置

 先頭のこの行は、Shaderの名前が"FirstShader"であるということと、Unity上で表示される位置が"Custom"ディレクトリの下にある、という意味で記述されています。"Custom/FirstShader"なので、Unity上ではCustom->FirstShaderという階層で表示されます。

Shader "Custom/FirstShader"


プロパティ

 ここでは、Unity上でMaterialのパラメータを設定するために必要なプロパティを記述します。
全てのパラメータにプロパティが必要というわけではありません。あくまでUnityのGUI上で設定するために必要なものです。
(ここに記述の無いパラメータは、前回のようにスクリプトから設定します)
 詳細はUnity - マニュアル: ShaderLab :プロパティーを参考にしてください。

Properties {
	_Color ("Color", Color) = (1,1,1,1)
	_MainTex ("Albedo (RGB)", 2D) = "white" {}
	_Glossiness ("Smoothness", Range(0,1)) = 0.5
	_Metallic ("Metallic", Range(0,1)) = 0.0
}


サブシェーダブロック

 このSubShaderに囲まれた領域が、実際に実行されるShaderになります。もしGPUが古いなどの理由で、このSubShaderを実行できなかった場合は、次に記述されたSubShaderを探し実行しようとします。すべてのSubShaderが実行不可能だった場合、FallBackに指定してあるShaderを探しに行きます。
(この連載ではShaderのことをイラストレーターだと例えていますが、この場合複数のイラストレーターがリストにあって、そのうち今描ける人を上から探していって、最終的に順列は一番低いけども絶対描いてくれる人に頼む、みたいな感じですね…)

SubShader
{
    ~~
}
FallBack "Diffuse"


タグ

 絵(Pixiv)でも動画(ニコニコ,YouTube)でも、分類のためにタグが用いられます。Shaderでも同じことで、Shaderを分類するためにタグ付けをします。
 今回は"RenderType"というタグに、"Opaque"という値が割り当てられています。シャドウマップやG-Buffer生成のために描画タイプを明確に区別する必要があり、そのために"RenderType"タグは使われています。
 またここには記述されていませんが、描画順を決定するための"Queue"タグも重要です。
 詳細はUnity - マニュアル: ShaderLab : SubShader 内の Tagsを参照してください。

Tags { "RenderType"="Opaque" }


LOD

 LODというのはLevel Of Detailのことで、これは状況に応じて描画クオリティを変更する仕組みのことです。
現在設定されたLOD値が200以上なら、このShaderは描画してもいいですよーという意味のようで、負荷調整のために用いられるようです(使ったことは無いです)
 詳細はUnity - マニュアル: シェーダー LODを参照してください。

LOD 200


CGPROGRAM

 タグやLODなどのメタ情報ではなく、プログラムの中身がここから始まりますよ~という合図です。

CGPROGRAM
~
ENDCG


#pragma

 プログラムの中身は始まりましたが、ここにも設定が沢山出てきます。

// Physically based Standard lighting model, and enable shadows on all light types
#pragma surface surf Standard fullforwardshadows

// Use shader model 3.0 target, to get nicer looking lighting
#pragma target 3.0
surface surf Surface Shaderの関数として、surfを指定しています
Standard ライティング計算を行う関数に物理ベースレンダリングである、Standardを指定しています。この場合surfの引数がSurfaceOutputStandardになります。
fullforwardshadows Forwardレンダリングのときに(DirectionalLightだけでなく)全てのタイプのシャドウをサポートする、という指定のようです。
target 3.0 Shaderモデルに3.0を指定しています。テッセレーションなどをしたい場合は5.0にします(PC向けならもう全部5.0でいい気がします)

 他にも指定できる項目が山ほどあるので、暇なときにUnity - マニュアル: サーフェスシェーダーの記述を眺めてみると新しい発見があります。

sampler2D

 サンプラーと書いていますが、実はテクスチャです。
_MainTexっていう名前のテクスチャを使うよ!ということです。

sampler2D _MainTex;



SurfaceShaderのInput

 SurfaceShader関数(surf)に入力される構造体を定義しています。
ここではuv_MainTex(つまり_MainTexのuv座標)だけですが、worldPos(ピクセルのワールド座標値)、screenPos(ピクセルのスクリーン空間座標値)、worldNormal(ピクセルのワールド法線)など色々追加で取り寄せることができます。
詳しくは…さっきと同じページにあります。

struct Input {
    float2 uv_MainTex;
};



その他パラメータたち

 Sampler2Dと一緒でもよかったのですが…追加のパラメータたちです。_Glossinessはプロパティのところを見ると、Smoothnessとして使われているのが分かります。_Metallicはそのままメタリック、_Colorは色ですね。
 今まで見てきてわかるかと思いますが、パラメータの名前には必ず先頭にアンダースコア(_)が入っています。必須ではありませんが、そのような命名規則になっているようですね。

 halfは半精度浮動小数点数、fixedは固定小数点数、fixed4はその4要素ベクトル版。floatはもちろん単精度浮動小数点数になります。色々書きましたが、PCのGPUでは全てfloatとして実行されるようなので、このサイトでは区別しません。

half _Glossiness;
half _Metallic;
fixed4 _Color;


surf関数

 プログラムらしいプログラムは、全てここに記述されています。バラバラにして解説していきます。

void surf (Input IN, inout SurfaceOutputStandard o) {
	// Albedo comes from a texture tinted by color
	fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
	o.Albedo = c.rgb;
	// Metallic and smoothness come from slider variables
	o.Metallic = _Metallic;
	o.Smoothness = _Glossiness;
	o.Alpha = c.a;
}


テクスチャから色(テクセル:Texel)を取り出す(フェッチ:Fetch)

 この行は、tex2D関数を使ってテクスチャからTexelを取得(Fetch)し、_Colorを乗算しています。
 入力のuv座標がここで使われているのが分かります。

fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;



※テクスチャの1ピクセルのことをテクセル(texel)と呼んだりします。
※テクスチャからテクセルを取り出すことを、フェッチ(Fetch)と言います。
※フィルタ系の場合、フェッチする回数について、タップ(tap)と呼ばれるのをよく見ます。3x3tapなど。

パラメータを入れるだけ

 あとはパラメータをSurfaceOutputStandardという構造体に渡しているだけです。このSurfaceOutputStandard構造体はStandard(Lighting)関数に送られ、そこでライティング計算が行われ(後に紆余曲折あって)最終的に1ピクセルの色として画面に出力されます。

o.Albedo = c.rgb;
// Metallic and smoothness come from slider variables
o.Metallic = _Metallic;
o.Smoothness = _Glossiness;
o.Alpha = c.a;

おわりに

 よく言う「おまじない」を使わずに、全てをごまかさず説明しようと試みました。初心者の方にはまだ難しいかと思いますが、全体像を把握するのにUnityの分散したマニュアルを読み漁るよりはマシかな、という感じです。
 基礎的な部分はここまで。あとはサンプルを使って実践的な解説していきたいと思います。