XNA Shader编程教程2-漫反射光照
这次我们要在教程1的基础上通过实现漫反射光照(又称为“位置光照模型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
我们需要三个全局变量:
- float4x4 matWorldViewProj ;
- float4x4 matInverseWorld ;
- float4 vLightDirection ;
我们仍然要教程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 阅读次数:12250