索引顶点

索引顶点
2008-04-22 19:39
"你能不能快点写呀?",一个做游戏策划的朋友在网上问我。没想到我写的这篇教程还是有人关心的嘛,呵呵,嘴里虽然不说什么,但心里还是有一点小小的得意^_^。
  本来打算这次讲摄象机的,但发现还有一个很重要的渲染方法没有讲,那就是 索引 顶点的渲染,这个要是不说的话,以后做到MAX插件导出的时候就会一头雾水了,为什么?因为我就是在还没有搞清楚这个的时候就去做插件了。
  什么是 索引 顶点呢?上次我们讲的渲染是直接把所有 顶点的数据按顺序放到 顶点缓冲中,然后每次取3个点的数据出来渲染成一个三角形,但是大家有没有考虑过,在一个3D模型中一个点可能要同时处于多个三角形面上,如果直接把 顶点数据放进缓冲区,那么重复的 顶点就会占用多余的内存。
  怎么解决这个问题呢?
  答案就是我们今天的主题-- 索引 顶点
  我们把 顶点的具体数据和代表三角形的3个 顶点索引分开, 顶点数据还是放到 顶点缓冲区中,方法同上回。
  今天我们这样用5个 顶点做例子:

  // 顶点原始数据
  CUSTOMVERTEX g_Vertices[] =
  {
     { -1.0f,-1.0f, 0.0f, 0xffff0000, }, // A红色
     { 1.0f,-1.0f, 0.0f, 0xff0000ff, }, // B兰色
     { 1.0f, 0.0f, 0.0f, 0xff00ff00, }, // C绿色
     { 0.0f, 1.0f, 0.0f, 0xffff00ff, }, // D洋红
     { -1.0f, 0.0f, 0.0f, 0xffffffff, }, // E白色
  };

  这五个点具体到图上是这样的:

  

  那索引数据放到哪里呢?我们需要用到一个叫做索引缓冲的东西,看程序:

  LPDIRECT3DINDEXBUFFER8 g_pIB = NULL; // 索引缓冲区

  // 创建索引缓冲区
  if( FAILED( g_pd3dDevice->CreateIndexBuffer( 6*sizeof(WORD),
                         0,
                         D3DFMT_INDEX16,
                         D3DPOOL_DEFAULT,
                         &g_pIB ) ) )
  {
    return E_FAIL;
  }


  第一个参数表示缓冲区大小,我们给出了6个点。
  第二个参数暂时不管,填0。
  第三个参数是索引的格式,我们用16位存储索引值(也就是说最大65535个顶点)。
  第四个参数表示通过什么方式创建,用缺省值就好了
  第五个参数返回缓冲区指针。

  创建的方法和上次讲的顶点缓冲大同小异,创建好了后,我们填入数据:

  // 填充顶点缓冲区
  WORD* pi;
  if( FAILED( g_pIB->Lock( 0, 12, (BYTE**)&pi, 0 ) ) )
    return E_FAIL;
  pi[0] = 0; // A
  pi[1] = 1; // B
  pi[2] = 2; // C
  pi[3] = 0; // A
  pi[4] = 3; // D
  pi[5] = 4; // E
  g_pIB->Unlock();

  好,六个点正好是两个三角形,按我们添的数据,这两个三角形应该是这样的:

  

  现在我们就有了两个缓冲区,一个顶点缓冲区,放了5个原始顶点的数据;一个索引缓冲区,放了6个索引值,代表了两个三角形的面。
  OK,准备工作就绪了,最重要的是怎么把它们渲染出来,看程序:

  // --渲染图形--

  // 指定渲染源
  g_pd3dDevice->SetStreamSource( 0, g_pVB, sizeof(CUSTOMVERTEX) );
  g_pd3dDevice->SetIndices(g_pIB, 0);

  // 指定自定义的FVF
  g_pd3dDevice->SetVertexShader( D3DFVF_CUSTOMVERTEX );

  // 渲染
  g_pd3dDevice->DrawIndexedPrimitive( D3DPT_TRIANGLELIST, 0, 5, 0, 2);

  和上回的渲染部分比较一下,很明显,多了一个SetIndeces()的步骤。还有就是最后的渲染也由DrawPrimitive()变成了  DrawIndexedPrimivive()。
  DrawIndexedPrimivive()一共有五个参数,我们来简要说明一下(具体查dx8帮助)

  D3DPRIMITIVETYPE Type, // 渲染类型(点、线、面等)
  UINT MinIndex, // 顶点缓冲区中顶点的起始值,一般为0
  UINT NumVertices, // 顶点缓冲区中的顶点数量
  UINT StartIndex, // 索引缓冲区中索引的起始值
  UINT PrimitiveCount // 要渲染图形的数量

  具体程序运行的效果如下(万幸和设想的正好是一样):

