XNA学习笔记5-顶点绘制

上一节讨论到了model的组成,一个模型使用无数个ModelMeshPart组成,通过bone将这么messpart按照树形结构组合在一起。[align=center][img]http://dl.iteye.com/upload/attachment/359648/ba4541c4-f472-36ad-a043-2adb6b055ef4.png[/img][/align] Primitives (图元)是可以被XNA绘制的最基本的对象,最常被使用的图元是三角形。你想定义一个3D几何结构并绘制到屏幕上,这个结构可以是由三角形,线或点组成。ModelContent中存储几何结构的是一个个MeshContent,该对象中存储了其几何结构。我个人认为,model的几何组成最终是有IndexBuffer这个索引集合和vertextBuffer共同组成。因此,除了可以利用其他软件,通过内容管道处理加载得到model,形成3D图形之外,同样可以在程序中,自己通过Device的DrawUserPrimitives(刻画图元),直接往设备中输入3D结构所需的数据,刻画在屏幕上,本节就主要讲述一下通过代码来描绘3D图形。
[color=blue][b]VertexPositionColor[/b][/color]。定义顶点的坐标和颜色,将它们存储在一个VertexPositionColor数组中,然后利用DrawUserPrimitives方法进行绘制。
    vertices = new VertexPositionColor[3]; 
vertices[0] = new VertexPositionColor(new Vector3(-5, 1, 1), Color.Red);
vertices[1] = new VertexPositionColor(new Vector3(-5, 5, 1), Color.Green);
vertices[2] = new VertexPositionColor(new Vector3(-3, 1, 1), Color.Blue);
DrawUserPrimitives的第一个参数是PrimitiveType的枚举类型,用来表示如何进行刻画这些点。[list]
[*]TriangleList:要绘制的每个三角形的每个顶点。
[*]TriangleStrip:相对于TriangleList可以节省相邻三角形公用的顶点,只需要n+2长度的数组。但是,当使用TriangleStrip以逆时针方向定义三角形时,不可能遵循剔除规则。要解决这个问题,对于TriangleStrip 来说剔除有些特别,必须按照顺时针、逆时针交替循环的方式定义。
[*]LineList:用于绘制多条线段。
[*]TriangleFan:用扇形结构绘制多个三角形,适合所有三角形公用同一个顶点的情况。
[/list] [color=blue][b]VertexPositionTexture[/b][/color]。指定顶点坐标和对应纹理的位置,存储在VertexPositionTexture的数组中,并通过DrawUserPrimitives方法进行绘制。
    vertices = new VertexPositionTexture[9]; 
int i = 0;
vertices[i++] = new VertexPositionTexture(
new Vector3(4, 1, -5), new Vector2(0.25f, 0.5f));
vertices[i++] = new VertexPositionTexture(
new Vector3(7, 5, -5), new Vector2(0.5f, 0));
vertices[i++] = new VertexPositionTexture(
new Vector3(10, 1, -5), new Vector2(1, 1));
myVertexDeclaration=new VertexDeclaration(
device, VertexPositionTexture.VertexElements);
.......
basicEffect.Begin();
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Begin();
device.VertexDeclaration=myVertexDeclaration;
device.DrawUserPrimitives<VertexPositionTexture>(
PrimitiveType.TriangleList, vertices, 0, 3);
pass.End();
}
basicEffect.End();
除了三角形的绘制模式以外,还有纹理取样方式。当指定纹理坐标时,要记住纹理的左上为坐标(0,0),右上为(1,0)。这意味着第二个坐标值表示垂直位置,所以右下坐标为(1,1)。但这并没有限定纹理坐标的值必须在[0,1]范围内,例如设定为(1.5f, –0.5f)也可以。在这种情况中,你需要通过设置U和V(U是第一个纹理坐标,V是第二个)的纹理模式让XNA知道如何处理这样的坐标。

