第一个节的基础上通过实现漫反射光照(又称为“位置光照模型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。