无论是2D或是3D绘制,顶点的使用是一个重要的课题,顶点的使用与座标息息相关,这也就是为何之前一直在谈论座标系统的原因。

顶点的记录方式有许多种,不同的绘图目的应搭配不同的顶点资料结构,这边介绍最简单的几个立体物件顶点记录方式。

假设有一个正立方体,其中心位于原点,则可以如下图先定出顶点的座标:



纸上作业与程式规划所不同的是,如何使用这些顶点来绘制一个立方体,基本上必须以四个顶点为一个单位,使用绘制多边型的函式来绘制一个四边形,然后以较方便的方式选择四个顶点,通常会使用回圈,但为了能使用回圈,顶点资料结构必须有可重复索引的性质,在这边介绍两种规划方式。

其中一个规划方式是使用6*4=24个元素的阵列,每个面使用掉四个顶点,如下图所示:



如此就可以使用回圈取出顶点资讯,这个方法的好处是简单,但由于顶点会有重复,因而会耗用大量的记忆体,对于复杂图形并不适用。

可以使用顶点索引来解决顶点重复的问题,首先必须先将顶点编号,如下图所示:



通常为了具有判别法向量的作用,顶点编号时使用右手定则,以逆时针的顺序来编号同一个面的顶点;顶点编号完毕后,使用一个顶点索引阵列来记录每个面所使用到的顶点编号,如下所示:

int v_ord[][] = {{0, 1, 2, 3}, {0, 7, 6, 1}, {4, 5, 6, 7},
                 {2, 5, 4, 3}, {0, 3, 4, 7}, {1, 6, 5, 2}};



使用索引阵列的好处是减少记忆体使用量,虽然额外使用了一个索引阵列,但对于顶点越多时,记忆体的减少使用会更显著,但缺点就是必须额外耗用一些运算是处理顶点资讯。

下面是使用顶点索引阵列来绘制正立方体的Java Applet程式,您可以参考我们是如何处理顶点资讯的:

  • Vetex.java
import java.awt.*;
import java.applet.*;
import java.awt.event.*;

class Point3D {
public int x, y, z;

public Point3D() {
x = y = z = 0;
}

public Point3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}

