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.
---------------------------------------------------------------
// 根据地形row和column计算顶点数, 顶点索引数和三角形个数
// 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编程中的基本模型的构造过程.
相关推荐
Introduction to 3D Game Programming with DirectX 11 源码-上
Introduction to 3D Game Programming with DirectX 11 源码-下
Introduction to 3D Game Programming with DirectX 11 源码-中
基本的Direct3D编程技术,比如初始化、定义3D几何体、放置摄像机、创建顶点/像素/ 几何着色器、光照、纹理映射、混合和模板。第III部分主要是运用Direct3D实现一些 有趣的技术和特殊效果,比如使用网格、地形渲染、...
经典的DX11龙书,作者依然是Frank Luna,非常值得一看
这是龙书第二版 《 Introduction to 3D Game Programming with DirectX 9.0c: A Shader Approach 》 源码。
Introduction to 3D Game Programming with DirectX 9.0
with an emphasis on game development, using Direct3D 12. It teaches the fundamentals of Direct3D and shader programming, after which the reader will be prepared to go on and learn more advanced ...
Introduction to 3D Game Programming with DirectX 11-12,龙书11,龙书12,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除。
<<Introduction to 3D Game Programming with DirectX >>9 的源码
Advanced 3D Game Programming With Directx 9.0 With Code.rar
Introduction to 3D Game Programming with DirectX 9.01 Introduction to 3D Game Programming with DirectX 9.01 Introduction to 3D Game Programming with DirectX 9.01 Introduction to 3D Game Programming ...
源码 Introduction to 3D Game Programming with Directx12
Features: +Provides an introduction to programming interactive computer graphics, with an emphasis on game development using DirectX 11 +Covers new Direct3D 11 features +Includes companion DVD with ...
Introduction to 3D Game Programming with DirectX 11英文原版 无水印
Introduction to 3D Game Programming with DirectX 9.0c Shader Approach 源代码 This book presents an introduction to programming interactive computer graphics, with an emphasis on game development, ...
Introduction to 3D Game Programming with DirectX 12 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
this is the source code of sample programs in "Introduction to 3D Game Programming with DirectX 9.0