1. 程式人生 > >蘭伯特(Lambert)模型

蘭伯特(Lambert)模型

漫反射,是投射在粗糙表面上的光向各個方向反射的現象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射,所以入射線雖然互相平行,由於各點的法線方向不一致,造成反射光線向不同的方向無規則地反射,這種反射稱之為“漫反射”或“漫射”。這種反射的光稱為漫射光。很多物體,如植物、牆壁、衣服等,其表面粗看起來似乎是平滑,但用放大鏡仔細觀察,就會看到其表面是凹凸不平的,所以本來是平行的太陽光被這些表面反射後,瀰漫地射向不同方向。


(這圖盜的別人的,直觀,非原創)

漫反射光照符合蘭伯特定律:反射光線的強度與表面法線和光源方向之間的夾角的餘弦成正比,因此漫反射的部分計算如下:

                                    Cdiffuse =( C

light *Mdiffuse )max(0,dot(N,L))

N是表面法線,L是光源方向的單位向量,Mdiffuse是材質的漫反射顏色,Clight是光源顏色我們使用取最大值函式來將其擷取到0,還可以防止物體被從後面來的光源照亮。

逐頂點計算著色shader
我們在shader中需要計算輸出的顏色,逐頂點著色也就是說我們的計算主要放在了vertex shader中,根據頂點來計算,每個頂點中計算出了該點的顏色,直接作為vertex shader的輸出,pixel(fragment) shader的輸入,當到達pixel階段時,直接輸出頂點shader的結果。比如一個三角形面片,在vertex階段,分別計算了每個頂點的顏色值,在pixel階段時,這個面片經過投影,最終顯示在螢幕上的畫素,會根據該畫素周圍的頂點來插值計算畫素的最終顏色,這種著色方式也叫做
高洛德著色
Shader "ApcShader/DiffusePerVetex"
{
	//屬性  
	Properties{
		_Diffuse("Diffuse", Color) = (1,1,1,1)
	}

		//子著色器    
		SubShader
	{
		Pass
	{
		//定義Tags  
		Tags{ "RenderType" = "Opaque" }

		CGPROGRAM
		//引入標頭檔案 ,一些內建變數,如_LightColor0,需要使用到
#include "Lighting.cginc"  
		//定義Properties中的變數  
		fixed4 _Diffuse;
	//使用vert函式和frag函式  
#pragma vertex vert  
#pragma fragment frag  
	//定義結構體:應用階段到vertex shader階段的資料,如果定義了 
	struct a2v
	{
		float4 vertex : POSITION;
		float3 normal : NORMAL;
	};
	//定義結構體:vertex shader階段輸出的內容  
	struct v2f
	{
		float4 pos : SV_POSITION;
		fixed4 color : COLOR;
	};

	//定義頂點shader  
	v2f vert(a2v v)
	{
		v2f o;
		//模型頂點轉換
		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
		//環境光
		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz; 
		//把法線轉化到世界空間  
		float3 worldNormal = mul(v.normal, (float3x3)_World2Object);
		//歸一化法線  
		worldNormal = normalize(worldNormal);
		//把光照方向歸一化  
		fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		//根據蘭伯特模型計算頂點的光照資訊,dot可能有負值,小於0的部分可以理解為看不見,直接取0 ,lambert光強*材質diffuse顏色*光顏色 
		fixed3 diffuse = _LightColor0.xyz*_Diffuse.xyz*max(0.0, dot(worldNormal, worldLightDir));		 
		o.color = fixed4(diffuse + ambient, 1.0);
		return o;
	}

	//定義片元shader  
	fixed4 frag(v2f i) : SV_Target
	{
		return i.color;
	}
		ENDCG
	}

	}
		//前面的SubShader失效的話,使用預設的Diffuse  
		FallBack "Diffuse"
}

逐畫素計算著色shader

