问题

在前两个教程中,是在每个顶点中计算明暗程度,对三角形的每个像素需要对这个值进行插值。所以这叫做逐顶点光照(per-vertex lighting,又叫做高洛德着色,高洛德浓淡Gouraud shading)。

在某些情况中,逐顶点光照没不能产生最好的结果。特别是使用大三角形或有锐利的边缘或两者都有时,往往得不到想要的结果。

举例说明,如图左边是一个有三个面的立方体。图的右边表示共享的法线应该如何定义。本例中,光线方向用四根箭头表示。

image

图6-5 Vertex shader对per-pixel lighting

关注立方体的顶部,对应右图中顶点2和4之间的线段。使用逐顶点光照,会计算顶点2和4的明暗。在顶点4光照不多,因为顶点4的法线几乎是垂直于光线方向的,这里我们认为是20%的光照。顶点2光照得多,因为它的法线方向几乎和光线方向一致,我们设为80%光照。在逐顶点光照中,三角形中的像素的明暗要进行插值,所有介于这两个顶点之间的像素接受的光照是20%至80%之间的插值。这样,没有一个像素可以获得100%的光照。

但是,顶点2和4之间的某一个像素的顶点方向与光线方向完全一致!这个法线显示在图6-5的右图中。很明显,这个像素应该获得100%的光照,但是使用逐顶点光照,这个像素只能获得介于20%和80%之间的某个光照值。

解决方案

逐顶点光照只计算顶点的精确明暗,而在顶点间的像素的明暗是通过插值获取的。

使用逐像素光照,你对所有像素的法线进行插值,让你可以计算每个像素的精确明暗。

工作原理

使用BasicEffect,很容易使用逐像素光照。在设置BasicEffect参数时,只需添加以下代码行:

basicEffect.PreferPerPixelLighting = true; 

注意:要使逐像素shader可以工作,你必须要拥有支持Shader 2.0以上的显卡。你可以使用以下代码检查显卡的支持:

GraphicsDevice. GraphicsDeviceCapabilities.MaxPixelShaderProfile>=ShaderProfile.PS_2_0 
代码

下面的代码创建如图6-5左图所示的顶点。因为某些法线可能不再是单位长度,所有确保在最后要对它们进行归一化:

private void InitVertices() 
{
    vertices = new VertexPositionNormalTexture[8]; 
    vertices[0] = new VertexPositionNormalTexture(new Vector3(0, -1, 0), new Vector3(-1, 0, 0), new Vector2(0, 1)); 
    vertices[1] = new VertexPositionNormalTexture(new Vector3(0, -1,- 1), new Vector3(-1, 0, 0), new Vector2(0, 0)); 
    vertices[2] = new VertexPositionNormalTexture(new Vector3(0, 0, 0), new Vector3(-1, 1, 0), new Vector2(0.33f, 1));
    vertices[3] = new VertexPositionNormalTexture(new Vector3(0, 0,- 1), new Vector3(-1, 1, 0), new Vector2(0.33f, 0)); 
    vertices[4] = new VertexPositionNormalTexture(new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector2(0.66f, 1)); 
    vertices[5] = new VertexPositionNormalTexture(new Vector3(1, 0,- 1), new Vector3(1, 1, 0), new Vector2(0.66f, 0)); 
    vertices[6] = new VertexPositionNormalTexture(new Vector3(1, -1, 0), new Vector3(1, 0, 0), new Vector2(1, 1)); 
    vertices[7] = new VertexPositionNormalTexture(new Vector3(1, -1,- 1), new Vector3(1, 0, 0), new Vector2(1, 0)); 
    
    for (int i = 0; i < vertices.Length; i++)
        vertices[i].Normal.Normalize();
    
    myVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements); 
} 

阅读教程6-1中的“归一化法线”理解为何需要最后的for循环。

注意:因为XNA没有提供一个包含3D位置、颜色、法线的顶点结构,这个教程使用一个蓝色的纹理让每个像素的颜色都是一样的。通过这种方式,你看到的颜色中的所有变化都会被光照影响。

然后就可以使用逐像素光照绘制三角形了:

basicEffect.World = Matrix.Identity; 
basicEffect.View = fpsCam.ViewMatrix; 
basicEffect.Projection = fpsCam.ProjectionMatrix; 
basicEffect.Texture = blueTexture; 
basicEffect.TextureEnabled = true; 
basicEffect.LightingEnabled = true; 
Vector3 lightDirection = new Vector3(3, -10, 0); 
lightDirection.Normalize(); 
basicEffect.DirectionalLight0.Direction = lightDirection; 
basicEffect.DirectionalLight0.DiffuseColor = Color.White.ToVector3(); 
basicEffect.DirectionalLight0.Enabled = true; 
basicEffect.PreferPerPixelLighting = true; 

basicEffect.Begin(); 
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes) 
{
    pass.Begin(); 
    device.VertexDeclaration = myVertexDeclaration; 
    device.DrawUserPrimitives<VertexPositionNormalTexture>(PrimitiveType.TriangleStrip, vertices, 0, 6); 
    pass.End(); 
}
basicEffect.End();

image