5.4 使用顶点缓冲和索引缓冲将顶点和索引保存在显存中

问题

每次当你调用device. DrawUserPrimitives方法时,顶点都会从系统内存传递到显卡中。通常,大部分数据没有变化,这意味着每帧重复传递了相同的数据。今天的显卡有容量很大而且很快的显存,所以你可以将顶点数据存储在显存中加速程序。从显存中将顶点数据传递到显卡速度要快得多,因为这些数据只在同一板卡的不同芯片间传输。同样索引数据也能获得加速。

解决方案

通过创建顶点数组的VertexBuffer,你可以将顶点数据复制到显存中。将顶点存储到显存后,就可以调用DrawPrimitives方法 (代替DrawUserPrimitives方法),这个方法从更快的显存中获取顶点。

注意:如果顶点几乎无需更新,那么这个方法可以极大地提高性能。但当处理被称为动态顶点数据(dynamic vertex data)时,这意味着顶点数据更新频繁,你应使用DynamicVertexBuffer,这会在下一个教程介绍。

工作原理

你可以一次性地将数据传递到显卡并把它们存储在显存中,而不是在每次调用DrawUserPrimitives方法时将顶点数组传递到显卡。要实现这个方法,你可以在创建了顶点数组后将它加载到一个VertexBuffer(顶点缓冲)中。在项目中添加VertexBuffer变量:

VertexBuffer vertBuffer; 

然后使用下面的方法生成六个顶点显示两个带纹理的三角形(可见教程5-1)。在这个方法最后,这六个顶点会加载到VertexBuffer并传递到显存中:

