おもちゃラボ

Unityで遊びを作ってます

【Unityシェーダ入門】ランバート拡散照明モデルを試す

今回はUnityのライティングとシェーディングのお話をしようと思います。シェーダとは名前が示すとおり「影」をつけるための技術です。影の付け方で、様々な質感や見た目を表現できます。

f:id:nn_hokuson:20161031200416p:plain

上の画像は左からランバートシェーダ、フォンシェーダ、トゥーンシェーダになります。同じ球のもでるでも、まったく質感が違いますね。今回は、CGで最もよく使われるランバート拡散照明モデルについて紹介したいと思います。

今回のコンテンツは次のとおりです。

ランバート拡散照明モデルとは

拡散反射光

紙に描かれた物体や、ディスプレイ内のオブジェクトがあたかも立体のように見える理由は影の効果によるものです。形は同じでも、影があることで円に見えたり球に見えたりします。

f:id:nn_hokuson:20161101195320j:plain

では、どのように影をつけていくと立体っぽく見えるのでしょうか?答えは「光の進む方向と面とが垂直に近ければ近いほど明るくなる」です。下の図をみるとイメージしやすいと思います。

f:id:nn_hokuson:20161101195523j:plain

これをもう少し、固い言葉で説明すると「反射光の強さは、光の入射ベクトルとオブジェクトの法線ベクトルがなす角度のcosに比例する。」となります。入射ベクトルと面の法線が並行のときはcos0なので反射光の強さは1になります。逆に入射ベクトルと面法線が垂直のときはcos90で反射光の強さは0になります。

環境光

ランバート拡散照明モデルは光源とオブジェクトの面の法線だけでシェーディングできる手軽な方法ですが、法線の向きがライトと逆向きになると真っ黒に潰れてしまう欠点があります。この欠点をカバーするのが環境光です。環境光は明度のオフセットのようなもので、オブジェクト全体をほんのり明るくします。

f:id:nn_hokuson:20161101195830j:plain

環境光を使うことで、拡散照明モデルでは真っ黒に塗りつぶされていた部分が浮き出ているのがわかると思います。上で紹介した拡散反射光と環境光によるライティングをあわせてランバート照明モデルと呼びます。数式にすると次のとおりです。

I = LightColor * (N * L) + Env

ここで、LightColorは光源の色、Nは面の法線、Lはライトの方向、Envは環境光を表しています。

オブジェクトを配置する

ヒエラルキービューから「Create」→「3D Object」→「Sphere」を選択します。インスペクタから、作成した球のScaleを(5, 5, 5)にします。

f:id:nn_hokuson:20161101201049p:plain

世界を真っ暗にする

まずは、プロジェクトを作成して、ヒエラルキービューから「Main Camera」を選択し、インスペクタから「Clear Flags」 を「Solid Color」にし、「Background」を黒色にします。

f:id:nn_hokuson:20161101202057p:plain

まだ、球に空の色が反映されているので、それも切りましょう。メニューバーから「Window」 →「Lighting」を選択し、「Skybox」 を「None」に変更、「Ambient Source」を「Color」、「Ambient Color」を黒色、「Reflection Intensity」も「0」に設定します。

f:id:nn_hokuson:20161101203227p:plain

これで、純粋にライトからだけの影響を受けるステージができました。

シェーダを作るための下準備をする

続いてランバート拡散照明用のシェーダを作ります。プロジェクトビューで「右クリック」→「Create」→「Shader」→「Standard Surface Shader」を選択し、作成したファイル名をSimpleLambertに変更します。続いてこのシェーダファイルをアタッチするマテリアルも作成しましょう。SimpleLambertを選択した状態で「右クリック」→「Create」→「Material」を選択すると、SimpleLambertというマテリアルが作成されます。

f:id:nn_hokuson:20161101202504p:plain

このマテリアルを先に球にアタッチしておきます。プロジェクトビューからSimpleLambertのマテリアルを選択し、シーンビューのShpereにドラッグしてください。見た目は変わりませんが、Sphereのマテリアルが「Custom/SimpleLambert」になっていることを確認して下さい。

f:id:nn_hokuson:20161101202749p:plain

ランバート拡散照明モデルのシェーダを書く

では、SimpleLambertのシェーダファイルを開いて次のプログラムを入力してください。

Shader "Custom/SimpleLambert" {

	SubShader {
		Tags { "RenderType"="Opaque" }
		LOD 200
		
		CGPROGRAM
		#pragma surface surf SimpleLambert
		#pragma target 3.0

 
		struct Input {
			float2 uv_MainTex;
		};
 

		void surf (Input IN, inout SurfaceOutput  o) {
			o.Albedo = fixed4(1,1,1,1);
		}

		half4 LightingSimpleLambert(SurfaceOutput s, half3 lightDir, half atten)
		{
			 half NdotL = max(0, dot (s.Normal, lightDir));
			 half4 c;
			 c.rgb = s.Albedo * _LightColor0.rgb * NdotL + fixed4(0.2f, 0.2f, 0.2f, 1);
			 c.a = s.Alpha;
			 return c;
		}
		ENDCG
	}
	FallBack "Diffuse"
}

#pragmaの3つ目(SimpleLambert)の単語の先頭に”Lighting”をつけたものがライティング工程のメソッド名になります。(2つ目がサーフェスシェーダのメソッド名になります)

LightingSimpleLambertメソッドの中で、ライティングの処理を書いています。引数にサーフェスシェーダからの出力、ライトの方向ベクトル、減衰値を受け取ります。視線ベクトルなどの引数の受け取り方は下記のページを参照してください。

docs.unity3d.com

LightingSimpleLambertメソッドでは、まずオブジェクトの法線とライトベクトルの内積をとっています。次式のように、2つの内積をとることで法線とライトのベクトルがなす角度をcosの値として取得できます。(NとLの長さはどちらも1に正規化されています)

N*L=|N||L|cosθ=cosθ

法線が光のベクトルと逆に向いているときは出力に-1になりますが、マイナスの場合にはmaxメソッドを使って0にします。maxメソッドは2つの引数のうち、大きい方の値を返します。下の例では、valueが0よりも小さい場合には0、それ以外はvalueの値を出力します。

max(0, value);

最後にライトの色にcosの値をかけることで光の当たる部分は明るく、影になる部分は暗くしています。

また、最後のfixed4(0.2, 0.2, 0.2, 1)で表されているのが環境光になります。このように数式で見ると、環境光がただのオフセットであることがわかりやすいですね。

まとめ

ランバート拡散照明のモデルを使ってシェーディングした例が次のようになります。図のようにランバート拡散照明モデルはザラッとした質感に向いています。ランバートシェーダは非常に軽い処理で、それなりに綺麗に見えるので、CGなどでは未だによく使われています。

f:id:nn_hokuson:20161101203556p:plain

参考文献

シェーダと言ったらこの本!内容はDirectXとHLSLで書かれていますが、理論部分は一般論なのでUnityでも応用できます。

私の書いた「Unity5の教科書」もよろしくお願いします。