一步步学shader系列(2):环境光照diffuse漫反射光照

来源:互联网 发布:智商高女生 知乎 编辑:程序博客网 时间:2024/05/17 04:57

第一个节的基础上通过实现漫反射光照(又称为“位置光照模型positional lighting model”)让光照方程变得更有趣点。


漫反射光照
环境光满足下列公式:

I = Aintensity * Acolor

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

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

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

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

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

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

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

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

在第一节基础上实现shader,我们需要6个全局变量:

float4x4 matViewProjection;
float4   ambientMtl;
float    Aintensity;
float4   diffuseMtl;
float4   directionLight;
float    Diffusesity;

我们仍然要第一节中的worldviewprojection矩阵,但除此之外,我们还需要InverseWorld矩阵计算出与世界矩阵相关的正确法线,而directionLight表示光线的方向,即光照是一个方向光。我们还需要在vertex shader中定义InputOut结构,直接传出计算后的颜色:

struct VS_OUTPUT
{
   float4 Position : POSITION0;
   float4 Color    : COLOR0;
};
OK,现在处理vertex 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);
   dir = normalize(dir);
   float4 Normaltemp = mul(Input.Normal, matViewProjection);
   Normaltemp = normalize(Normaltemp);
   float s = dir * Normaltemp;
   if (s < 0.0f)
      s = 0;
   Output.Color = Aintensity * ambientMtl + Diffusesity * diffuseMtl * s;
   return( Output );
}

:因为dot(L, N)的范围在(-1,1)之间,所以需要saturate将它截取到(0,1)之间,两个写法:
写法(1): 
float s = dir * Normaltemp;
   if (s < 0.0f)
      s = 0;
写法(2):
float s = saturate(dir * Normaltemp);

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

然后,在Pixel Shader中直接输出颜色即可。


使用的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。