漫反射光照是Unity中最基本最簡單的光照模型,本篇將會介紹在片元著色器中實現反射效果,并會采用半蘭伯特光照技術對其進行改進。
1. 逐頂點光照與逐像素光照
在Unity Shader中,我們可以有兩個地方可以用來計算光照:在頂點著色器中計算,被稱為逐頂點光照(per-vertex lighting);在片元著色器中計算,被稱為逐像素光照(per-pixel lighting)。
在逐像素光照中,我們會以每個像素為基礎,得到它的法線(可以是對頂點法線插值得到的,也可以是從法線紋理中采樣得到的),然后進行光照模型的計算。而逐頂點光照會在每個頂點上計算光照,然后會在渲染圖元內部進行線性插值,最后輸出成像素顏色。由于頂點數目往往遠小于像素數目,因此逐頂點光照的計算量往往要小于逐像素光照,性能較高;但由于逐頂點光照會在渲染圖元內部對頂點顏色進行插值,這又會導致渲染圖元內部的顏色總是暗于頂點處的最高顏色值,這在某些情況下會產生明顯的棱角現象,使渲染效果下降。
2. 初始定義
為了控制材質的漫反射顏色,首先要在Shader的Properties語義塊中聲明一個Color類型的屬性,并把它的初始值設為白色:
_Diffuse("Diffuse", Color) = (1, 1, 1, 1)
接下來在Pass語義塊中,指定我們的光照模式:
Tags {"LightMode" = "ForwardBase" // 指定光照模式
}
為了使用Unity內置的一些變量,如_LightColor0,還需要包含Unity的內置文件Lighting.cginc:
#include "Lighting.cginc"
為了在Shader中使用Properties語義塊中聲明的屬性,我們需要定義一個和該屬性類型相匹配的變量:
fixed4 _Diffuse;
然后定義頂點著色器的輸入和輸出結構體:
// 頂點著色器輸入 結構體
struct a2v
{float4 vertex: POSITION; // 頂點坐標float3 normal: NORMAL; // 法線方向float4 texcorrd: TEXCOORD0; // 模型的第一套紋理坐標
};// 頂點著色器向片元著色器的輸出 結構體
struct v2f
{float4 pos: POSITION; // 頂點在裁剪空間中的位置信息float3 worldNormal: TEXCOORD0; // 世界空間法線
};
3. 實現漫反射
首先我們要在頂點著色器函數中計算世界空間法線并輸出到片元著色器:
// 將法線從模型空間轉世界空間
outData.worldNormal = mul(appData.normal, (float3x3)unity_WorldToObject);return outData;
然后在片元著色器中,通過法線和光源計算輻照度:
// 規范化世界空間法線
fixed3 worldNormal = normalize(inputData.worldNormal);
// 規范化世界空間光源
fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
// 計算輻照度
fixed E = saturate(dot(worldNormal, worldLightDir));
有了輻照度,再混合光照色和反射色計算出漫反射:
// 漫反射 = 光照色 * 反射色 * 輻照度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * E;
最后加入環境光,計算出反射光:
// 獲取環境光
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 環境光 + 漫反射
fixed3 color = ambient + diffuse;return fixed4(color, 1);
渲染效果如下:
?
4. 使用半蘭伯特模型對代碼進行改進
我們發現該漫反射渲染有一個問題:在光照無法到達的區域,模型的表面呈現為全黑,沒有任何明暗變化,這會使模型的背光區域看起來就像一個平面一樣,失去了模型細節表現,因此我們需要引入半蘭伯特光照技術對其進行改進。
我們在片元著色器代碼中,將原先計算漫反射的部分,修改代碼如下:
// 半蘭伯特模型輻照度
fixed halfLambertE = E * 0.5 + 0.5;
// 漫反射 = 光照色 * 反射色 * 半蘭伯特模型輻照度
fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * halfLambertE;
渲染效果如下:
不過需要注意的是,半蘭伯特是沒有任何物理依據的,它僅僅是一個視覺加強技術。?