XNA Shader编程教程2-漫反射光照

这次我们要在教程1的基础上通过实现漫反射光照(又称为“位置光照模型positional lighting model”)让光照方程变得更有趣点。

程序截图

漫反射光照

环境光满足下列公式:

I = Aintensity * Acolor

漫反射光的公式以此为基础,在方程中添加了一个定向光:

I = Aintensity*Acolor + Dintensity*Dcolor *N.L

从这个公式可以看到我们仍然使用环境光,但需要额外两个变量描述漫反射的的颜色和强度,两个向量N描述表面的法线,L描述光线的方向。我们可以将漫反射光线作为表示表面反射光线的多少。

光线反射的强度随着N和L夹角的变小而变得更强。如果L与N平行则反射最强烈,如果L平行于表面,则反射最弱。

图1

要计算L和N的夹角,我们可以使用点乘或标量乘积。这条规则可用来计算给定两个向量间的夹角,可以定义如下:

N.L = |N| * |L| * cos(a)

(译者注:这个公式实际上是Lambert定理的简化形式,若归一化N和L,则这个公式可简化为N.L=cos(a)

这里|N|是向量N的长度,|L|是向量L的长度,cos(a)是两个向量之间夹角的余弦。

实现shader

我们需要三个全局变量:

我们仍然要教程1中的worldviewprojection矩阵,但除此之外,我们还需要InverseWorld矩阵计算出与世界矩阵相关的正确法线,而vLightDirection表示光线的方向。我们还需要在vertex shader中定义OUT结构,这样才能在pixel shader中获得正确的光线方向:

struct OUT
{
    float4 Pos: POSITION;
    float3 L: TEXCOORD0;
    float3 N: TEXCOORD1;
};

这里定义了位置Pos,光线方向L和法线方向N存储在不同的寄存器中。TEXCOORDn可用于任何值,现在我们还没有使用任何纹理坐标,所以我们可以用这些寄存器储存这两个向量。

OK,现在处理vertex shader:

OUT VertexShader( float4 Pos: POSITION, float3 N: NORMAL )
{ 
    OUT Out = (OUT) 0;
    Out.Pos = mul(Pos, matWorldViewProj);
    Out.L = normalize(vLightDirection);
    Out.N = normalize(mul(matInverseWorld, N));
    return Out;
} 

我们从模型文件获取位置和法线并通过它们传递到shader。根据这些值和全局变量我们可以转换位置,法线和光线方向,并转换和归一化表面的法线。

然后,在Pixel Shader中获取TEXCOORD0中的的值并把它放在L中,TEXCOORD1中的值放入N。这些寄存器的数据是由vertex shader添加的。然后,我们在pixel shader执行上面的漫反射方程:

float4 PixelShader(float3 L: TEXCOORD0, float3 N: TEXCOORD1) : COLOR 
{
   float Ai = 0.8f;
   float4 Ac = float4(0.075, 0.075, 0.2, 1.0);
   float Di = 1.0f;
   float4 Dc = float4(1.0, 1.0, 1.0, 1.0);
   return Ai * Ac + Di * Dc * saturate(dot(L, N));
} 

译者注:因为dot(L, N)的范围在(-1,1)之间,所以需要saturate将它截取到(0,1)之间。

使用的technique如下:

technique DiffuseLight
{
    pass P0
    {
        VertexShader = compile vs_1_1 VertexShader(); 
        PixelShader = compile ps_1_1 PixelShader();
    }
}

好了,这就是漫反射光照!可以下载源码更好地理解原理,希望你能感受到shader的威力并知道如何在程序中实现。

译者注:还看过一个例子中不使用InverseWorld矩阵,而是使用Out.N = normalize(mul(N,matWorld));而本例使用的是Out.N = normalize(mul(matInverseWorld, N));

另外本例中的L向量对应的vLightDirection在程序中设置为:

Vector4 vLightDirection = new Vector4(0.0f, 0.0f, 1.0f, 1.0f);

这表示指向z轴正方向,即垂直屏幕向外,这里实际是指“顶点指向光源的方向”,也就是说光线的方向是垂直屏幕向里的,教程里使用“光线方向”容易引起误解,至少我是一开始就搞错了。习惯上光线的方向是指“从光源指向顶点的方向”,这时应该是用saturate( dot(-L,N));而不是本例中的saturate(dot(L, N));因为根据Lambert定理,光线的方向是指从顶点指向光源的方向,而导入的L是指光源指向顶点的方向,所以要-L。


发布时间:2009/4/1 下午1:07:09  阅读次数:12133

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号