XNA Shader编程教程3-镜面反射光照
这次我们将实现一个叫做镜面反射的光线算法。该算法是建立在前面环境光照和漫反射光照的基础上的,所以,如果你前面没有弄懂,现在是时候了。:)
在这个教程中,您需要一些shader编程的基本知识,矢量和矩阵的数学知识。
镜面反射光照
迄今为止,我们已经实现了一个很好的照明模式。但是,如果我们想绘制一个抛光或闪耀的物体该怎么办?比如说金属表面,塑料,玻璃,瓶子等。为了模拟这种情况,我们需要在照明算法中加入一个新的向量:eye 向量。您可能会想什么是“eye”向量?这很容易回答:“eye”向量是从相机位置指向观察目标的向量。在程序代码中已经有了这个向量:
viewMatrix= Matrix.CreateLookAt( new Vector3(x, y, zHeight), Vector3.Zero, Vector3.Up );
"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
如前面的截图可见,现在这个对象看起来很有光泽,只需通过shader就能实现!很酷吧!
首先声明一些变量:
float4x4 matWorldViewProj; float4x4 matWorld; float4 vecLightDir; float4 vecEye; float4 vDiffuseColor; float4 vSpecularColor; float4 vAmbient;
然后是OUT结构。Shader将返回经过变换的位置Pos,光线向量L,法线向量N和观察向量V(Eye向量)。
struct OUT { float4 Pos : POSITION; float3 L : TEXCOORD0; float3 N : TEXCOORD1; float3 V : TEXCOORD2; };
除了V向量,在vertex shader没有新的东西。V可以通过将Eye向量减去经变换后的位置向量得到。由于V是OUT结构的一部分,而且我们已经定义了OUT out,所以可以通过下列代码计算V:
float4 PosWorld = mul(Pos,matWorld); Out.V= vecEye - PosWorld
这里的vecEye向量是通过参数(相机位置)传递到shader中的。
OUT VS(float4 Pos : POSITION, float3 N :NORMAL) { OUT Out = (OUT)0; Out.Pos = mul(Pos, matWorldViewProj); Out.N = mul(N, matWorld); float4 PosWorld = mul(Pos, matWorld); Out.L = vecLightDir; Out.V = vecEye - PosWorld; return Out; }
然后处理pixelshader。首先归一化Normal,LightDir和ViewDir简化计算。
Pixelshader会根据前面镜面反射公式返回float4代表当前像素的最终颜色和光强。然后使用教程2同样的方法计算漫反射光线的方向。
Pixel Shader中新的东西是通过L和N计算反射向量R,并使用这个向量计算镜面反射光。因此,首先计算反射向量R:
R = 2 * (N.L) * N – L
在前面我们已经在计算漫反射光时计算了N和L的点乘。所以可以使用如下代码:
float3 Reflect = normalize(2 * Diff * Normal - LightDir);
译者注:也可以使用HLSL内置的函数reflect计算Reflect向量,注意在LightDir前有个负号,想想为什么:
float3 Reflect = normalize(reflect(-LightDir, Normal));
现在只剩下计算镜面反射光了。我们知道,这需要计算反射向量R和观察向量V的n次幂:(R.V)^n
n表示物体的光泽程度,n越大,反光区域越小。您可能注意到,我们使用了一个新的HLSL函数pow(a,b),它返回a的b次方。
float Specular = pow(saturate(dot(Reflect, ViewDir)), 15);
我们终于准备将一切整合在一起并计算出最终的像素颜色:
return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular;
这个公式你应该很熟悉了,对不对? 我们首先计算环境光和漫反射光并把它们相加。然后将镜面反光的颜色乘以刚才算出的反光强度Specular,并和环境光颜色和漫反射颜色相加。本教程的pixel shader如下所示:
float4 PS(float3 L: TEXCOORD0, float3 N : TEXCOORD1, float3 V : TEXCOORD2) : COLOR { float3 Normal = normalize(N); float3 LightDir = normalize(L); float3 ViewDir = normalize(V); float Diff = saturate(dot(Normal, LightDir)); // R = 2 * (N.L) * N – L float3 Reflect = normalize(2 * Diff * Normal - LightDir); float Specular = pow(saturate(dot(Reflect, ViewDir)), 15); // R.V^n // I = A + Dcolor * Dintensity * N.L + Scolor * Sintensity * (R.V)n return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular; }
当然,我们还要指定一个technique 并编译Vertex 和Pixel shader:
technique SpecularLight { pass P0 { // compile shaders VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS(); } }
shader( .fx )文件的完整代码如下:
float4x4 matWorldViewProj; float4x4 matWorld; float4 vecLightDir; float4 vecEye; float4 vDiffuseColor; float4 vSpecularColor; float4 vAmbient; struct OUT { float4 Pos : POSITION; float3 L : TEXCOORD0; float3 N : TEXCOORD1; float3 V : TEXCOORD2; }; OUT VS(float4 Pos : POSITION, float3 N : NORMAL) { OUT Out = (OUT)0; Out.Pos = mul(Pos, matWorldViewProj); Out.N = mul(N, matWorld); float4 PosWorld = mul(Pos, matWorld); Out.L = vecLightDir; Out.V = vecEye - PosWorld; return Out; } float4 PS(float3 L: TEXCOORD0, float3 N : TEXCOORD1, float3 V : TEXCOORD2) : COLOR { float3 Normal = normalize(N); float3 LightDir = normalize(L); float3 ViewDir = normalize(V); float Diff = saturate(dot(Normal, LightDir)); // R = 2 * (N.L) * N - L float3 Reflect = normalize(2 * Diff * Normal - LightDir); float Specular = pow(saturate(dot(Reflect, ViewDir)), 15); // R.V^n // I = A + Dcolor * Dintensity * N.L + Scolor * Sintensity * (R.V)n return vAmbient + vDiffuseColor * Diff + vSpecularColor * Specular; } technique SpecularLight { pass P0 { // compile shaders VertexShader = compile vs_1_1 VS(); PixelShader = compile ps_2_0 PS(); } }
使用shader
相对上一个教程几乎没有新的东西,除了设置vecEye参数。我们只是将相机的位置传递给shader。如果您使用的是相机类,那么在这个类中可能有一个方法可以获取相机的位置,这取决于您如何处理。在我的例子,我使用了相同的变量设置相机的位置,并创建了一个vector。
Vector4 vecEye = new Vector4(x, y, zHeight,0);
并将它传递到shader中
effect.Parameters["vecEye"].SetValue(vecEye);
从程序中设置参数以及如何实现Shader应该不是一个新话题了,所以我不打算进一步详细说明这一点。请参阅教程2和教程1。别忘了将technique设置为“SpecularLight”。
练习
1. 创建一个新的全局变量指定对象的“shininess”。您可以在应用程序中设定这个值。
2.本教程中,你对光线设置的控制权不多(如设置Ai和Ac,Di和Dc)。使这个shader支持设置Ai,Ac,Di,Dc、Si和Sc。
发布时间:2009/4/2 下午3:17:02 阅读次数:15469