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:
- BufferUsage. None:允许从VertexBuffer中读写。
- BufferUsage. Points:表示VertexBuffer中的顶点数据是用来绘制点和精灵的。这个与性能无关。
- BufferUsage. WriteOnly:不从VertexBuffer读取数据。当使用VertexBuffer时,可以将顶点数据放在快得多的显存中。这样,当你想处理顶点数据时,比起将顶点数据在系统内存中存储一个副本,调用VertexBuffers的GetData方法往往更好。
你也可以组合这些标志,用 | 分隔表示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 阅读次数:6380