问题

在前面的教程中,你学习了如何根据法线数据使三角形获取正确的光照。

但是,盲目地将这个方法施加到所有三角形中往往得不到最好的效果。

如果三角形的每个顶点具有相同的法线方向,那么光照是一样的,所有像素会获得同样的光照。如果两个相邻三角形(不在同一平面)也是如此施加光照,那么一个三角形的所有像素获得同样的光照,另一个三角形的所有像素获得另一个光照。这会导致很容易地可见看见两者的边界,因为两个三角形有不同的颜色。

如果三角形中的颜色是平滑过渡的,你想获取一个更好的效果。要做到这点,从一个三角形到另一个三角形的明暗应该平滑过渡。

解决方案

显卡是根据三角形的三个顶点计算明暗的。三角形中的所有像素的明暗会进行插值。如果三个顶点的法线是相同的,所有像素会获得相同的明暗。如果不同,像素的明暗在角中会平滑过渡。

看一下两个三角形,共享一条边,由六个顶点组成。这种情况如图6-3的左图所示。要保证从一个三角形到另一个三角形的颜色能够平滑过渡, 你需要确保两个边界的颜色是一样的。这可以通过让共享的顶点具有相同的法线实现。在图6-3的左图,顶点1和4,顶点2和3有相同的法线。

image

图6-3 两个共享一条边的三角形

工作原理

本教程中,你将使用两个方法定义两个三角形。首先,定义两个所有顶点都有相同法线的三角形,这会导致三角形中的所有像素都有相同的光照。然后,你要确保共享顶点中的法线是相同的,这会在三角形边界上获得光滑的明暗效果。

每个三角形拥有各自的法线

这个方法获取垂直于三角形的方向并将这个方向存储在顶点中。

下面的代码定义了如图6-3左图中所示的六个顶点。每个三角形的三个顶点具有相同的法线方向,垂直于三角形。左边的三角形垂直放置,所以它的法线向左。第二个三角形水平放置,法线向上。

private void InitVertices() 
{
    vertices = new VertexPositionNormalTexture[6]; 
    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, 0, - 1), new Vector3(-1, 0, 0), new Vector2(0.5f, 0));
    vertices[2] = new VertexPositionNormalTexture(new Vector3(0, 0, 0), new Vector3(-1, 0, 0), new Vector2(0.5f, 1));
    vertices[3] = new VertexPositionNormalTexture(new Vector3(0, 0), new Vector3(0, 1, 0), new Vector2(0.5f,1));
    vertices[4] = new VertexPositionNormalTexture(new Vector3(0,- 1), new Vector3(0, 1, 0), new Vector2(0.5f,1)); 
    vertices[5] = new VertexPositionNormalTexture(new Vector3(1, 0), new Vector3(0, 1, 0), new Vector2(1,1)); 
    
    myVertexDeclaration = new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements); 
} 

然后定义一个稍微偏右下方向的光线。确保归一化这个光线的方向:

Vector3 lightDirection = new Vector3(10, -2, 0); 
lightDirection.Normalize(); 
basicEffect.DirectionalLight0.Direction = lightDirection; 

然后绘制两个三角形:

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

可参见教程6-1中的“归一化法线”一节理解为何需要归一化光线方向。

这时应该看到两个三角形,都有一个不透明颜色,如图6-4左图所示。你可以轻易地看到两者间的边界,在绘制大物体时这并不是你想要的结果。

image

图6-4 三角形着色(左图),共享的顶点着色(右图)

共享法线

这次,你将在顶点1和4、顶点2和3上施加同样的法线方向。带来的一个问题是你应该选择哪个方向。

要获取最光滑的效果,你只需简单地求法线的平均值,如下面的代码所示:

private void InitVertices() vertices = new VertexPositionNormalTexture[6]; 
Vector3 normal1 = new Vector3(-1, 0, 0); 
Vector3 normal2 = new Vector3(0, 1, 0); 
Vector3 sharedNormal = normal1 + normal2; 
sharedNormal.Normalize(); 

vertices[0] = new VertexPositionNormalTexture(new Vector3(0, -1, 0), normal1, new Vector2(0,1)); 
vertices[1] = new VertexPositionNormalTexture(new Vector3(0, 0, - 1), sharedNormal, new Vector2(0.5f, 0)); 
vertices[2] = new VertexPositionNormalTexture(new Vector3(0, 0, 0), sharedNormal, new Vector2(0.5f, 1)); 
vertices[3] = new VertexPositionNormalTexture(new Vector3(0, 0), sharedNormal, new Vector2(0.5f,1)); 
vertices[4] = new VertexPositionNormalTexture(new Vector3(0,- 1), sharedNormal, new Vector2(0.5f,1));
vertices[5] = new VertexPositionNormalTexture(new Vector3(1, 0), normal2, new Vector2(1,1)); 

myVertexDeclaration=new VertexDeclaration(device, VertexPositionNormalTexture.VertexElements); 

首先求出两个三角形的法线之和,然后归一化结果(见教程6-1中的“归一化法线”一节)。计算的结果会指向左和上之间。

然后定义六个顶点。两侧的两个顶点不共享法线,所以仍保持原来的法线值。其他四个共享法线的顶点使用相同的法线。

现在,当绘制两个三角形时,明暗会光滑地从一个两侧的顶点过渡到共享的边,如图6-4右图所示。让你很难看到两个三角形的共同边,这样用户不会发现物体是由多个三角形组成的。

技巧:教程5-7介绍了如何给一个大物体自动计算共享法线。

当使用索引时也可以使用这个方法(参见教程5-3)。

代码

在教程的前面可以找到定义三角形的代码,下面的代码用来绘制三角形:

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(10, -2, 0); 
lightDirection.Normalize(); 
basicEffect.DirectionalLight0.Direction = lightDirection; 
basicEffect.DirectionalLight0.DiffuseColor = Color.White.ToVector3(); 
basicEffect.DirectionalLight0.Enabled = true; 

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

注意:这个教程使用的纹理只有一个不透明的颜色(蓝色), 你可以确认最后的颜色渐变效果。