public class Vetex extends Applet {
public void paint(Graphics g) {
final int r = 100;

// 索引array
int v_ord[][] =
{{0, 1, 2, 3}, {0, 7, 6, 1}, {4, 5, 6, 7},
{2, 5, 4, 3}, {0, 3, 4, 7}, {1, 6, 5, 2}};

// 立方体顶点
Point3D[] vetex =
{new Point3D(r, r, r), new Point3D(r, -r, r),
new Point3D(r, -r, -r), new Point3D(r, r, -r),
new Point3D(-r, r, -r), new Point3D(-r, -r, -r),
new Point3D(-r, -r, r), new Point3D(-r, r, r)};

Point3D[] tp = new Point3D[4];

// 视窗中心
final int orgX = (int)getSize().width / 2,
orgY = (int)getSize().height / 2;
final double rd = Math.PI / 180;
double ax, ay;

int[] px = new int[4],
py = new int[4];

int i, j, k;

// 旋转以斜角绘制图形
ax = 30 * rd;
ay = -30 * rd;

setBackground(Color.black);
g.setColor(Color.yellow);

for(i = 0; i < 6; i++) {
for(j = 0; j < 4; j++) {
// 利用索引array取出正确的顶点
tp[j] = vetex[v_ord[i][j]];

// 旋转以斜角绘制图形
px[j] = (int) (tp[j].x*Math.cos(ay)
+ tp[j].z*Math.sin(ay));
py[j] = (int) (tp[j].y*Math.cos(ax)
- (-tp[j].x*Math.sin(ay)
+ tp[j].z*Math.cos(ay)) * Math.sin(ax));
px[j] = px[j] + orgX;
py[j] = -py[j] + orgY;
}
g.drawPolyline(px, py, 4);
}
}
}


如果您使用3D函式库时,通常可以自行选择使用哪一种顶点记录方式,而且包装成物件之后,您也无须亲自处理索引的细节。

 

顶点索引例子
2008-04-22 19:44

一个正方体的顶点索引

CUSTOMVERTEX_CUBE modelDef[] =
{

   { 0.5f, -0.5f, -0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 1.0, 1.0 }, // 0(顶点索引编号)
   { -0.5f, -0.5f, -0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 0.0, 1.0 }, // 1
   { 0.5f, 0.5f, -0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0, 0.0 }, // 2
   { -0.5f, 0.5f, -0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0, 0.0 }, // 3


   { 0.5f, 0.5f, -0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0, 1.0 }, // 4
   { -0.5f, 0.5f, -0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0, 1.0 }, // 5
   { 0.5f, 0.5f, 0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0, 0.0 }, // 6
   { -0.5f, 0.5f, 0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0, 0.0 }, // 7


   { -0.5f, -0.5f, 0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 1.0, 1.0 }, // 8
   { 0.5f, -0.5f, 0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 0.0, 1.0 }, // 9
   { 0.5f, 0.5f, 0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0, 0.0 }, // 10
   { -0.5f, 0.5f, 0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0, 0.0 }, // 11


   { 0.5f, -0.5f, 0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 1.0, 1.0 }, // 12
   { 0.5f, 0.5f, -0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0, 0.0 }, // 13
   { 0.5f, 0.5f, 0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0, 0.0 }, // 14
   { 0.5f, -0.5f, -0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 0.0, 1.0 }, // 15


   { 0.5f, -0.5f, 0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 1.0, 1.0 }, // 16
   { -0.5f, -0.5f, -0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 0.0, 0.0 }, // 17
   { 0.5f, -0.5f, -0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 1.0, 0.0 }, // 18
   { -0.5f, -0.5f, 0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 0.0, 1.0 }, // 19


   { -0.5f, 0.5f, 0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 0.0, 0.0 }, // 20
   { -0.5f, 0.5f, -0.5f, D3DCOLOR_ARGB(255, 255, 255, 255), 1.0, 0.0 }, // 21
   { -0.5f, -0.5f, 0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 0.0, 1.0 }, // 22
   { -0.5f, -0.5f, -0.5f, D3DCOLOR_ARGB(255, 127, 127, 127), 1.0, 1.0 }, // 23
};
WORD indices[] = { 0, 1, 2, 3, 2, 1,          //第一个面(一个面由2个三角形构成)
                                4, 5, 6, 5, 7, 6,          //第二个面
                                8, 9, 10, 8, 10, 11,        //第三个面
                              12, 13, 14, 12, 15, 13,     //第四个面
                              16, 17, 18, 16, 19, 17,     //第五个面
                              20, 21, 22, 21, 23, 22 };   //第六个面

转自:  http://hi.baidu.com/bdruiruili/blog/item/38d3cb2491fdfd36c89559a4.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值