device.SamplerStates[0].AddressU = TextureAddressMode.Clamp;
device.SamplerStates[0].AddressV = TextureAddressMode.Clamp;
TextureAddressMode有多种取值:[list]
[*]Clamp。这个寻址模式让所有的纹理坐标截取到[0,1]范围内,所有小于0的坐标截取到0,大于1的截取到1。
[*]Wrap。显卡会从坐标加上或减去1直到坐标仍回到[0,1]范围。
[*]Mirror。当坐标值小于0时,将值不断地以比他大的最小整数做轴对称变换;当坐标值大于1时,将值不断地以比他小的最大整数做轴对称变换。直到坐标值在[0,1]的范围。
[*]MirrorOnce。这个模式将[-1,1] 区域内的纹理坐标镜像到[0,1]区间,而所有超出[-1,1]区间的坐标会截取到–1(小于–1的值)或1(大于1的值)。
[/list] [align=center][img]http://dl.iteye.com/upload/attachment/360788/c3d3a8c6-f9dc-366c-a450-8c813551d9f8.png[/img][img]http://dl.iteye.com/upload/attachment/360790/402eaa1f-0fd7-395d-98a1-8a81106f5ac0.png[/img][/align]
[color=blue][b]使用索引移除冗余顶点[/b][/color]。通常绘制图形的数组中,有许多冗余顶点数据,在GPU与CPU之间传递,很浪费时间。好的办法是将独立顶点存储在数组中并将这个数组传递到显卡。然后在绘制图形数组中,存储顶点数组的索引,这可以节省显存和带宽。
/*indices为索引数组*/
device.DrawUserIndexedPrimitives<VertexPositionColor>(
PrimitiveType.TriangleList, vertices, 0, 9, indices, 0, 8);
使用索引不见得都能优化性能,所以在使用索引前,你应该首先不使用索引绘制三角形。有些情况中使用索引反而会使性能降低。例如,有五个三角形并不共享一个顶点。不使用索引,你需要使用15个顶点,而使用索引,你仍要定义15个顶点,因为这些顶点是独立的!而且还要定义15个索引,每个索引对应一个顶点,这种情况下,传递到显卡的数据反而变多了!作为一个规律,你可以将(独立顶点的数量) 除以(三角形的数量)。如果没有共享顶点,那么这个值是3,如果有共享,这个值会小于3。这个值越小,,使用索引提升的性能就越多,表示使用索引可以极大地提升性能。
[color=blue][b]DrawPrimitives[/b][/color]每次当你调用DrawUserPrimitives方法时,顶点都会从系统内存传递到显卡中。通常,大部分数据没有变化,这意味着每帧重复传递了相同的数据。今天的显卡有容量很大而且很快的显存,所以你可以将顶点数据存储在显存中加速程序。从显存中将顶点数据传递到显卡速度要快得多,因为这些数据只在同一板卡的不同芯片间传输。同样索引数据也能获得加速。通过创建顶点数组的VertexBuffer,你可以将顶点数据复制到显存中。将顶点存储到显存后,就可以调用DrawPrimitives方法 (代替DrawUserPrimitives方法),这个方法从更快的显存中获取顶点。

vertBuffer = new VertexBuffer(device,
VertexPositionTexture.SizeInBytes * vertices.Length,
BufferUsage.WriteOnly);
vertBuffer.SetData(vertices, 0, vertices.Length);
........
device.Vertices[0].SetSource(vertBuffer, 0,
VertexPositionTexture.SizeInBytes);
device.DrawPrimitives(PrimitiveType.TriangleList, 0, 2);
VertexBuffer的构造函数可以在最后一个参数中指定一些有用的可选择的标志,显卡的驱动会使用这个标志决定哪种内存是最快的。下面是可以使用的BufferUsages:
[list]
[*]BufferUsage. None:允许从VertexBuffer中读写。
[*]BufferUsage. Points:表示VertexBuffer中的顶点数据是用来绘制点和精灵的。这个与性能无关。
[*]BufferUsage. WriteOnly:不从VertexBuffer读取数据。当使用VertexBuffer时,可以将顶点数据放在快得多的显存中。这样,当你想处理顶点数据时,比起将顶点数据在系统内存中存储一个副本,调用VertexBuffers的GetData方法往往更好。
[/list] 如果你还想将索引存储在显卡中,你应在创建VertexBuffer之后再创建一个IndexBuffer,创建IndexBuffer时,你需要指定索引的类型,类型可以是ints或shorts,还要指定索引的多少。一些低端显卡只支持16-bit的索引,如果你使用32-bit的整数型索引会出错。要解决这个问题你应将索引存储在一个short数组中,指定创建一个short类型的索引缓冲。如果你的数组包含不超过32,768的索引,你应该使用shorts而不是ints。这样索引缓冲可以节省一半内存。

