天天看點

與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫

      概要

   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結果)

與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫
與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫
與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫

源理論以及公式

        文章理論描述

    有不少文章專門講述了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很類似,反之則描述各向異性反射模型。接下來出場的是一個非常重要的函數:

與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫

    f函數描述的是該表面上已知入射角向量i,那麼得到出射角向量為o的機率是多少。由于BRDF具有對稱性,是以反過來也是一樣的,即已知出射角向量o,得到入射角向量為i的機率就是這個函數要求的東西。這其中的θi,θo,θh,Φh都是在表面局部坐标系下計算的,見下圖:

與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫

(圖中的向量v與z軸夾角寫為θv,Φv則是v在平面xOy上的投影與x方向的夾角)

    上面的圖檔可能描述的并不一定令你覺得清楚,我們試一試使用《Advanced Lighting And Materials With Shaders》中Ward模型的公式來了解上面到底說了什麼gui.... ..        

與Intel的古老顯示卡的戰鬥----Ward光照模型的高光部分的Unity Shader的編寫

      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運作太慢,沒看出來。       以上是腳本的所有重要代碼,如果懶得碼字的話,可以用這個

繼續閱讀