DrawIndexedPrimitive函数的详细解释

为了便于说明,首先假设存在下列的顶点缓冲和索引缓冲
   vb=
   {
    {-1.0, 1.0, 0.0}, index 0
    { 1.0, 1.0, 0.0}, index 1
    { 1.0,-1.0, 0.0}, index 2
    {-1.0,-1.0, 0.0}, index 3

    {-2.0, 2.0, 0.0}, index 4

    { 2.0, 2.0, 0.0}, index 5

   }
   和对应该顶点缓冲的一组索引缓冲ib 
   
   初学D3D,DrawIndexedPrimitive这个函数是个难点,主要是MSDN中的解释不是很明确,这个函数共6个参数,下面对这6个参数进行一下详细的解释。

   参数1:D3DPRIMITIVETYPE type-图元类型
   使用的图元类型,这个比较好理解,D3D只能绘制三种图元-点、线和三角形,其中线和三角形又细分为线列表(LINELIST)、线条带(LINESTRIP)、三角列表(TRIANGLELIST)、三角条带(TRIANGLESTRIP),根据实际需要选择使用某一种图元,这不会影响最终的绘制效果,但理论上讲,同样多边形数目的物体,使用三角形要比使用线绘制效率高(一个三角形=3条线),而条带比列表要节省顶点数量,同样n个顶点,线列表能绘制n/2条线,线条带能绘制n-1条线,同样,三角列表能绘制n/3个三角形,而三角条带能绘制n-2个三角形,不过经过D3D最优化处理后,绘制条带或列表的性能差别并不大。

   参数2:INT BaseVertexIndex-起始顶点索引
   MSDN中的原话是这样:Offset from the start of the vertex buffer to the first vertex.
   “从顶点缓冲的起始位置到第一个顶点的偏移量”,第一个顶点不就是起始位置么?不一定是,DrawIndexedPrimitive这个函数接受从顶点缓冲的任何一个位置开始读入顶点数据,你可以从index0的位置开始读取顶点数据,那这时BaseVertexIndex就是0,也可以从index2的位置开始读取数据,那这时BaseVertexIndex就是2,如此类推。

   参数3:UINT MinVertexIndex-最小顶点索引(这里MSDN应该是个笔误,写成MinIndex了,应该是MinVertexIndex)
   MSDN:Minimum vertex index for vertices used during this call. This is a zero based index relative to BaseVertexIndex.
   本次函数调用中,顶点缓冲中最小的顶点索引。MSDN解释中我认为最关键的一句是“relative to BaseVertexIndex”-“相对与起始顶点”,即MinVertexIndex实际上也是个相对偏移量,例如,起始顶点是index2,MinVertexIndex是0,那么实际是从index2读入数据,起始顶点是index1,MinVertexIndex是1,那么实际也是从index2读入数据,即实际上的第一个顶点位置在BaseVertexIndex+MinVertexIndex处,最终偏移量=BaseVertexIndex+MinVertexIndex,这里很容易产生误解,就是认为MinVertexIndex是指顶点索引的最小值,即总是0,如果这样那MinVertexIndex这个参数就没有意义了。当初MS如果把这形参声明为OffsetRelativeToBaseVertexIndex应该是更明确一点,不会让人产生误解。
   另一个问题就是,理论上这个偏移量如果是负值也可能是有意义的,例如BaseVertexIndex是1,而MinVertexIndex是-1就是指的顶点缓冲中的第一个顶点,这是有意义的,但实际中如果给MinVertexIndex赋负值我没有试验过。一般情况下MinVertexIndex都是0。
  
   参数4:UINT NumVertices-顶点数量
   MSDN:Number of vertices used during this call. The first vertex is located at index: BaseVertexIndex + MinIndex.
   本次调用所使用的顶点数量。MSDN的解释依然(依旧)模糊,第一个顶点位于BaseVertexIndex + MinIndex这个我已经知道啦,还写这个干什么?这里的顶点数量不是指的顶点缓冲中的顶点总数量,如vb有6个顶点,“The first vertex is located at index: BaseVertexIndex + MinIndex”这句话是要告诉我们,计算顶点数量是要从BaseVertexIndex + MinIndex开始的,例如,我要画一条从顶点index4到顶点index5的线段,那么参数应该是这样的:
   BaseVertexIndex=4

   MinVertexIndex=0
   NumVertices=2
   NumVertices是2而不是6,即NumVertices不是顶点总数,而总是你绘制图元实际需要的顶点数,注意这个“实际需要”,指的是你用到了几个顶点,这取决于索引缓冲的绘制这个图形所使用的不同的顶点个数(可能不太好理解,看下面的例子就明白了)。1条线段需要2个顶点,所以就是2,也许这形参声明为NumVerticesUsedForDrawing更明确一些。

   参数5:UINT StartIndex-起始索引
   又是一个Index,如果把这个形参声明为StartIndexOfIndexBuffer,我想就不会头晕了,这个起始索引指的是索引缓冲中的起始索引,也就是指索引缓冲数组的下标(顶点缓冲和索引缓冲都是用数组表示)-ib[StartIndex]。

   参数6:UINT PrimitiveCount-图元数量
   这个没什么难度,有一点要注意,对于绘制的几何图形而言,n-2和n/3指的都是索引的数量而不是顶点的数量,也就是说n是索引缓冲中索引的数量。

   好了,所有参数都明确了,现在终于可以自由运用DrawIndexedPrimitive绘制图形了!

   先等一下,还有问题,怎么说好呢?DrawIndexedPrimitive这个函数依然存在逻辑陷阱,而这个问题的最大问题就是,虽然你写了有问题的代码,但在HAL设备下没有任何错误提示,编译运行没有任何问题,甚至有可能你的绘制结果和你的预期也是一样的,这个问题只有在REF设备下才能被发现。例如下面的例子:

   顶点缓冲如同文章顶部的定义,我要使用vb前4个顶点绘制2个三角形,最后2个顶点绘制1条线段,所以我这样定义索引缓冲

   ib={0,1,2,0,2,3,4,5,},0-5号索引绘制2个三角形,6、7两个索引绘制一条线段

   进行编码,

   DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,4,0,2);//绘制2个三角形

   DrawIndexedPrimitive(D3DPT_LINELIST,4,0,2,6,1);//绘制1条线段

   先看第一条绘制代码,根据索引缓冲的定义可以知道,前6个索引使用了4个顶点(0,1,2,3),所以NumVertices参数为4,起始索引为0,绘制2个三角形,没有任何问题。再看第二条绘制代码,线段的顶点数据是从vb中的第5个顶点开始的,所以起始顶点索引是4,最小顶点索引为0,使用了2个顶点(4和5)所以NumVertices为2,因为索引缓冲中的线段索引是从第7个索引开始的,所以起始索引是6,绘制1条线段,也没有任何问题。

   运行,2个三角形出现了,线段呢?线段怎么没了?显然,第二个条绘制代码不正确,可参数完全是按照规则定义的啊,为什么不正确呢?原因在于,当第一个顶点的位置不是0时,也就是说起始顶点索引+最小顶点索引大于0时,且NumVertices值如果为实际需要的顶点数而不是顶点总数的时候,索引缓冲中对应顶点索引需要减去这个偏移量。回到上面的代码为例,当绘制线段的时候,第一个顶点被移动到了第5个位置(总偏移量为4+0),NumVertices为2是实际需要的顶点数,索引缓冲中的索引从第7(偏移量为6)个开始,对应index4号顶点,而这时index4号顶点对于索引缓冲来说已经不是4了,而是0,同样index5号顶点也不是5而是1,所以需要修改索引缓冲数据。另一种办法是不修改索引缓冲数据,指定总偏移量为0,NumVertices是全部顶点数。正确的代码应该是这样,上面说的有些绕,结合代码就明白了,

修改方法1:

   ib={0,1,2,0,2,3,0,1,}//将以前的4、5修改为0、1

   DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,4,0,2);//绘制2个三角形

   DrawIndexedPrimitive(D3DPT_LINELIST,4,0,2,6,1);//绘制1条线段

修改方法2:

   ib={0,1,2,0,2,3,4,5,}//保持索引缓冲数据不变

   DrawIndexedPrimitive(D3DPT_TRIANGLELIST,0,0,4,0,2);//绘制2个三角形

   DrawIndexedPrimitive(D3DPT_LINELIST,0,0,5,6,1);//NumVertices修改为顶点总数

   上面那段错误代码HAL设备中是无法被触发的(池类型为托管池),在REF设备中会触发内存读写违例。为何HAL无法触发原因不明,可能数据写入显存后硬件会忽略这个错误,必定REF是用来调试的而HAL不是。

 http://blog.csdn.net/bluekitty/article/details/6068093

posted @ 2013-03-21 16:59  冷夜 - 网游编程技术  阅读(409)  评论(0编辑  收藏  举报