Phong光照模型、Blinn-phong光照模型歸納

carbonsunsu發表於2011-12-26

上次說到Lambert漫反射光照模型,這是一個用來模擬粗糙表面對光線的漫反射現象的經驗模型,對於紙張、粗糙牆壁等等來說,這個模型或許夠用,但對於金屬這樣的光滑表面來說,我們就需要使用Phong模型來模擬光滑表面對光線的鏡面反射現象。同Lambert一樣,這個模型也是經驗模型,而且在程式中,我們經常同時使用Lambert和Phong兩個模型,因為在現實世界中,任何表面都會同時發生漫反射和鏡面反射兩種現象,因此我們就要使用兩種模型分別計算兩種反射後的光強(也就是頂點顏色值),是渲染的效果看起來真實一些。但要注意,這樣做並不會帶來真正真實的渲染效果,畢竟這兩種模型都是經驗模型,考慮的都是理想情況下。而Blinn-phong光照模型是基於Phong的修正模型,因此就一併歸納了。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

接下來讓我們看看Phong模型:

我們知道,在理想狀況下,鏡面反射後的光之集中在一條線上,因此我們的視線離這條線的距離越近,射入我們眼中的光線就越多,我們看到的光強也就越強。同時,鏡面反射也與物體表面的高光指數(物體表面光澤程度)有關,其數值越大,反射後的光線越集中,反之則越分散,這裡可能會有人想:如果將高光指數設定的很大,也就是光線極其分散,Phong是否可以用來代替Lambert?我想答案肯定是不行的,原因是漫反射是將光線反射到各個角度,而鏡面反射即使反射光線再分散,它們依舊被限制在一個90度的區域中,因此與漫反射的效果是不一樣的。

下面給出公式:

I_spec = k_z * I_l(V • R)^n_s

其中:k_z為表面材質的鏡面反射係數,n_s是高光指數,V表示頂點到視點的向量,R表示反射光的單位向量。

但是在實際的程式編寫中,我們一般都會更容易得到入射光線的單位向量L,所以我們最好轉換一下公式。設頂點的單位法向量為N,有公式:

R + L = (2N • L)N (這裡再次提醒一下,L的方向是由頂點指向光源的)

由這個可以推出:

R = (2N • L)N - L

所以模型公式可以變為:

I_spec = k_z * I_l(V • ((2N • L)N - L))^n_s

下面是用Cg語言實現Phong模型的程式碼:

/**************************************************/

/*Phone鏡面光照模型,同時也加入了Lambert漫反射光照模型 */

/**************************************************/

struct vertex_In

{

float4 in_Position : POSITION;

float4 in_Normal   : NORMAL;

}

struct vertex_Out

{

float4 out_Position : POSITION;

float4 out_Color    : COLOR0;

}

void main_v(vertex_In vertexI,

        out vertex_Out vertexO,



        uniform float4*4 modelViewProj,

        uniform float4*4 worldMatrix,

        uniform float4*4 worldMatrix_IT,

        uniform float3 globalAmbient,

        uniform float3 eyePosition,

        uniform float3 lightPosition,

        uniform float3 lightColor,

        uniform float3 K_d,

        uniform float3 K_s,

        uniform float N_s

        )

{

vertexO.out_Position = mul(modelViewProj , vertexI.in_Position);//計算輸出定點位置



float3 worldPosition = mul(worldMatrix , vertexI.in_Position).xyz;//計算定點世界位置



float3 N = normalize(mul(worldMatrix , vertexI.in_Normal).xyz);//計算定點法線向量

float3 V = normalize(eyePosition - worldPosition);//計算定點到視點的方向

float3 L = normalize(lightPosition - worldPosition);//計算入射光方向

float3 R = normalize(N*max(dot(2*N , L) , 0) - L);//計算反射光方向



float3 diffuseColor = K_d*globalAmbient + K_d*lightColor*max(dot(N , L) , 0);//計算漫反射光強

float3 specularColor = K_s*lightColor*pow(max(dot(V , R) , 0) , N_s);//計算鏡面反射光強



vertexO.out_Color.rgb = diffuseColor + specularColor;

vertexO.out_Color.a = 1;

}

以上程式碼只使用了頂點著色程式,因此只對幾何頂點進行渲染,而不對片段內部的點進行處理,因此如果用來渲染低精度模型效果會很不好,因此我們就要再加入片段著色程式。

程式碼如下:

/**************************************************/

/Phone鏡面光照模型,同時也加入了Lambert漫反射光照模型/

/**************************************************/

struct vertex_In

{

float4 in_Position : POSITION;

float4 in_Normal   : NORMAL;

}

struct vertex_Out

{

float4 out_Position     : POSITION;

float4 out_objecPos     : TEXCOORD0;

float4 out_objectNormal : TEXCOORD1;

}

void main_v(vertex_In vertexI,

        vertex_Out vertexO,

        uniform float4*4 modelViewProj

        )//頂點著色程式入口

{

vertexO.out_Position = mul(modelViewProj , vertexI.in_Position);//計算輸出定點位置    

vertexO.out_objectPos = vertexI.in_Position;

vertexO.out_objectNormal = vertexI.in_Normal;

}

void main_f(vertex_Out vertexI,

        out float4 colorO : COLOR,



        uniform float4*4 worldMatrix,

        uniform float4*4 worldMatrix_IT,

        uniform float3 globalAmbient,

        uniform float3 eyePosition,

        uniform float3 lightPosition,

        uniform float3 lightColor,

        uniform float3 K_d,

        uniform float3 K_s,

        uniform float N_s

        )//片段著色程式入口

{

float3 worldPos = mul(worldMatrix , vertexI.out_objecPos).xyz;//計算定點世界位置

float3 N = normalize(mul(worldMatrix , vertexI.out_objectNormal).xyz);//計算定點法線向量    



float3 V = normalize(eyePosition - worldPosition);//計算定點到視點的方向

float3 L = normalize(lightPosition - worldPosition);//計算入射光方向

float3 R = normalize(N*max(dot(2*N , L) , 0) - L);//計算反射光方向



float3 diffuseColor = K_d*globalAmbient + K_d*lightColor*max(dot(N , L) , 0);//計算漫反射光強

float3 specularColor = K_s*lightColor*pow(max(dot(V , R) , 0) , N_s);//計算鏡面反射光強



colorO.rgb = diffuseColor + specularColor;

colorO.a = 1;

}

這樣的話程式就會對片段中的點也進行處理,渲染效果也會好一些。

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

接下來歸納一下Blinn-phong光照模型

相比較Phong模型,Blinn-phong模型只適用N•H替換了V•R,但卻獲得了明顯的提高,它能提供比Phong更柔和、更平滑的高光,而且速度上也更快,因此成為很多CG軟體中預設的光照渲染方法,同時也被整合到大多數的圖形晶片中,而且在OpenGL和DirectX 3D的渲染管線中,它也是預設的光照模型。

由於這兩個光照模型公式基本相同,所以只解釋一下N•H:

N與前面相同,是頂點的單位法向量,而H則是入射光L和頂點到視點的單位向量的角平分線單位向量,通常也成為半形向量。其計算方法為:

H = (L + V) / (|L + V|)

書上說半形向量廣泛應用於各類關照模型,其中蘊含了很重要的資訊,至於具體是神馬,還需要去在考證一下。。。

Blinn-phong光照模型的程式碼與Phong幾乎一樣,這裡也就不給出了。

相關文章