逐畫素計算時,我們的主要計算放到了pixel shader裡,在vertex shader階段只是進行了基本的頂點變換操作,以及頂點的法線轉化到世界空間的操作,然後將轉化後的法線作為引數傳遞給pixel shader。其他的計算都放到了pixel shader階段,這樣,針對每個畫素,我們都可以來計算這個畫素的光照情況,而不是像逐頂點計算時,先計算好頂點的顏色,然後差值得到中間的畫素顏色。這種逐畫素著色的方式也叫作Phone著色(非Phone光照模型)
Shader "Custom1/LambertFragment"
{
		//屬性  
		Properties{
			_Diffuse("Diffuse", Color) = (1,1,1,1)
		}

			//子著色器    
			SubShader
		{
			Pass
		{
			//定義Tags  
			Tags{ "RenderType" = "Opaque" }

			CGPROGRAM
			//引入標頭檔案  
#include "Lighting.cginc"  
			//定義Properties中的變數  
			fixed4 _Diffuse;
		//定義結構體:應用階段到vertex shader階段的資料  
		struct a2v
		{
			float4 vertex : POSITION;
			float3 normal : NORMAL;
		};
		//定義結構體:vertex shader階段輸出的內容  
		struct v2f
		{
			float4 pos : SV_POSITION;
			float3 worldNormal : TEXCOORD0;
		};

		//定義頂點shader  
		v2f vert(a2v v)
		{
			v2f o;
			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
			//把法線轉化到世界空間  
			o.worldNormal = mul(v.normal, (float3x3)_World2Object);
			return o;
		}

		//定義片元shader  
		fixed4 frag(v2f i) : SV_Target
		{
		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
		//歸一化法線,即使在vert歸一化也不行,從vert到frag階段有差值處理,傳入的法線方向並不是vertex shader直接傳出的  
		fixed3 worldNormal = normalize(i.worldNormal);
		//把光照方向歸一化  
		fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		//根據蘭伯特模型計算畫素的光照資訊,小於0的部分理解為看不見,置為0  
		fixed3 lambert = max(0.0, dot(worldNormal, worldLightDir));
		//最終輸出顏色為lambert光強*材質diffuse顏色*光顏色  
		fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz;
		return fixed4(diffuse+ambient, 1.0);
		}

			//使用vert函式和frag函式  
#pragma vertex vert  
#pragma fragment frag     

			ENDCG
		}

		}
			//前面的Shader失效的話,使用預設的Diffuse  
			FallBack "Diffuse"
}

半蘭伯特模型

廣義的半蘭伯特模型公式如下:

                                 Cdiffuse =( Clight *Mdiffuse )*(阿魯法dot(N,L)+貝塔)

可以看到與原蘭伯特模型相比,半蘭伯特沒有使用max操作來防止點積為負,而是對結果進行了一個阿魯法倍的縮放再加上一個貝塔大小的偏移。當然絕大多情況阿魯法和貝塔的值都為0.5
Shader "Custom1/LambertFragment"
{
		//屬性  
		Properties{
			_Diffuse("Diffuse", Color) = (1,1,1,1)
		}

			//子著色器    
			SubShader
		{
			Pass
		{
			//定義Tags  
			Tags{ "RenderType" = "Opaque" }

			CGPROGRAM
			//引入標頭檔案  
#include "Lighting.cginc"  
			//定義Properties中的變數  
			fixed4 _Diffuse;
		//定義結構體:應用階段到vertex shader階段的資料  
		struct a2v
		{
			float4 vertex : POSITION;
			float3 normal : NORMAL;
		};
		//定義結構體:vertex shader階段輸出的內容  
		struct v2f
		{
			float4 pos : SV_POSITION;
			float3 worldNormal : TEXCOORD0;
		};

		//定義頂點shader  
		v2f vert(a2v v)
		{
			v2f o;
			o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
			//把法線轉化到世界空間  
			o.worldNormal = mul(v.normal, (float3x3)_World2Object);
			return o;
		}

		//定義片元shader  
		fixed4 frag(v2f i) : SV_Target
		{
		fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
		//歸一化法線,即使在vert歸一化也不行,從vert到frag階段有差值處理,傳入的法線方向並不是vertex shader直接傳出的  
		fixed3 worldNormal = normalize(i.worldNormal);
		//把光照方向歸一化  
		fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
		//根據半蘭伯特模型計算畫素的光照資訊
		fixed3 lambert = 0.5* dot(worldNormal, worldLightDir)+0.5;
		//最終輸出顏色為lambert光強*材質diffuse顏色*光顏色  
		fixed3 diffuse = lambert * _Diffuse.xyz * _LightColor0.xyz;
		return fixed4(diffuse+ambient, 1.0);
		}

			//使用vert函式和frag函式  
#pragma vertex vert  
#pragma fragment frag     

			ENDCG
		}

		}
			//前面的Shader失效的話,使用預設的Diffuse  
			FallBack "Diffuse"
}

如圖,左邊是蘭伯特,右邊是半蘭伯特。對比效果還是比較明顯的

.Half Lambert最初是由Valve(遊戲半條命2使用的引擎即是其開發的)提出來,用於提高物體在一些光線無法照射到的區域的亮度的。簡單說來,它提高了漫反射光照的亮度,使得漫反射光線可以看起來照射到一個物體的各個表面。而Half Lambert最初也是被用於遊戲半條命的畫面渲染,為了防止某個物體的背光面丟失形狀並且顯得太過平面化。這個技術是完全沒有基於任何物理原理的,而僅僅是一種感性的視覺增強