indexBuffer = new IndexBuffer(device, typeof(int),
indices.Length, BufferUsage.WriteOnly);
indexBuffer.SetData<int>(indices);
//.....
device.Vertices[0].SetSource(
vertBuffer, 0, VertexPositionTexture.SizeInBytes);
device.Indices = indexBuffer;
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 5, 0, 2);
如果你计划频繁更新顶点数据,应该使用DynamicVertexBuffer而不是VertexBuffer。 这会让数据不是存储在最快的显存中,而是更容易处理的某些内存中。所以,这样做会让性能有一点降低,但在VertexBuffer中频繁地改变数据会让性能得到极大地提高。
因为当你想频繁更新数据时才会使用DynamicVertexBuffer,所以你会经常使用SetData 方法。因此DynamicVertexBuffer的SetData方法还接受一个额外的参数SetDataOptions。这个参数让你可以指定一些选项可以提高程序的速度。默认情况下,当你想覆盖显存中的内容时,因为不支持同时读写,显卡无法从显存中读取数据。当你把大量数据写至内存时会导致显卡的绘制过程中止,这是因为显卡要等待你的复制过程结束。但是,有两个方法可以让你确保显卡不会等待复制操作结束。你可以使用SetDataOptions 参数,下面是可选项:
[list]
[*][color=blue]SetDataOptions. None[/color]:这个选项可以完全控制覆盖VertexBuffer 哪一部分。但是,如前面解释的那样,这会导致性能降低。如果显卡绘制的数据是从VertexBuffer 之前内容提取的,那么显卡必须停止绘制直到较慢的复制过程结束。
[*][color=blue]SetDataOptions. Discard[/color]:使用这个选项表示你不再需要VertexBuffer的之前的内容。这时数据存储在显存的一个新的位置。在写入过程发生时,显卡还能继续使用老的数据。一旦写入过程完成,显卡可以使用新的数据绘制而抛弃老数据。简而言之,显卡无需等待,但你必须重写所有数据。(在Xbox平台上无法使用这个选项,你只能调用DrawUserIndexedPrimitive绘制频繁更新的顶点。)
[*][color=blue]SetDataOptions . NoOverwrite[/color]:这个选项很强大但比较危险。你必须保证正在覆盖的VertexBuffer部分没有被渲染过程使用。这样,你可以覆盖VertexBuffer 的特定部位,此时显卡无需等待复制过程结束,因为你正在覆盖的部分不参与绘制过程。这比使用Discard选项快,因为你无需保留在内存中保留一块新的部分。
[/list]下面,讨论通过顶点绘制的一些其他的话题。
[color=blue][b]开启Backface Culling(背面剔除)[/b][/color]
你定义三角形顶点的方式告知XNA你想让哪一面朝向相机。当绘制一个实心对象时,人们可以清楚地说出三角形的哪个面在物体的内部还是外部。要绘制必须的三角形,你可以要求XNA只绘制朝向相机的那个面,其他三角形在物体的内部被前面隐藏!但是对计算机来说事情不是那么简单。对你定义的每个三角形,你需要说明三角形是在外面还是里面,这可以通过沿逆时针方向或顺时针方向定义三角形的顶点做到这点。当定义顶点时,你需要考虑相机的位置。要绘制三角形,顶点的顺序必须以相机看来按顺时针方向递增。你可以想象一条从相机出发指向三角形中心的射线,如图下所示。如果顶点的顺序(从相机看过来!)是绕着这个中心点顺时针旋转的(左图),XNA就认为这个三角形是面向相机的,这样,三角形才会被绘制。[align=center][img]http://dl.iteye.com/upload/attachment/361149/0766824a-d721-3e29-a96b-2be251410fc7.png[/img][/align] 下图的左图显示了一根从相机出发的射线并与两个三角形相交,圆点表示射线和前面的三角形的交点。当你沿着前面的三角形的顶点增加的顺序(从0到1到2)前进时,会发现绕着圆点做顺时针旋转,这时XNA会绘制这个三角形。现在看一下射线与后面的三角形的交点,当你沿着顶点增加的顺序(从6到7到8)前进时,会绕逆时针旋转,这时XNA会将这个三角形剔除,这样处理很好,因为这个三角形位于立方体的后方,被前表面隐藏了。图5-12的右图显示了同样的立方体,但旋转了180度,这样前表面和后表面就互换了位置,相机仍位于页面的同一侧。现在如果顺着顶点6到7到8,将绕视线做顺时针旋转,这次,显卡会绘制这个三角形并剔除另一个!这正是我们想要的结果。通过这种方式,显卡知道应该剔除哪些三角形,剔除那些不朝向相机的三角形可以极大地提高程序的性能,这也是默认激活的。[align=center][img]http://dl.iteye.com/upload/attachment/361151/039df841-5d5f-3f5b-a01f-6bc25db9d6f9.png[/img][/align] 虽然当绘制由实心表面组成的对象时使用剔除可以带来极大地好处,但有时你也需要将提出关闭。例如,你想创建一个只由两个三角形组成的长方形墙,从外部看效果不错,但当你进入到建筑物内部,这面墙会被剔除,导致你可以看穿这堵墙!简单的方法是使用下列代码将剔除关闭:
device.RenderState.CullMode = CullMode.None; 

[color=blue][b]计算顶点缓冲中所有顶点的法线[/b][/color]。
当绘制自定义的结构时,你会发现光照不正确。这是因为你没有指定正确的法线向量,显卡要求每个顶点都有法线信息,这样它才可以决定每个三角形获得多少光照。如果每个顶点只被一个三角形使用,你只需找到三角形的法线向量(换句话说,这个向量垂直于三角形)并将这个向量作为三个顶点的法线向量。但是在一个结构中,所有顶点被几个三角形共享。要获取平滑的效果,每个顶点需要存储周围三角形所有法线的平均值。
一般情况通过叉乘计算三角形的法线,步骤如下:[list]
[*]对于结构中的每个三角形,计算法线向量。
[*]将这个向量添加到三角形的三个顶点的法线中。对所有三角形进行这个操作后,执行以下操作:
[*]归一化结构中的每个顶点的法线向量。
[/list]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值