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 15:17:02  阅读次数:14967

2006 - 2024,推荐分辨率1024*768以上,推荐浏览器Chrome、Edge等现代浏览器,截止2021年12月5日的访问次数:1872万9823 站长邮箱

沪ICP备18037240号-1

沪公网安备 31011002002865号