这次我们将实现一个叫做镜面反射的光线算法。该算法是建立在前面环境光照和漫反射光照的基础上的,所以,如果你前面没有弄懂,现在是时候了。:)
在这个教程中,您需要一些shader编程的基本知识,矢量和矩阵的数学知识。
迄今为止,我们已经实现了一个很好的照明模式。但是,如果我们想绘制一个抛光或闪耀的物体该怎么办?比如说金属表面,塑料,玻璃,瓶子等。为了模拟这种情况,我们需要在照明算法中加入一个新的向量:eye 向量。您可能会想什么是“eye”向量?这很容易回答:“eye”向量是从相机位置指向观察目标的向量。
其实就是 vector3 eyePos = objectPos - CamraPos
"The eye" 的位置在这:
Vector3(x, y, zHeight)
让我们把这个向量存储在一个变量中: Vector4 vecEye = new Vector4(x, y, zHeight,0);
让我们深入讨论如何在创建这个向量后使用shader。镜面高光的公式是
I=AiAc+Di*Dc*N.L+Si*Sc*(R.V)^n
其中R=2*(N.L)*N-L
我的另外一篇博客文章有详细介绍,大家可以翻阅。
如我们所见,我们需要新的Eye向量和一个反射向量R。为了计算镜面光,我们需要将R点乘V并进行n次幂的运算,而指数n表示光泽属性,n越大说明物体表面越光滑,反光越强。
如前面的截图可见,现在这个对象看起来很有光泽,只需通过shader就能实现!很酷吧!
首先声明一些变量:
float4x4 matViewProjection;float4x4 matWorld;
float4 ambientMtl;
float Aintensity;
float4 diffuseMtl;
float4 directionLight;
float Diffusesity;
float specularsity; //高光系数
int Ispecular; //光泽属性
float4 ViewDirection; //EYE
float4 specularMtl; //高光光泽颜色,对应游戏里面就是材质
然后是INT 和 OUT结构。仍然和前面一样是:
struct VS_INPUT
{
float4 Position : POSITION0;
float4 Normal : NORMAL0;
};
struct VS_OUTPUT
{
float4 Position : POSITION0;
float4 Color : COLOR0;
};
除了V向量,在vertex shader没有新的东西。
这里的vecEye向量是通过参数(相机位置)传递到shader中的。
VS_OUTPUT vs_main( VS_INPUT Input )
{
VS_OUTPUT Output;
Output.Position = mul( Input.Position, matViewProjection );
float4 directionLightTemp = directionLight;
directionLightTemp.w = 0;
float4 dir = mul(directionLightTemp, matViewProjection);
float4 Normaltemp = mul(Input.Normal, matViewProjection);
float s = dir * Normaltemp;
if (s < 0.0f)
s = 0;
float4 posWorld = mul(Input.Position, matWorld);
float4 eyeDir = normalize(ViewDirection- posWorld);
float4 R = 2 * s * Normaltemp - dir;
Output.Color = Aintensity * ambientMtl +
+ Diffusesity * diffuseMtl * s
+ specularsity * specularMtl * pow((eyeDir * R), Ispecular);
Output.TexCoord1 = Input.TexCoord1;
return( Output );
}
Pixel Shader中新的东西是通过L和N计算反射向量R,并使用这个向量计算镜面反射光。因此,首先计算反射向量R:
R = 2 * (N.L) * N – L
在前面我们已经在计算漫反射光时计算了N和L的点乘。所以可以使用如下代码:
float4 R = 2 * s * Normaltemp - dir;
译者注:也可以使用HLSL内置的函数reflect计算Reflect向量,注意在LightDir前有个负号,想想为什么:
float3 R = normalize(reflect(-dir, Normaltemp ));
现在只剩下计算镜面反射光了。我们知道,这需要计算反射向量R和观察向量V的n次幂:(R.V)^n
n表示物体的光泽程度,n越大,反光区域越小。您可能注意到,我们使用了一个新的HLSL函数pow(a,b),它返回a的b次方。
float Specular = pow(saturate(dot(R , ViewDirection)), 15);
我们终于准备将一切整合在一起并计算出最终的像素颜色:
return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular;
这个公式你应该很熟悉了,对不对? 我们首先计算环境光和漫反射光并把它们相加。然后将镜面反光的颜色乘以刚才算出的反光强度Specular,并和环境光颜色和漫反射颜色相加。
当然,我们还要指定一个technique 并编译Vertex 和Pixel shader:
technique SpecularLight { pass P0 { // compile shaders VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS(); } }