第七章 顶点缓冲
一、索引缓冲
3D物体中的三角形经常会有许多共用顶点。如果用两个三角形组合为一个长方形,有两个点被重复使用,当要表现一个更精细更复杂的模型的时候,重复的顶点数将会变得很大。下图的立方体,仅有八个顶点,但是当用三角形列表示它的时候,所有的点都被重复使用。
索引缓冲就是一块保存了顶点数据索引的缓冲。缓冲中的索引为32位或16位的整数。比如,当使用索引0,1,6来绘制一个三角形时,会通过索引映射到相应的顶点来渲染图像。
以下为创建一个索引缓冲的代码示例:
int numberVerts = 8;
short[] indices = {
0,1,2, // Front Face
1,3,2, // Front Face
4,5,6, // Back Face
6,5,7, // Back Face
0,5,4, // Top Face
0,2,5, // Top Face
1,6,7, // Bottom Face
1,7,3, // Bottom Face
0,6,1, // Left Face
4,6,0, // Left Face
2,3,7, // Right Face
5,2,7 // Right Face
};
Mesh mesh = new Mesh(numberVerts * 3, numberVerts, MeshFlags.Managed,
CustomVertex.PositionColored.Format, device);
using(VertexBuffer vb = mesh.VertexBuffer)
{
GraphicsStream data = vb.Lock(0, 0, LockFlags.None);
data.Write(new CustomVertex.PositionColored(-1.0f, 1.0f, 1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(-1.0f, -1.0f, 1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(1.0f, 1.0f, 1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(1.0f, -1.0f, 1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(-1.0f, 1.0f, -1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(1.0f, 1.0f, -1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(-1.0f, -1.0f, -1.0f, 0x00ff00ff));
data.Write(new CustomVertex.PositionColored(1.0f, -1.0f, -1.0f, 0x00ff00ff));
vb.Unlock();
}
using (IndexBuffer ib = mesh.IndexBuffer)
{
ib.SetData(indices, 0, LockFlags.None);
}
顶点和索引缓存有相似的地方,一个顶点缓存是一块连续的存储了顶点数据的内存。同样的,一个索引缓存是一块连续的存储了索引数据的内存。使用顶点和索引缓存保存数据是因为它们能被放置在显存中。渲染显存中的数据要比渲染系统内存中的数据快的多。
二、索引缓冲实例
下面为一使用索引缓冲的例子,显示一个旋转的正方体;
相关代码如下:
首先,定义对象变量;
private
VertexBuffer vb = null;
private IndexBuffer ib = null;
在图形的初始化函数中实例化对象:
public
void InitializeGraphics()
{
……
vb = new VertexBuffer(typeof(CustomVertex.PositionColored), 8, device, Usage.Dynamic | Usage.WriteOnly, CustomVertex.PositionColored.Format, Pool.Default);
vb.Created += new EventHandler(this.OnVertexBufferCreate);
OnVertexBufferCreate(vb, null);
ib = new IndexBuffer(typeof(short),indices.Length,device,Usage.WriteOnly,Pool.Default);
ib.Created += new EventHandler(OnIndexBufferCreated);
this.OnIndexBufferCreated(ib,null);
}
对于IndexBuffer()构造函数,相关参数说明如下:
indices.Length —— 分配给缓存的字节大小。假如想得到一个能存储8个顶点的顶点缓存,那么就要在顶点结构中设置这个参数为 8 * sizeof ( Vertex ) 。
Usage指定关于怎样使用缓存的额外信息。这个值可以是0,没有标记,或者是下面标记的组合: ——
Dynamic:设置这个参数可以使缓存是动态的。
Points:这个参数指定缓存存储原始点。
Softwareprocessing:使用软件顶点处理
Writeonly:指定应用程序只能写缓存。它允许驱动程序分配最适合的内存地址作为写缓存。
Pool —— 缓存放置在哪一个内存池中
不使用Dynamic
参数创建的缓存被叫做静态缓存。静态缓存通常被放置在显存中,在其中的数据能被很好的处理。然而,对于静态缓存,从中读取和写入数据是很慢的,因为访问显存是很慢的。所以一般用静态缓存存储不需要被经常改变的数据。比如
,
可以存储地形和建筑物。静态缓存应该在应用程序初始化的时候就被填充好,而不是在运行时才做。
动态缓存通常被放在
AGP
内存中,这种内存中的数据能被很快的更新。处理动态缓存中的数据不会比处理静态缓存中的数据快,因为这些数据必须在渲染前被转移到显存中,动态缓存的优点是能够被更迅速地更新。因此,假如需要经常更新缓存中的数据,那么就应该使用动态缓存。比如
,
对于粒子系统来说
,
一般应用动态缓存。
下面看看灌入数据的代码:
private
void OnIndexBufferCreated(object sender, EventArgs e)
{
IndexBuffer buffer = (IndexBuffer)sender;
buffer.SetData(indices,0,LockFlags.None);
}
另外在渲染函数中:
protected
override void OnPaint(System.Windows.Forms.PaintEventArgs e)
{
……
device.SetStreamSource(0, vb, 0);
device.Indices = ib;
angle += 0.05f;
device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI,angle / (float)Math.PI * 2.0f,angle / (float)Math.PI / 4.0f) ;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,8,0,indices.Length / 3);
……
.
}
注意,对于顶点缓冲来说,使用的是SetStreamSource()方法,而对于索引缓冲来说,则是设置Indices属性。因为在同一时间内只可能使用同一种类型的索引缓冲。
另外,注意把以前的DrawPrimitives改为了DrawIndexedPrimitives。来看看这个方法的参数如下:
public void DrawIndexedPrimitives(PrimitiveType primitiveType,int baseVertex ,int minVertexIndex,int numVertices, int startIndex, int primCount);
PrimitiveType:表示要绘制的图元类型。
baseVertex:表示从索引缓冲起点到要使用的第一个顶点索引的偏移量。
MinVertexIndex:这几个顶点中最小的顶点索引值。
numVertices:是所要使用的顶点数量。
startIndex:从数组中的哪一个位置开始读取顶点。
primCount:是要绘制的图元数量。
当创建完索引缓冲后,如果以后需要获得相关信息,可以使用IndexBuffer.Description属性来得到;
三、深度缓冲
深度缓冲不是用来存储图像数据,而是用来记录像素的深度信息。以便确定哪一个像素最后被绘制出来。因为在3D场景中,经常会发生一个物体把将另一个物体的一部分遮住了。为了使Direct3D能确定物体的前后关系并正确的绘制出来,需要使用深度缓冲。
它有时也称z-buffer或w-buffer,为每一个像素计算深度值并进行深度测试。通过深度测试可以比较得出哪个像素离摄相机更近并将它画出来。这样就可以只绘制最靠近摄相机的像素,被遮住的像素就不会被制。
深度缓冲的格式决定着深度测试的精确程度。一个24位的深度缓冲比16位的深度缓冲更精确。通常,应用程序在24位深度缓冲下就能工作的很好,但是Direct3D也同时支持32位的深度缓冲。
四、深度缓冲示例
打开前面绘制的立方体,在场景中再加入两个立方体,同时,为了观察方便,对加入的立方体进行了平移;代码如下:
device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI,angle / (float)Math.PI * 2.0f,angle / (float)Math.PI / 4.0f) ;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,8,0,indices.Length / 3);
device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI,angle / (float)Math.PI * 2.0f,angle / (float)Math.PI / 4.0f)
* Matrix.Translation(0,1,5);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,8,0,indices.Length / 3);
device.Transform.World = Matrix.RotationYawPitchRoll(angle / (float)Math.PI,angle / (float)Math.PI * 2.0f,angle / (float)Math.PI / 4.0f)
* Matrix.Translation(0,-1,-5);
device.DrawIndexedPrimitives(PrimitiveType.TriangleList,0,0,8,0,indices.Length / 3);
执行程序,注意这是没有加入深入缓冲的效果:
下面加入深度缓冲,加入很简单;只需在创建设备时,将改变一下参数的属性:
PresentParameters presentParams = new PresentParameters();
presentParams.EnableAutoDepthStencil = true;
presentParams.AutoDepthStencilFormat = DepthFormat.D16;
presentParams.SwapEffect = SwapEffect.Discard;
Format current = Manager.Adapters[0].CurrentDisplayMode.Format;
其中EnableAutoDepthStencil表示是否使用深度缓冲。AutoDepthStencilFormat定义精度,常用枚举格式及含义如下:
D32——表示32位深度缓冲
D24S8——表示24位深度缓冲并保留8位模版缓冲(stencil buffer)
D24X8——表示24位深度缓冲
D24X4S4——表示24位深度缓冲并保留4位模版缓冲
D16——表示16位深度缓冲
现在如果执行程序,还将无法得到正确的结果,在渲染函数中将代码:
device.Clear(ClearFlags.Target , Color.Black , 1.0f, 0);
更改为:
device.Clear(ClearFlags.Target | ClearFlags.ZBuffer, Color.Black , 1.0f, 0);
现在,再执行程序,结果如下:
现在,三个立方体有明显的层次感了。