逐頂點光照
所謂逐頂點光照,簡單地說就是在vetext shader中計算光照顏色,該過程將為每個頂點計算一次光照顏色,然后在通過頂點在多邊形所覆蓋的區域對像素顏色進行線形插值。現實中,光照值取決於光線角度,表面法線,和觀察點(對於鏡面高光來說)。具體實現時的shader代碼如下:
//相關全局變量
shared float4x4 matWorldViewProj;
shared float4x4 matWorld;
shared float3 lightPosition;
shared float4 ambientLightColor;
shared float4 diffuseLightColor;
shared float4 specularLightColor;
shared float3 cameraPosition;
//VertexShader輸出結構
struct VertexShaderOutput
{
float4 Position : POSITION;
float4 Color : COLOR0;
};
//PixelShader輸入結構,只接受從VertexShader傳來的顏色
struct PixelShaderInput
{
float4 Color: COLOR0;
};
VertexShaderOutput VertexDiffuseAndPhong(float3 position : POSITION,float3 normal : NORMAL )
{
VertexShaderOutput output;
//transform the input position to the output
output.Position = mul(float4(position, 1.0), matWorldViewProj);
float3 worldNormal = mul(normal, matWorld);
float4 worldPosition = mul(float4(position, 1.0), matWorld);
worldPosition = worldPosition / worldPosition.w;
float3 directionToLight = normalize(lightPosition - worldPosition.xyz);
float diffuseIntensity = saturate( dot(directionToLight, worldNormal));
float4 diffuse= diffuseLightColor * diffuseIntensity;
float3 reflectionVector = normalize(reflect(-directionToLight, worldNormal));
float3 directionToCamera = normalize(cameraPosition - worldPosition.xyz);
float4 specular = specularLightColor * pow(saturate(dot(reflectionVector,
directionToCamera)), 20);
output.Color = specular + diffuse + ambientLightColor;
output.Color.a = 1.0;
//return the output structure
return output;
}
float4 SimplePixelShader(PixelShaderInput input) : COLOR
{
return input.Color;
}
technique PerVertexDiffuseAndPhong
{
pass P0
{
//set the VertexShader state to the vertex shader function
VertexShader = compile vs_2_0 VertexDiffuseAndPhong();
//set the PixelShader state to the pixel shader function
PixelShader = compile ps_2_0 SimplePixelShader();
}
}
由以上代碼可見,各像素的顏色計算都是在VertexShader中實現的。程序截圖如下:

當考慮光照時,大部分人都認為逐頂點光照已經足夠好了。對於鑲嵌度較高的模型來說是這樣,但對某些多邊形較少的模型來說卻不一定。比如這個示例,球的多邊形較少,可以明顯看出棱角分明,高光效果也不理想。直接對頂點顏色進行插值所得的結果通常不夠精確,特別是對面積較大的多邊形來說。當處理高精度多邊形模型時,由於每個多邊形所覆蓋的區域很小,因此插值之后每個像素的誤差也很小,所以逐頂點光照可以工作的很好。而當處理低模時,這種誤差就變的很大了。
逐像素光照
逐像素光照是對所有光照元素進行單獨插值,簡單地說就是在pixelshader中計算顏色。具體實現時的shader代碼如下:
//全局變量
shared float4x4 matWorldViewProj;
shared float4x4 matWorld;
shared float3 cameraPosition;
shared float3 lightPosition;
shared float4 ambientLightColor;
shared float4 diffuseLightColor;
shared float4 specularLightColor;
struct VertexShaderOutputPerPixelDiffuse
{
float4 Position : POSITION;
float3 WorldNormal : TEXCOORD0;
float3 WorldPosition : TEXCOORD1;
};
struct PixelShaderInputPerPixelDiffuse
{
float3 WorldNormal : TEXCOORD0;
float3 WorldPosition : TEXCOORD1;
};
VertexShaderOutputPerPixelDiffuse PerPixelDiffuseVS( float3 position : POSITION, float3 normal : NORMAL )
{
VertexShaderOutputPerPixelDiffuse output;
//transform the input position to the output
output.Position = mul(float4(position, 1.0), matWorldViewProj);
output.WorldNormal = mul(normal, matWorld);
float4 worldPosition = mul(float4(position, 1.0), matWorld);
output.WorldPosition = worldPosition / worldPosition.w;
//return the output structure
return output;
}
float4 DiffuseAndPhongPS(PixelShaderInputPerPixelDiffuse input) : COLOR
{
//calculate per-pixel diffuse
float3 directionToLight = normalize(lightPosition - input.WorldPosition);
float diffuseIntensity = saturate( dot(directionToLight, input.WorldNormal));
float4 diffuse = diffuseLightColor * diffuseIntensity;
//calculate Phong components per-pixel
float3 reflectionVector = normalize(reflect(-directionToLight, input.WorldNormal));
float3 directionToCamera = normalize(cameraPosition - input.WorldPosition);
//calculate specular component float4 specular = specularLightColor *
pow(saturate(dot(reflectionVector, directionToCamera)), 20);
//all color components are summed in the pixel shader
float4 color = specular + diffuse + ambientLightColor;
color.a = 1.0;
return color;
}
technique PerPixelDiffuseAndPhong
{
pass P0
{
VertexShader = compile vs_2_0 PerPixelDiffuseVS();
PixelShader = compile ps_2_0 DiffuseAndPhongPS();
}
}
由上面兩段代碼對比可知,算法實際上是一樣的,只不過顏色的計算過程一個放在VertexShader中,而另一個放在PixelShader中。程序截圖如下,源代碼中可以通過按空格鍵切換兩種效果,逐像素光照效果好:

使用逐像素光照的另一個好處是可以在渲染時添加並不存在的表面細節。通過bump map或normal map,可以在像素級別讓原本平坦的表面表現出近似的凹凸效果。
當然,由於逐像素的計算量要比逐頂點要大,所以請根據具體情況靈活選擇,如果你使用BasicEffect,那么默認是使用逐頂點光照,你必須添加basicEffect.PreferPerPixelLighting=true才能開啟逐像素光照。
最后以上大部分文字來自於clayman博客中的The Complete Effect and HLSL Guide的第十二章,在此感謝。
