概要
Wake me up when Augest End,始寫于八月末,對于Lambert和Blinn來說,Ward光照模型屬于一款BRDF光照模型,比起同是BRDF的Cook-Torrance光照模型的鏡面反射來說,更難得的是它能調節自己材質各向異性。
雖然既是BRDF又自帶各向異性,但Ward光照模型并沒有受到Unity程式員的青睐,對比于它實作的效果,它過于複雜的計算導緻效率實在令人诟病。是以本文純屬學習性質,工作中不會用這麼耗的。本文試寫一下Ward模型的高光部分,但是最後并沒有遵守BRDF模型中漫反射與鏡面反射能量守恒這個觀點,隻是很簡單地把Lambert光照和Ward的高光部分加起來。
本文章出自http://blog.csdn.net/blinkseed
最終效果
右邊的正方體為Ward模型,對比于左邊的正方體模型為來自《Unity Shader And Effects Cook Book》中自帶的各向異性模型,你可以在附帶資源裡找到,又或者你不想下載下傳,你也可以看看這篇文章來實作各向異性(下面第三張圖檔為别人渲染Ward結果)

源理論以及公式
文章理論描述
有不少文章專門講述了Ward的理論與實作,其中比較著名的是Bruce Walter在2005年寫的《Notes on the Ward BRDF》,該文章從理論到實作都講了一遍。我在做Ward實作的時候就主要參考了這篇文章。Ward的BRDF定義用到了half vector的概念,half vector定義為h=(wi+wo)/|wi+wo|,就是入射光向量與出射光向量求和再标準化。按照ward本人的描述,Ward BRDF有兩個組成部分,一個是經典的漫反射部分ρd/π,另外一個部分有3個參數ρs,αx與αy。其實ρs就跟phong模型中的鏡面反射系數是差不多的,而αx與αy控制了高光在x和y方向上的範圍(也可以了解成表面在x和y方向上的粗糙程度)。當αx=αy的時候,該brdf描述的是一個各向同性的反射模型,其效果和phong很類似,反之則描述各向異性反射模型。接下來出場的是一個非常重要的函數:
f函數描述的是該表面上已知入射角向量i,那麼得到出射角向量為o的機率是多少。由于BRDF具有對稱性,是以反過來也是一樣的,即已知出射角向量o,得到入射角向量為i的機率就是這個函數要求的東西。這其中的θi,θo,θh,Φh都是在表面局部坐标系下計算的,見下圖:
(圖中的向量v與z軸夾角寫為θv,Φv則是v在平面xOy上的投影與x方向的夾角)
上面的圖檔可能描述的并不一定令你覺得清楚,我們試一試使用《Advanced Lighting And Materials With Shaders》中Ward模型的公式來了解上面到底說了什麼gui.... ..
Unity Shader實作代碼
Properties部分
<span style="font-size:14px;">Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}//主紋理
_Bump ("Bump", 2D) = "bump" {}//法線紋理
_Specular ("Specular", Range(1.0, 10000.0)) = 250.0//高光指數
_SpecularColor ("Specular Color", Color) = (1,1,1,1)//高光顔色
_AlphaX("Alpha X",Range(0.001,1)) = 0.1//X方向的向異指數
_AlphaY("Alpha Y",Range(0.001,1)) = 0.1//Y方向的向異指數
_LightAtten("Light Atten",Float) = 1//高光強度
}</span>
自定義結構體部分
<span style="font-size:14px;">struct a2v
{
float4 vertex : POSITION; // 輸入的模型頂點資訊
fixed3 normal : NORMAL; // 輸入的法線資訊
fixed4 texcoord : TEXCOORD0; // 輸入的坐标紋理集
fixed4 tangent : TANGENT; // 切線資訊
};
struct v2f
{
float4 pos : POSITION; // 輸出的頂點資訊
fixed2 uv : TEXCOORD0; // 輸出的UV資訊
fixed3 lightDir: TEXCOORD1; // 輸出的光照方向
fixed3 viewDir : TEXCOORD2; // 輸出的錄影機方向
fixed4 tangent : TANGENT;
//LIGHTING_COORDS(3,4) // 封裝了下面的寫法
fixed3 _LightCoord : TEXCOORD3; // 光照坐标
fixed4 _ShadowCoord : TEXCOORD4; // 陰影坐标
};</span>
頂點着色器以及片段着色器
v2f vert(a2v v)
{
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.uv = TRANSFORM_TEX (v.texcoord, _MainTex);
o.tangent = v.tangent;
// 建立一個正切空間的旋轉矩陣,TANGENT_SPACE_ROTATION由下面兩行組成
//TANGENT_SPACE_ROTATION;
float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal );
// 将頂點的光方向,轉到切線空間
// 該頂點在對象坐标中的光方向向量,乘以切線空間旋轉矩陣
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex));
// 該頂點在錄影機坐标中的方向向量,乘以切線空間旋轉矩陣
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex));
// 将照明資訊給像素着色器,應該是用于下面片段中光衰弱atten的計算
// TRANSFER_VERTEX_TO_FRAGMENT(o); // 由下面兩行組成
// 頂點轉到世界坐标,再轉到光坐标
o._LightCoord = mul(_LightMatrix0, mul(_Object2World, v.vertex)).xyz;
// 頂點轉到世界坐标,再從世界坐标轉到陰影坐标
o._ShadowCoord = mul(unity_World2Shadow[0], mul(_Object2World, v.vertex));
// 注:把上面兩行代碼注釋掉,也看不出上面效果,或許我使用的是平行光
return o;
}
fixed4 frag(v2f i) : COLOR
{
// 對主紋理進行采樣
fixed4 texColor = tex2D(_MainTex, i.uv);
// 對法線圖進行采樣
fixed3 norm = normalize(UnpackNormal(tex2D(_Bump, i.uv)));
// 求漫反射
// 公式:漫反射色 = 光顔色*N,L的餘弦值(取大于0的),是以夾角越小亮度越小
fixed Diff=dot (norm, normalize(i.lightDir));
//半角向量
fixed3 halfVector = normalize ( i.lightDir + i.viewDir);
//切線方向
fixed3 Tangent = normalize(i.tangent.rgb);
//反射公式
// 光衰弱
fixed atten = LIGHT_ATTENUATION(i);
// 環境光,Unity内置
//fixed3 ambi = UNITY_LIGHTMODEL_AMBIENT.xyz;
//光線的反射向量
fixed3 reflectVector = normalize(2 * dot(halfVector,-i.lightDir) * halfVector - i.lightDir);
fixed NdotH = dot (norm , halfVector);
fixed Sigma = atan(NdotH);
//fixed NdotL = max (0, Diff);
fixed NdotL = Diff;
//fixed NdotV = max (0,dot(norm , normalize(i.viewDir)));
fixed NdotV = dot(norm , normalize(i.viewDir));
//法向量與半角向量的夾角
fixed angle = acos(NdotH);
//法向量與半角向量的夾角的正切值
fixed tanangle = tan(angle);
//計算垂直于反射向量與半角向量的共有平面的向量
fixed3 vertical1 = cross(reflectVector,norm);
//fixed3 vertical1 = cross(norm,reflectVector);
//vertical2垂直于vertical1與法線的共有平面的向量,即是反射向量投射在切線與副法線所共有的平面
fixed3 vertical2 = normalize(cross(norm,vertical1));
//vertical2的cos值
fixed cosTheta = dot(Tangent,vertical2);
fixed cos2Theta = cosTheta * cosTheta;
fixed sin2Theta = 1 - cos2Theta;
//計算高光公式的分子部分
fixed Molecular = exp(-tanangle*tanangle*(cos2Theta/(_AlphaX * _AlphaX)+sin2Theta/(_AlphaY * _AlphaY)));
//計算高光公式的分母部分
fixed Denominator = 4 * 3.14159 * _AlphaX * _AlphaY * sqrt(NdotL * NdotV);
fixed specularpower;
specularpower = pow(saturate(Molecular/Denominator),_Specular);
Diff = saturate (Diff);
// 最終顔色
// 公式: (漫反射 + 反射高光) * 光衰弱 ) * 材質主色
fixed4 cfinal;
cfinal.rgb = (texColor.rgb + (texColor.rgb * specularpower * _LightAtten)) * Diff *_LightColor0.rgb;
cfinal.a = texColor.a;
return cfinal;
}
寫在最後
上面代碼中由于出現差乘,本來以為交換兩個向量會對最終效果出現影響,但最後結果卻沒有出現:-)。可能是我GPU運作太慢,沒看出來。 以上是腳本的所有重要代碼,如果懶得碼字的話,可以用這個