`
dengbaoleng
  • 浏览: 1128511 次
文章分类
社区版块
存档分类
最新评论

Game Programming with DirectX -- 07[基本3D模型构造]

 
阅读更多

GameProgrammingwithDirectX--07[基本3D模型构造]

第七集 基本3D模型构造

7.1 顶点索引

7.1.1 金字塔模型

game1 project中使用的金字塔是由六个三角形的三角形带组成的, 金字塔一共有5个顶点, 如图7.1左边所示.

图7.1

由5个顶点构成的金字塔的三角形带为图右边的顺序, 0-1-2, 1-2-3, ..., 5-6-7, 其中有3个顶点是重复的.

7.1.2 Indices buffer

实际由5个顶点组成的金字塔在IDirect3DVertexBuffer9表示需要8个顶点, 每多一个顶点, 渲染时计算量就会增加. 顶点索引的作用就是减少顶点的重复定义, 顶点索引使用IDirect3DIndexBuffer9表示, IDirect3DIndexBuffer9中存储的数值是相应的在IDirect3DVertexBuffer9中顶点数组序列号, 金字塔用顶点索引表示如下,

IDirect3DIndexBuffer9 IDirect3DVertexBuffer9

[ 0 ] [ 0.0, 8.0, 0.0 ]

[ 1 ] [ 6.0, 0.0, -6.0 ]

[ 2 ] [ -6.0, 0.0, -6.0 ]

[ 3 ] [ -6.0, 0.0, 6.0 ]

[ 0 ] [ 6.0, 0.0, 6.0 ]

[ 4 ]

[ 1 ]

[ 3 ]

IDirect3DIndexBuffer9是由IDirect3DDevice9接口中的CreateIndexBuffer(...)函数创建的, 创建方式和参数同IDirect3DVertexBuffer9类似, 要注意顶点索引根据实际情况可使用32位的索引(对应数值类型是DWORD), 16位的索引(对应数值类型是WORD).

当使用顶点索引后, 图形绘制的调用函数有点小改动, 如绘制金字塔,

// m_pD3DDev --- pointer of IDirect3DDevice9

// m_pD3DVBuffer --- pointer of IDirect3DVertexBuffer9

// m_pD3DIBuffer --- pointer of IDirect3DIndexBuffer9

m_pD3DDev->SetStreamSource(0, m_pD3DVBuffer, 0, sizeof(MYVERTEX));

m_pD3DDev->SetFVF(D3DFVF_MYVERTEX);

// 绘制图形前要告诉IDirect3DDevice9现在用到的顶点索引

m_pD3DDev->SetIndices(m_pD3DIBuffer);

// 绘制图形的函数由DrawIndexedPrimitive替换DrawPrimitive

m_pD3DDev->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, 0, 0, 5, 0, 6);

具体的解释顶点和顶点索引的关系的可以在DirectX9c SDK中的Rendering from Vertex and Index Buffers主题中找到.

7.1.3 Indices buffer VS vertex buffer

这里我们要注意在光照(或纹理映射)条件下, 由于多个面共用的顶点在各各面的法向量(纹理坐标)不同, 还是需要存储同一顶点的不同法向量(纹理坐标)的多个实例.

例如表示一个简单的立方体模型, 只须8个顶点数值和相应的顶点索引. 实际要考虑物体纹理映射或光照计算, 这时8个顶点是不够的, 如图7.2立方体, 我们计算在光照条件下时, 每个顶点在不同面的法向量不同, 所以每个顶点实际需存储3次, 一个立方体需 3 * 8 = 24 个顶点数值, 如果不考虑顶点索引, 每个面有 6 个顶点数值, 共需 6 * 6 = 36 个顶点数值.

图7.2

game5 project中矩形在有纹理映射条件下无顶点索引时有 36 个顶点数值.

7.2 基本3D几何模型

7.2.1 圆柱模型

圆柱模型的底面和顶都是由三角扇形组成, 简单的侧面是由一三角形带组成, 侧面也可用多层三角形带组成. 像切蛋糕一样, 圆柱被分成块状, 分的越细模型越精确. 可参考图7.3, 在构造时, 模型局部坐标系的中心是圆柱的中心.

7.2.2 圆锥模型

圆锥模型实际就是去掉顶部的圆柱, 侧面三角形带有一共同的顶点坐标. 其实圆柱模型的构造过程可以生成圆台, 棱台和棱锥几何模型.

图7.3

7.2.3 球模型

图7.4左边是两个四棱锥组成的正8面体, 现在从这个正8面体导出球的模型.

通过正8面体内正方形的对角线将正8面体切成4块, 其中的角a为90度; 如果称正8面体为球的话, 正8面体内正方形的四条边围成的就是赤道, 球半径R为正方形的对角线长的一半, 那么正8面体是一个4经度2纬度的球了.

图7.4

现在在切平面上寻找一个点C, 使得三角形ABC为等边三角形, 并且C到中心O的距离是R, 这样就形成图7.4右边的4经度4纬度的球. 如果再寻找点D, E, 使得三角形DAC, ECB为等边三角形, 并且D, E到中心O的距离是R, ... , 按以上方法, 类似C的点越多, 切平面中的图形越接近圆. ------ 球的层次数

现在再增加赤道切平面上的点C', 使得三角形AB'C'为等边三角形, 并且C'到中心O的距离是R, ... ------ 球的分块数

由上的方法, 正8面体就趋向于它的外接球了. 所以, 只要知道球的半径, 层次数, 分块数, 我们就可以构造出球的模型.

图7.5

如图7.5, 以正方形的对角线分别为X, Z轴, 交点为原点建立局部坐标系, B点的坐标是( Rsina * sinb, Rcosa, Rsina * cosb), 使用三角形带(DirectX Graphics推荐)实现,为代码简单, 将ABC, ACD, ADE, AEB看成是四边形ABCA, ACDA, ADEA, AEBA, 底部的也同样. 这样球的顶点数为PartCount * (LayerCount + 1), 图7.5中4经度4纬度的球PartCount = 4, LayerCount = 4, 每个块半切面有(LayerCount + 1) = 5个顶点, 共有PartCount = 4个半块切面, 所以顶点数PartCount * (LayerCount + 1) = 4 * 5 = 20(有6个重复点).

FLOAT fPlaneRad = D3DX_PI / (m_nPart >> 1); // (2PI / PartCount) = b 弧度增量

FLOAT fApeakRad = D3DX_PI / m_nLayer; // (PI / LayerCount) = a 弧度增量

FLOAT fuMap = (FLOAT)(1.0 / m_nPart); // 纹理坐标 U 的增量

FLOAT fvMap = (FLOAT)(1.0 / m_nLayer); // 纹理坐标 V 的增量

FLOAT nx = 0.0;

FLOAT ny = 0.0;

FLOAT nz = 0.0;

FLOAT fPlane = 0.0;

WORD nIndex = 0;

WORD nLayerIndex = 0;

for (INT i = 0; i <= m_nLayer; i++)

{

fPlane = sinf(fApeakRad * i);

ny = cosf(fApeakRad * i);

for (INT j = 0; j < m_nPart; j++)

{

nx = (FLOAT)fPlane * sinf(fPlaneRad * j);

nz = (FLOAT)fPlane * cosf(fPlaneRad * j);

pVertex->x = FLOAT(m_fRadius * nx);

pVertex->y = FLOAT(m_fRadius * ny);

pVertex->z = FLOAT(m_fRadius * nz);

pVertex->nx = nx;

pVertex->ny = ny;

pVertex->nz = nz;

pVertex->tu = FLOAT(fuMap * j);

pVertex->tv = FLOAT(fvMap * i);

pVertex++;

if (i < m_nLayer)

{

*pIndex = nIndex; // A 点索引

pIndex++;

*pIndex = WORD(nIndex + m_nPart); // B 点索引

pIndex++;

nIndex++;

}

}

if (i < m_nLayer)

{

*pIndex = nLayerIndex;

pIndex++;

nLayerIndex += (WORD)m_nPart;

*pIndex = nLayerIndex;

pIndex++;

}

}

代码以层次关系填写顶点值和顶点索引, 图7.5顶点数值为AAAA-BCDE-FGHI-JKLM-NNNN,

(1), 内循环第一次(第一层)索引值为ABACADAE; 内循环第一次结束ABACADAE + AB.

(2), 内循环第二次(第二层)索引值为ABACADAE + AB + BFCGDHEI; 内循环第二次结束ABACADAE + AB + BFCGDHEI + BF.

......

我们发现在层之间跳转时会出现无效的三角形ABB, BBF, 每增加一层就会有多余的2个无效的三角形, 其中最顶层和最底层只有一个.

每层索引为2 * PartCount + 2(注意加2),总共索引2 * (PartCount + 1) * LayerCount = 40, 三角形个数2 * (PartCount + 1) * LayerCount - 2(注意减2) = 38, 其中有6个无效三角形.

7.3 基本地形模型

7.3.1 简单地形模型

最最简单的地形就是四边形平面了(最好是矩形或正方形), 用两个三角形组成. 在复杂一点的地形都是在四边形平面地形基础上加强的.

想象一下家里N年前刚铺木地板时的平整光滑, 经过N年, 平整的木地板有的地方被砸凹了, 有的泡水太长拱上来了...., 总之凹凸不平了.

有高低起伏的地形可仿造家中木地板的演变得到, 先把四边形平面分成很多的小四边形平面(最好是矩形或正方形), 这样地形就需要行数row, 纵数column及小四边形的边长来描述划分的密度了, 图7.6上边是一个4 * 4的平整地形.

图7.6

现在来描述地形的高低信息, 图7.6中, 只要相应的改变各点的高度值, 就可以形成高低起伏的地形了, 高度值可用一个小于1的随机数值 * 最高高度得到(游戏一般是用高度图描述的), 图7.6下边是4 * 4的平整地形通过改变顶点高度值得到的.

用这种方式形成的地形简单快速, 顶点数为(row + 1) * (column + 1), 图7.6中有25个顶点. 每个四边形有两个三角形, 总共三角形个数row * column * 2, 每个三角形有三个顶点, 总共索引row * column * 2 * 3.

如图7.6建立坐标系, 各顶点的顺序按图中的标号, 顶点索引的实现为,

// m_pIndex 为指向顶点索引数组的指针

WORD v1 = 0; // 从第0个顶点开始

WORD v2 = m_nCols + 1; // column + 1 = 5

WORD v3 = v2 + 1; // column + 1 + 1 = 6

for (INT a = 0; a < m_nRows; a++)

{

for (INT b = 0; b < m_nCols; b++)

{

m_pIndex[i] = v1;

m_pIndex[i + 1] = v2;

m_pIndex[i + 2] = v3;

m_pIndex[i + 3] = v1;

m_pIndex[i + 4] = v3;

m_pIndex[i + 5] = v1 + 1;

v1++;

v2++;

v3++;

i += 6;

}

v1++;

v2++;

v3++;

}

7.4 基本3D模型的例子

7.4.1 game6 project代码更新

game6 project中, 用基本地形构造了埃及的金字塔群, 为地形加入了一个地形类CD9Terrain.

---------------------------------------------------------------

// 根据地形rowcolumn计算顶点数, 顶点索引数和三角形个数

// nMaxHeight 为地形最高高度, fSide为小四边形(正方形)的边长

HRESULT CD9Terrain::Init(INT nRows, INT nCols, INT nMaxHeight, FLOAT fSide)

{

SAFERELEASE( m_pD3DIBuffer );

SAFERELEASE( m_pD3DVBuffer );

m_nRows = nRows;

m_nCols = nCols;

m_nMaxHeight = nMaxHeight;

m_nVertices = (nRows + 1) * (nCols + 1);

m_nTriangle = nRows * nCols * 2;

m_nIndices = m_nTriangle * 3;

m_fSide = fSide;

if (SUCCEEDED(InitVertexBuffer()))

{

if (SUCCEEDED(InitIndexBuffer()))

{

return UpdateD3DVertex();

}

}

return E_FAIL;

}

---------------------------------------------------------------

7.4.2 game6 project说明

例子中地形纹理映射和顶点的法向量计算都是前几集的内容.

7.4.3 game7 project代码更新

game7是比较综合的例子 --- 地月系统, 例子中加入了构造圆柱, 圆锥和球的3D模型的类, 其实这些模型DirectX Graphics有专门的实用函数来构造, 这里只是为后面的天空顶技术热热脑子...

---------------------------------------------------------------

// 圆锥构造

HRESULT CD9Cone::UpdateD3DVertex()

{

LPWORD pIndex = NULL;

MYVERTEXTEX* pVertex = NULL;

INT nIndicesSize = m_nIndices * sizeof(WORD);

INT nVerticesSize = m_nVertices * sizeof(MYVERTEXTEX);

FLOAT fRadian = D3DX_PI / (m_nPart >> 1);

FLOAT fTextureMap = FLOAT(1.0 / m_nPart);

FLOAT fHalfH = FLOAT(m_fHeight / 2);

FLOAT fHalfH1 = FLOAT(0 - fHalfH);

FLOAT ny = sinf(atan(m_fRadius / m_fHeight));

FLOAT nx = 0.0;

FLOAT nz = 0.0;

FLOAT x = 0.0;

FLOAT z = 0.0;

FLOAT u = 0.0;

WORD nIndex = 0;

if (SUCCEEDED(m_pD3DIBuffer->Lock(0, nIndicesSize, (LPVOID*)&pIndex, 0)))

{

if (FAILED(m_pD3DVBuffer->Lock(0, nVerticesSize, (LPVOID*)&pVertex, 0)))

{

m_pD3DIBuffer->Unlock();

return E_FAIL;

}

// side

for (INT i = 0; i < m_nPart; i++)

{

nx = sinf(fRadian * i);

nz = cosf(fRadian * i);

x = m_fRadius * nx;

z = m_fRadius * nz;

u = FLOAT(fTextureMap * i);

pVertex->x = 0;

pVertex->y = fHalfH;

pVertex->z = 0;

pVertex->nx = nx;

pVertex->ny = ny;

pVertex->nz = nz;

pVertex->tu = u;

pVertex->tv = 0.0;

pVertex++;

pVertex->x = x;

pVertex->y = fHalfH1;

pVertex->z = z;

pVertex->nx = nx;

pVertex->ny = ny;

pVertex->nz = nz;

pVertex->tu = u;

pVertex->tv = 1.0;

pVertex++;

*pIndex = nIndex;

pIndex++;

nIndex++;

*pIndex = nIndex;

pIndex++;

nIndex++;

*pIndex = nIndex + 1;

pIndex++;

}

*(pIndex - 1) = 1;

// bottom

pVertex->x = 0.0;

pVertex->y = fHalfH1;

pVertex->z = 0.0;

pVertex->nx = 0.0;

pVertex->ny = -1.0;

pVertex->nz = 0.0;

pVertex->tu = 0.5;

pVertex->tv = 0.5;

pVertex++;

for (INT j = 0; j <= m_nPart; j++)

{

nx = sinf(fRadian * j);

nz = cosf(fRadian * j);

pVertex->x = m_fRadius * nx;

pVertex->y = fHalfH1;

pVertex->z = m_fRadius * nz;

pVertex->nx = 0.0;

pVertex->ny = -1.0;

pVertex->nz = 0.0;

pVertex->tu = FLOAT(nx * 0.5 + 0.5);

pVertex->tv = FLOAT(nz * 0.5 + 0.5);

pVertex++;

}

m_pD3DVBuffer->Unlock();

m_pD3DIBuffer->Unlock();

m_bInit = TRUE;

return S_OK;

}

return E_FAIL;

}

---------------------------------------------------------------

7.4.4 game7 project说明

使用IDirect3DDevice9的SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME)函数来查看模型的网格.

第七集 小结

这一集我们学习了要进行DirectX Graphics 3D编程中的基本模型的构造过程.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics