当前位置: 代码迷 >> 综合 >> Game Programming with DirectX -- 07[基本3D模型构造]
  详细解决方案

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

热度:89   发布时间:2023-12-06 04:28:23.0

 

Game Programming with DirectX -- 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编程中的基本模型的构造过程.

  相关解决方案