5.3 使用索引移除冗余顶点

问题

你要绘制的三角形共享了很多顶点,如图5-7所示。

图5-7

图5-7 可以从使用索引中受益的结构

如图5-7所示的八个三角形在使用TriangleList的情况下需要8*3 = 24个顶点,从这个图中可以看到实际上只有9个独立的顶点,所以其余15个顶点会浪费显卡的内存,带宽和处理能力。

解决方案

好的办法是将这9个独立顶点存储在数组中并将这个数组传递到显卡。然后创建一个包含24个数字的集合,作为这9个顶点的引用。图5-8左边是包含24个顶点的大集合,右边是包含9个顶点和24个索引的小集合,注意每个索引都对应一个顶点。

图5-8

图5-8 由24个顶点绘制的8个三角形(左)或24个指向9个顶点的索引(右)

这样做可以带来很大的益处。因为索引只是数字,所以相对于包含一个Vector3,一个颜色,一个纹理坐标,可能还有其他信息的顶点来说,索引是很小的。这可以节省显存和带宽。而且显卡上的vertex shader只需处理9个顶点而不是24个,当处理由上千个三角形构成的复杂结构时这样做的好处是非常明显的。

每个索引指向9个顶点之一,所以三角形实际上是基于索引绘制的。使用TriangleList时,你需要24个索引用来绘制8个三角形。

工作原理

首先你需要定义独立顶点的数组。这个数组对应图5-7中的结构:

private void InitVertices()
{
    vertices = new VertexPositionColor[9]; 
    vertices[0]= new VertexPositionColor(new Vector3(0, 0, 0), Color.Red); 
    vertices[1]= new VertexPositionColor(new Vector3(1, 0, 0), Color.Green); 
    vertices[2]= new VertexPositionColor(new Vector3(2, 0, 1), Color.Blue); 
    vertices[3]= new VertexPositionColor(new Vector3(0, 1, -1), Color.Orange); 
    vertices[4]= new VertexPositionColor(new Vector3(1, 1, 0), Color.Olive); 
    vertices[5]= new VertexPositionColor(new Vector3(2, 1, 0), Color.Magenta); 
    vertices[6] = new VertexPositionColor(new Vector3(0, 2, 0), Color.Yellow); 
    vertices[7] = new VertexPositionColor(new Vector3(1, 2, 1), Color.Tomato); 
    vertices[8] = new VertexPositionColor(new Vector3(2, 2, -1), Color.Plum); 
    myVertexDeclaration = new VertexDeclaration(device, VertexPositionColor.VertexElements); 
}

现在你需要定义使用哪个顶点创建三角形,每个三角形需要三个索引。

接下来,你将创建索引集合对应这些顶点。首先添加一个数组保存这些索引:

private int[] indices;

下面的方法将索引添加到新定义的数组中:

private void InitIndices()
{
    indices = new int[24]; 
    
    indices[0] = 0; 
    indices[1] = 3; 
    indices[2] = 1; 
    
    indices[3] = 1; 
    indices[4] = 3; 
    indices[5] = 4; 
    
    indices[6] = 1; 
    indices[7] = 4; 
    indices[8] = 5; 
    
    indices[9] = 1; 
    indices[10] = 5; 
    indices[11] = 2; 
    
    indices[12] = 3; 
    indices[13] = 6; 
    indices[14] = 7; 
    
    indices[15] = 3;
    indices[16] = 7; 
    indices[17] = 4; 
    
    indices[18] = 4; 
    indices[19] = 7; 
    indices[20] = 5; 
    
    indices[21] = 5; 
    indices[22] = 7; 
    indices[23] = 8; 
}

我将代码按每三个索引一组分成代码块,每个块对应一个三角形,这个集合也对应图5-7中的灰色数字。例如,左上角的三角形由索引12至14定义,对应顶点3,6,7。

注意:三角形顶点是按顺时针方向定义的,要知道为什么可见教程5-6. 别忘了调用这个方法,例如在Initialize方法中: InitIndices();

定义了顶点和索引,你就可以将这些数据发送到显卡绘制三角形了:

basicEffect.World = Matrix.CreateScale(2.0f); 
basicEffect.View = fpsCam.ViewMatrix; 
basicEffect.Projection = fpsCam.ProjectionMatrix; 
basicEffect.VertexColorEnabled 	= true; 
basicEffect.Begin(); 
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes) 
{
    pass.Begin(); 
    device.VertexDeclaration = myVertexDeclaration; 
    device.DrawUserIndexedPrimitives<VertexPositionColor>(PrimitiveType.TriangleList, vertices, 0, 9, indices, 0, 8); 
    pass.End(); 
} 
basicEffect.End();

除了有两行代码,上面的代码段与教程5-1中的是相同的。第一行代码将世界矩阵缩放到2倍,表明所有顶点位置都乘以2。这样网格会从0扩展到4,关于世界矩阵更多的信息可见教程4-2。

第二第二行代码使用DrawUserIndexedPrimitives方法表示你使用索引数组绘制三角形。你需要声明顶点数组和需要绘制多少个顶点以及从哪个顶点开始绘制。接下来,需要指定包含索引的数组和从哪个索引开始绘制。最后一个参数指定绘制多少个图元,本例中图元是三角形。

注意:这个方法支持教程5-1中讨论过的所有图元类型。例如,可以使用索引绘制 TriangleStrip或LineList,但是,使用索引绘制点是毫无用处的。

何时使用索引?

使用索引不见得都能优化性能,所以在使用索引前,你应该首先不使用索引绘制三角形。

有些情况中使用索引反而会使性能降低。例如,有五个三角形并不共享一个顶点。不使用索引,你需要使用15个顶点,而使用索引,你仍要定义15个顶点,因为这些顶点是独立的!而且还要定义15个索引,每个索引对应一个顶点,这种情况下,传递到显卡的数据反而变多了!

作为一个规律,你可以将(独立顶点的数量) 除以(三角形的数量)。如果没有共享顶点,那么这个值是3,如果有共享,这个值会小于3。这个值越小,,使用索引提升的性能就越多。例如在图5-7中,这个值是9/8 = 1.125,这表示使用索引可以极大地提升性能。

代码

前面已经包含了所有的代码,这里我就不重复写了。


发布时间:2009/5/26 下午5:31:32  阅读次数:5540

2006 - 2024,推荐分辨率 1024*768 以上,推荐浏览器 Chrome、Edge 等现代浏览器,截止 2021 年 12 月 5 日的访问次数:1872 万 9823 站长邮箱

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号