private void InitVertices()
{
    myVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements); 
    VertexPositionTexture[] vertices = new VertexPositionTexture[6]; 
    int i = 0; 
    
    vertices[i++] = new VertexPositionTexture(new Vector3(-5.0f, new Vector2(-0.5f,1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(-2.5f, new Vector2(0.5f, -1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(0, -3, new Vector2(1.5f, 1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(0, -3, -1), new Vector2(-0.5f, 1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(2.5f, 5, -1), new Vector2(0.5f, 1.5f));
    vertices[i++] = new VertexPositionTexture(new Vector3(5.0f, -3, -1), new Vector2(1.5f, 1.5f)); 
    vertBuffer = new VertexBuffer(device, VertexPositionTexture.SizeInBytes * vertices.Length, BufferUsage.WriteOnly);
    vertBuffer.SetData(vertices, 0, vertices.Length); 
} 

当定义了所有顶点后,你需创建一个VertexBuffer。这个VertexBuffer将传递到显卡的保留内存中,由于这个原因,你需要指定对设备的链接和顶点占据的字节数量,这个数量等于 (一个顶点的字节大小)乘以(顶点数量)。

注意:你当然不想每帧都创建一个VertexBuffer,请确保只调用这行代码一次。如果你想覆盖VertexBuffer的数据,不要删除它并重建一个,而是应该使用SetData方法将新数据加载到当前已经存在的VertexBuffer中。只要当你需要增加顶点的数量时才有必要删除VertexBuffer 并重建一个。

创建了VertexBuffer后,你就可以使用SetData方法将顶点数据传递到内存中。显然,SetData方法需要从顶点数组中复制数据。它的一个重载方法可以只写入VertexBuffer的部分数据,在这种情况下,你需指定从哪个顶点开始以及复制多少个顶点。

注意:如果你要频繁地更新VertexBuffer的内容,你应该使用DynamicVertexBufferDynamicVertexBuffer的SetData方法更为强大。

在显卡中存储了顶点数据后就可以绘制三角形了。你可以使用教程5-1中的代码,但有两处变化。

你将使用DrawPrimitive方法表明你将从VertexBuffer中获取数据进行绘制。在这之前首先要激活VertexBuffer。

device.RenderState.CullMode = CullMode.None; 
basicEffect.World = Matrix.Identity; 
basicEffect.View = fpsCam.ViewMatrix; 
basicEffect.Projection = fpsCam.ProjectionMatrix; 
basicEffect.Texture = myTexture; 
basicEffect.TextureEnabled = true; 
basicEffect.Begin(); 
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
    pass.Begin(); 
    device.VertexDeclaration = myVertexDeclaration; 
    device.Vertices[0].SetSource(vertBuffer, 0, VertexPositionTexture.SizeInBytes); 
    device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2); pass.End(); 
}
basicEffect.End(); 

在pass中,与以前一样需要将VertexDeclaration传递给设备表明顶点存储的数据类型、下一行代码设置VertexBuffer。对每个顶点来说,你可以在不同的VertexBuffers中存储不同的信息。本例中,你只使用了一个VertexBuffer,因此指定索引为0。使用SetSource方法,表明你将激活vertBuffer作为顶点的源。你还要指定第一个顶点的开始位置和一个顶点占据多少个字节(这样显卡才能将字节流裁切成独立的顶点)。激活VertexBuffer后,从第一个顶点开始使用TriangleList进行绘制,这个VertexBuffer 包含两个三角形的数据。

对性能的考虑:VertexBuffer构造函数

VertexBuffer的构造函数可以在最后一个参数中指定一些有用的可选择的标志,显卡的驱动会使用这个标志决定哪种内存是最快的。下面是可以使用的BufferUsages:

你也可以组合这些标志,用 | 分隔表示OR操作(如果不矛盾,结果是指定选项的AND逻辑)。

IndexBuffer(索引缓冲)

如果你还想将索引存储在显卡中,你应在创建VertexBuffer之后再创建一个IndexBuffer ,所以在项目中添加一个IndexBuffer变量:

IndexBuffer indexBuffer; 

作为一个简单的例子,你将定义两个三角形,因为它们共享一个顶点,所以只需定义5个独立顶点:

private void InitVertices() 
{
    myVertexDeclaration = new VertexDeclaration(device, VertexPositionTexture.VertexElements);
    VertexPositionTexture[] vertices = new VertexPositionTexture[5]; 
    int i = 0; 
    
    vertices[i++] = new VertexPositionTexture(new Vector3(-5.0f, -3, -1), new Vector2(-0.5f, 1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(-2.5f, 5, -1), new Vector2(0.5f, -1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(0, -3, -1), new Vector2(1.5f, 1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(2.5f, 5, -1), new Vector2(0.5f, -1.5f)); 
    vertices[i++] = new VertexPositionTexture(new Vector3(5.0f, -3, -1), new Vector2(-0.5f, 1.5f)); 
    vertBuffer = new VertexBuffer(device, VertexPositionTexture.SizeInBytes * vertices.Length, BufferUsage.WriteOnly); 
    vertBuffer.SetData(vertices, 0, vertices.Length); 
} 

接下来使用InitIndices方法创建索引数组并将它们复制到索引缓冲中:

private void InitIndices() 
{
    int[] indices = new int[6]; 
    int i = 0; 
    indices[i++] = 0; 
    indices[i++] = 1; 
    indices[i++] = 2; 
    
    indices[i++] = 2; 
    indices[i++] = 3; 
    indices[i++] = 4; 
    
    indexBuffer = new IndexBuffer(device, typeof(int), indices.Length, BufferUsage.WriteOnly); 
    indexBuffer.SetData<int>(indices); 
} 

创建IndexBuffer时,你需要指定索引的类型,类型可以是ints或shorts,还要指定索引的多少。

当心:一些低端显卡只支持16-bit的索引,如果你使用32-bit的整数型索引会出错。要解决这个问题你应将索引存储在一个short数组中,指定创建一个short类型的索引缓冲。

技巧:如果你的数组包含不超过32,768的索引,你应该使用shorts而不是ints。这样索引缓冲可以节省一半内存。

别忘了在项目开始调用这个方法。

当进行绘制时,你需要激活VertexBuffer和IndexBuffer,然后调用DrawIndexedPrimitives 方法绘制三角形:

device.RenderState.CullMode = CullMode.None; 
basicEffect.World = Matrix.Identity; 
basicEffect.View = fpsCam.ViewMatrix;
basicEffect.Projection = fpsCam.ProjectionMatrix; 
basicEffect.Texture = myTexture; 
basicEffect.TextureEnabled = true; 
basicEffect.Begin(); 
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
    pass.Begin(); 
    device.VertexDeclaration = myVertexDeclaration; 
    device.Vertices[0].SetSource(vertBuffer, 0, VertexPositionTexture.SizeInBytes); 
    device.Indices = indexBuffer; 
    device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 5, 0, 2); 
    pass.End(); 
}
basicEffect.End(); 

代码

InitVertices,InitIndices和Draw方法的代码前面已经写过了。

程序截图


发布时间:2009/5/27 上午10:18:42  阅读次数:6441

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

沪 ICP 备 18037240 号-1

沪公网安备 31011002002865 号