摘抄“GPU Programming And Cg Language Primer 1rd Edition” 中文名“GPU編程與CG語言之陽春白雪下里巴人”
Lambert 模型較好地表現了粗糙表面上的光照現象,如石灰粉刷的牆壁、紙張等,但在用於諸如金屬材質製成的物體時,則會顯得呆板,表現不出光澤,主要原因是該模型沒有考慮這些表面的鏡面反射效果。一個光滑物體被光照射時,可以在某個方向上看到很強的反射光,這是因爲在接近鏡面反射角的一個區域內,反射了入射光的全部或絕大部分光強,該現象稱爲鏡面反射。
故此, Phong Bui Tuong 提出一個計算鏡面反射光強的經驗模型,稱爲phong 模型,認爲鏡面反射的光強與反射光線和視線的夾角相關,其數學表達如公式(9-5) 所示:
Ks爲材質的鏡面反射係數, Ns 是高光指數, V 表示從頂點到視點的觀察方向, R 代表反射光方向。
高光指數反映了物體表面的光澤程度。 Ns 越大,反射光越集中,當偏離反射方向時,光線衰減的越厲害,只有當視線方向與反射光線方向非常接近時才能看到鏡面反射的高光現象,此時,鏡面反射光將會在反射方向附近形成亮且小的光斑; Ns 越小,表示物體越粗糙,反射光分散,觀察到的光斑區域小,強度弱。
反射光的方向 R 可以通過入射光方向 L (從頂點指向光源)和物體法向量 N 求出:
所以有:
實際上, Cg 語言標準函數庫中有求取反射光方向的函數(參閱 8.3.2 節),不過掌握求取反射光方向的方法是有必要的,很多公司在考計算機圖形學算法時會要求你寫出這個公式,到時候諸如 “ 我知道某某函數可以實現這個功能 ” 之類的話語是不會打動面試官的。自從微軟經常拿 VS 中的一些函數要求面試者寫出實現時,這種筆試方法就形成了一種潮流,不過如果有可能的話,我很想對這種筆試方法說: “ 滾 ” !
9.3.1 p hong 模型渲染
Phong 光照模型的渲染代碼如 代碼 3 所示。 圖 18 展示了在頂點着色程序中進行 phong 光照渲染的效果:
從 圖 18 可以看出,與漫反射模型的渲染效果相比, phong 光照模型的渲染效果要圓潤很多,明暗界限分明,光斑效果突出。不過請注意 圖 18 中馬的渲染效果,可以很清楚的發現,馬的渲染效果沒有其他三個模型好,原因在於馬模型的面片少,是低精度模型,而頂點着色渲染只對幾何頂點進行光照處理,並不會對內部點進行處理。
爲了使得低精度模型也能得到高質量的渲染效果,就必須進行片段渲染,所以本節中我們還將給出使用片段着色程序的 phong 光照模型渲染代碼和效果。
代碼 3 phong 光照模型的頂點着色程序實現
struct VertexIn
{
float4 position : POSITION; // Vertex in object-space
float4 normal : NORMAL;
};
struct VertexScreen
{
float4 oPosition : POSITION;
float4 color : COLOR;
};
void main_v( VertexIn posIn,
out VertexScreen posOut,
uniform float4x4 modelViewProj,
uniform float4x4 worldMatrix,
uniform float4x4 worldMatrix_IT,
uniform float3 globalAmbient,
uniform float3 eyePosition,
uniform float3 lightPosition,
uniform float3 lightColor,
uniform float3 Kd,
uniform float3 Ks,
uniform float shininess)
{
posOut.oPosition = mul(modelViewProj, posIn.position);
float3 worldPos = mul(worldMatrix, posIn.position).xyz;
float3 N = mul(worldMatrix_IT, posIn.normal).xyz;
N = normalize(N);
// 計算入射光方向、視線方向、反射光線方向
float3 L = normalize(lightPosition - worldPos);
float3 V = normalize(eyePosition - worldPos);
float3 R = 2*max(dot(N, L), 0)*N-L;
R = normalize(R);
// 計算漫反射分量
float3 diffuseColor = Kd * globalAmbient+Kd*lightColor*max(dot(N, L), 0);
// 計算鏡面反射分量
float3 specularColor = Ks * lightColor*pow(max(dot(V, R), 0), shininess);
posOut.color.xyz = diffuseColor + specularColor;
posOut.color.w = 1;
}
下面給出同時使用頂點着色程序和片段着色程序的 phong 光照模型代碼。依然是首先定義結構體,用來包含輸入、輸出數據流,不過這裏使用的結構體( 代碼 4 )和 代碼 3 中的有所不同,在 VertexScreen 結構體中有兩個綁定到 TEXCOORD 語義詞的變量, objectPos 和 objectNormal ,這兩個變量用於傳遞頂點模型空間座標和法向量座標到片段着色器中。
代碼 4 phong 光照模型片段着色實現的結構體
struct VertexIn
{
float4 position : POSITION;
float4 normal : NORMAL;
};
struct VertexScreen
{
float4 oPosition : POSITION;
float4 objectPos : TEXCOORD0;
float4 objectNormal : TEXCOORD1;
};
代碼 5 展示了當前的頂點着色程序代碼,其所做的工作有兩點:首先將幾何頂點的模型空間座標轉換爲用於光柵化的投影座標;然後將頂點模型座標和法向量模型座標賦值給綁定 TEXCOORD 語義詞的變量,用於傳遞到片段着色程序中。
代碼 5 phong 光照模型頂點着色程序
void main_v(VertexIn posIn,
out VertexScreen posOut,
uniform float4x4 modelViewProj)
{
posOut.oPosition = mul(modelViewProj, posIn.position);
posOut.objectPos = posIn.position;
posOut.objectNormal = posIn.normal;
}
代碼 6 展示了使用 phong 光照模型渲染的片段着色程序。我將反射光方向、視線方向、入射光方向都放在片段着色程序中計算,實際上這些光照信息也可以放到頂點着色程序中計算,然後傳遞到片段着色程序中。
代碼 6 phong 光照模型片段着色程序
void main_f( VertexScreen posIn,
out float4 color : COLOR,
uniform float4x4 worldMatrix,
uniform float4x4 worldMatrix_IT,
uniform float3 globalAmbient,
uniform float3 eyePosition,
uniform float3 lightPosition,
uniform float3 lightColor,
uniform float3 Kd,
uniform float3 Ks,
uniform float shininess)
{
float3 worldPos = mul(worldMatrix, posIn.objectPos).xyz;
float3 N = mul(worldMatrix_IT, posIn.objectNormal).xyz;
N = normalize(N);
// 計算入射光方向、視線方向、反射光線方向
float3 L = normalize(lightPosition - worldPos);
float3 V = normalize(eyePosition - worldPos);
float3 R = 2*max(dot(N, L), 0)*N-L;
R = normalize(R);
// 計算漫反射分量
float3 diffuseColor = Kd * globalAmbient+Kd*lightColor*max(dot(N, L), 0);
// 計算鏡面反射分量
float3 specularColor = Ks * lightColor*pow(max(dot(V, R), 0), shininess);
color.xyz = diffuseColor + specularColor;
color.w = 1;
}
圖 19 展示了同時使用頂點着色程序和片段着色程序的 phong 光照模型渲染效果。