蘭伯特(Lambert)模型
漫反射,是投射在粗糙表面上的光向各個方向反射的現象。當一束平行的入射光線射到粗糙的表面時,表面會把光線向著四面八方反射,所以入射線雖然互相平行,由於各點的法線方向不一致,造成反射光線向不同的方向無規則地反射,這種反射稱之為“漫反射”或“漫射”。這種反射的光稱為漫射光。很多物體,如植物、牆壁、衣服等,其表面粗看起來似乎是平滑,但用放大鏡仔細觀察,就會看到其表面是凹凸不平的,所以本來是平行的太陽光被這些表面反射後,瀰漫地射向不同方向。
(這圖盜的別人的,直觀,非原創)
漫反射光照符合蘭伯特定律:反射光線的強度與表面法線和光源方向之間的夾角的餘弦成正比,因此漫反射的部分計算如下:
Cdiffuse =( C
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最初也是被用於遊戲半條命的畫面渲染,為了防止某個物體的背光面丟失形狀並且顯得太過平面化。這個技術是完全沒有基於任何物理原理的,而僅僅是一種感性的視覺增強