ShaderTips

シェーダーTips

主にUnityシェーダーについての記事を書いています。

【Unity】【シェーダー】ランバート反射モデル (グローシェーディング)

ランバート反射モデルとは

[1]サーフェスの法線ベクトルと、光源方向のベクトルがなす角度から 0.0 ~ 1.0 の値を算出し、それをサーフェスの「明るさ」とします。

 

原理

光とは粒子の集合(フォトン)でフォトンが物体に当たることにより、光ります。

とある光源からX個の粒子が放出した場合に光源方向ベクトルとサーフェイスの法線の角度によって当たるであろう面積(=S)が変化します。

懐柔電灯の光を紙の上に当てたことを想像してください。

垂直に当てた時より斜めに当てた時の方が光っている紙の面積が大きくなることが分かると思います。

仮に垂直に当てた時の面積をS=1、斜めに当てた時の面積をS=2とし、

S=1あたりに当たるフォトンの数を比較するとX/1>X/2になり、垂直に当てた方が一部に多くのフォトンが集まっていることになります。

つまり、光が垂直に当たるほど明るくなる!

 

[1]サーフェス:プリミティブによって表されるモデルの表面。フラグメントシェーダー内では、特に現在 処理の対象としている1点(あるいは微少面)のことを指している事が多い。法線ベクトルなど各種の情報を持っている。

 

ソース
ShaderLab
Shader "Hidden/Lambart"
{
	Properties
	{
		_MainTex ("Texture", 2D) = "white" {}
	}
	SubShader
	{
		// No culling or depth
		Cull Back  ZTest Always

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag
			
			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
			};

			struct v2f
			{
				float2 uv : TEXCOORD0;
				float4 vertex : SV_POSITION;
				//拡散反射
				float diffuse : COLOR0;
			};

			v2f vert (appdata v)
			{
				v2f o;
				//頂点をクリップ空間座標に変換
				o.vertex = UnityObjectToClipPos(v.vertex);
				//uv座標
				o.uv = v.uv;

				float3 normal = v.normal;
				//ライト方向ベクトル
				float3 lightDir = normalize(ObjSpaceLightDir(v.vertex));
				//法線-ライトのcosθ
				float NdotL = dot(normal, lightDir);
				//拡散反射量の決定
				o.diffuse = max(0, NdotL);
				return o;
			}
			
			sampler2D _MainTex;

			fixed4 frag (v2f i) : SV_Target
			{
				fixed4 texCol = tex2D(_MainTex, i.uv);
				// just invert the colors
				//サンプリングしたカラー値に拡散反射量を乗算する
				return i.diffuse * texCol;
			}
			ENDCG
		}
	}
}

 

解説

//法線-ライトのcosθ float NdotL = dot(normal, lightDir);

//拡散反射量の決定 o.diffuse = max(0, NdotL);

 

拡散反射量 =  (N ・ L)

N 法線

L 光源方向ベクトル

 

内積の公式は

a・b = || a || * || b || * cosθ

法線と光源方向ベクトルは単位ベクトルを使用します。

= 1 * 1 * cosθ

= cosθ

cosθが残り、計算結果の値が-1〜1を取ります。

θ(二つのベクトルのす角度)が0のき = 1

θが90のとき =  0

θが180のとき = -1

(ただし、90度を超えた場合は、サーフェスが光源の方を向いておらず、光が届いていないので、結果が0未満になる場合はすべて0に丸めます)

法線と光源方向ベクトルが平行の時は先程の例でいうと懐柔電灯の光を垂直に当てた状態です。

つまり、値が1に近づくほど明るくすれば原理通りになります。

 

//サンプリングしたカラー値に拡散反射量を乗算する return i.diffuse * texCol;

バーテックスシェーダーで計算したディフューズ色をテクスチャーカラーに掛けています。