本文由zhangbaochong原创,转载请注明出处http://www.cnblogs.com/zhangbaochong/p/5573970.html
前面实现简单地形的教程,我们只是绘制了一个网格,这一次我们来学习一下几种基本几何体的绘制,包括平面网格、立方体、圆柱和球体等。
原来在GeometryGenerator类中只给出了CreateGrid一个方法来绘制网格,现在让我们添加其他方法绘制一些几何体。
为了方便绘制几何体方法的调用,GeometryGenerator类我们使用了单例模式。很简单,将构造函数设为private,添加一个GetInstance函数如下:
//单例模式
static GeometryGenerator* GetInstance()
{
static GeometryGenerator instance;
return &instance;
}
这只是实现单例模式的一种方法,还有几种实现单例模式的方法就不一一说明了。
1.基本几何体绘制方法
下面介绍几种常见几何体的绘制方法(代码均参考dx11龙书)。
1.1网格
网格可以说是最常见同时也是最重要的,像实现地形水面等都离不开网格。生成一个网格首先要给出网格的宽和高,以及在宽和高上划分的格子数。
看龙书中给出的一张图片就明白了:
由此,顶点的坐标就很容易生成了。
顶点索引的计算关键是推导出一个用于求构成第i行,第j列的顶点处右下方两个三角形的顶点索引的通用公式。
对顶点缓存中的任意一点A,如果该点位于地形中的第i行、第j列的话,那么该点在顶点缓存中所对应的位置应该就是i*m+j(m为每行的顶点数)。如果A点在索引缓存中的位置为k的话,那么A点为起始点构成的三角形ABC中,B、C顶点在顶点缓存中的位置就为(i+1)x m+j和i x m+(j+1)。且B点索引值为k+1,C点索引值为k+2.这样。这样,公式就可以推导为如下:
三角形ABC=【i*每行顶点数+j,i*每行顶点数+(j+1),(i+1)*行顶点数+j】
三角形CBD=【(i+1)*每行顶点数+j,i*每行顶点数+(j+1),(i+1)*行顶点数+(j+1)】
void GeometryGenerator::CreateGrid(float width, float height, UINT m, UINT n, MeshData &mesh)
{
mesh.vertices.clear();
mesh.indices.clear();
//每行顶点数、每列顶点数
UINT nVertsRow = m + ;
UINT nVertsCol = n + ;
//起始x、z坐标
float oX = -width * 0.5f;
float oZ = height * 0.5f;
//每一格坐标变化
float dx = width / m;
float dz = height / n; //顶点总数量:nVertsRow * nVertsCol
mesh.vertices.resize(nVertsRow * nVertsCol); //逐个添加顶点
for (UINT i = ; i < nVertsCol; ++i)
{
float tmpZ = oZ - dz * i;
for (UINT j = ; j < nVertsRow; ++j)
{
UINT index = nVertsRow * i + j;
mesh.vertices[index].pos.x = oX + dx * j;
mesh.vertices[index].pos.y = .f;
mesh.vertices[index].pos.z = tmpZ; mesh.vertices[index].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[index].tangent = XMFLOAT3(.f, .f, .f); mesh.vertices[index].tex = XMFLOAT2(dx*i, dx*j);
}
} //总格子数量:m * n
//因此总索引数量: 6 * m * n
UINT nIndices = m * n * ;
mesh.indices.resize(nIndices);
UINT tmp = ;
for (UINT i = ; i < n; ++i)
{
for (UINT j = ; j < m; ++j)
{
mesh.indices[tmp] = i * nVertsRow + j;
mesh.indices[tmp + ] = i * nVertsRow + j + ;
mesh.indices[tmp + ] = (i + ) * nVertsRow + j;
mesh.indices[tmp + ] = i * nVertsRow + j + ;
mesh.indices[tmp + ] = (i + ) * nVertsRow + j + ;
mesh.indices[tmp + ] = (i + ) * nVertsRow + j; tmp += ;
}
}
}
1.2立方体
立方体的绘制就很简单了,一个立方体只需要提供三维方向上的长度。有一点与之前绘制彩色立方体时不一样的是,我们这里创建立方体用到24个顶点(每个面4个),而之前彩色立方体只用到了8个顶点(每个顶点被3个面共享)。这是因为在后面学习过程中我们需要顶点的法线坐标,而一个顶点相对于其连接的3个面来说,法线完全不同,因此无法共享顶点。
void GeometryGenerator::CreateBox(float width, float height, float depth, MeshData &mesh)
{
mesh.vertices.clear();
mesh.indices.clear(); //一共24个顶点(每面4个)
mesh.vertices.resize();
//一共36个索引(每面6个)
mesh.indices.resize(); float halfW = width * 0.5f;
float halfH = height * 0.5f;
float halfD = depth * 0.5f; //眼睛面向z轴正方向
//构建顶点
//前面
mesh.vertices[].pos = XMFLOAT3(-halfW, -halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, -halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
//左侧面
mesh.vertices[].pos = XMFLOAT3(-halfW, -halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, -halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, -.f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
//背面
mesh.vertices[].pos = XMFLOAT3(halfW, -halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, -halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(-.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
//右侧面
mesh.vertices[].pos = XMFLOAT3(halfW, -halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, -halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
//上面
mesh.vertices[].pos = XMFLOAT3(-halfW, halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
//底面
mesh.vertices[].pos = XMFLOAT3(-halfW, -halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, -.f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(-halfW, -halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, -.f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, -halfH, -halfD);
mesh.vertices[].normal = XMFLOAT3(.f, -.f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f);
mesh.vertices[].pos = XMFLOAT3(halfW, -halfH, halfD);
mesh.vertices[].normal = XMFLOAT3(.f, -.f, .f);
mesh.vertices[].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[].tex = XMFLOAT2(.f, .f); //构建索引
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ; mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ; mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ; mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ; mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ; mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
mesh.indices[] = ;
}
1.3圆柱
为了构建一个圆柱,需要提供如下信息:圆柱的上口半径(topRadius),下口半径(bottomRadius),高度(height)。此外,为了指定圆柱的精细度,还需要指定两个参数,一个为没高度方向上平均划分的个数(stack),另一个为沿圆周方向等分的个数(slice)。
可以根据龙书中给出的图理解一下:
void GeometryGenerator::CreateCylinder(float topRadius, float bottomRadius, float height, int slice, int stack, MeshData &mesh)
{
mesh.vertices.clear();
mesh.indices.clear(); //从上到下每个stack半径变化量:dRadius
float dRadius = (bottomRadius - topRadius) / stack;
//每个stack高度:dHeight
float dHeight = height / stack; //每个圆周上顶点数量:slice+1
int vertsPerRow = slice + ;
//顶点行数:stack+1
int nRows = stack + ; //总顶点数
int nVerts = vertsPerRow * nRows;
//总索引数
int nIndices = slice * stack * ; mesh.vertices.resize(nVerts);
mesh.indices.resize(nIndices); //顶部Y坐标
float topY = height * 0.5f; for (int i = ; i < nRows; ++i)
{
float tmpY = topY - dHeight * i;
float tmpRadius = topRadius + i * dRadius; for (int j = ; j < vertsPerRow; ++j)
{
float theta = XM_2PI * j / slice;
int index = i * vertsPerRow + j;
mesh.vertices[index].pos = XMFLOAT3(tmpRadius*cos(theta), tmpY, tmpRadius*sin(theta));
}
} UINT tmp();
for (int i = ; i < stack; ++i)
{
for (int j = ; j < slice; ++j)
{
mesh.indices[tmp] = i * vertsPerRow + j;
mesh.indices[tmp + ] = (i + ) * vertsPerRow + j + ;
mesh.indices[tmp + ] = (i + ) * vertsPerRow + j;
mesh.indices[tmp + ] = i * vertsPerRow + j;
mesh.indices[tmp + ] = i * vertsPerRow + j + ;
mesh.indices[tmp + ] = (i + ) * vertsPerRow + j + ; tmp += ;
}
}
}
1.4球
绘制球基本参数只有一个半径,但是同圆柱一样为了指定精细程度也要给出stack和slice两个参数,这里slice是从上极点沿球面到下极点的180度角等分。具体绘制可以看代码理解:
void GeometryGenerator::CreateSphere(float radius, int slice, int stack, MeshData &mesh)
{ mesh.vertices.clear();
mesh.indices.clear(); int vertsPerRow = slice + ;
int nRows = stack - ; int nVerts = vertsPerRow * nRows + ;
int nIndices = (nRows - )*slice * + slice * ; mesh.vertices.resize(nVerts);
mesh.indices.resize(nIndices); for (int i = ; i <= nRows; ++i)
{
float phy = XM_PI * i / stack;
float tmpRadius = radius * sin(phy);
for (int j = ; j < vertsPerRow; ++j)
{
float theta = XM_2PI * j / slice;
UINT index = (i - )*vertsPerRow + j; float x = tmpRadius*cos(theta);
float y = radius*cos(phy);
float z = tmpRadius*sin(theta); //位置坐标
mesh.vertices[index].pos = XMFLOAT3(x, y, z);
//法线
XMVECTOR N = XMVectorSet(x, y, z, .f);
XMStoreFloat3(&mesh.vertices[index].normal, XMVector3Normalize(N));
//切线
XMVECTOR T = XMVectorSet(-sin(theta), .f, cos(theta), .f);
XMStoreFloat3(&mesh.vertices[index].tangent, XMVector3Normalize(T));
//纹理坐标
mesh.vertices[index].tex = XMFLOAT2(j*.f / slice, i*.f / stack);
}
} int size = vertsPerRow * nRows;
//添加顶部和底部两个顶点信息
mesh.vertices[size].pos = XMFLOAT3(.f, radius, .f);
mesh.vertices[size].normal = XMFLOAT3(.f, .f, .f);
mesh.vertices[size].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[size].tex = XMFLOAT2(.f, .f); mesh.vertices[size + ].pos = XMFLOAT3(.f, -radius, .f);
mesh.vertices[size + ].normal = XMFLOAT3(.f, -.f, .f);
mesh.vertices[size + ].tangent = XMFLOAT3(.f, .f, .f);
mesh.vertices[size + ].tex = XMFLOAT2(.f, .f); UINT tmp();
int start1 = ;
int start2 = mesh.vertices.size() - vertsPerRow - ;
int top = size;
int bottom = size + ;
for (int i = ; i < slice; ++i)
{
mesh.indices[tmp] = top;
mesh.indices[tmp + ] = start1 + i + ;
mesh.indices[tmp + ] = start1 + i; tmp += ;
} for (int i = ; i < slice; ++i)
{
mesh.indices[tmp] = bottom;
mesh.indices[tmp + ] = start2 + i;
mesh.indices[tmp + ] = start2 + i + ; tmp += ;
} for (int i = ; i < nRows - ; ++i)
{
for (int j = ; j < slice; ++j)
{
mesh.indices[tmp] = i * vertsPerRow + j;
mesh.indices[tmp + ] = (i + ) * vertsPerRow + j + ;
mesh.indices[tmp + ] = (i + ) * vertsPerRow + j;
mesh.indices[tmp + ] = i * vertsPerRow + j;
mesh.indices[tmp + ] = i * vertsPerRow + j + ;
mesh.indices[tmp + ] = (i + ) * vertsPerRow + j + ; tmp += ;
}
}
}
2.场景绘制
2.1最终效果
2.2多个几何体共享顶点索引缓冲区
我们一共绘制了四种几何体:网格、立方体、球和圆柱,它们公用了一个顶点和索引缓冲区。这样我们就需要在其中找出每个几何体对应的位置。
为了在顶点、索引缓冲区中找到一个物体对应的位置,我们使用三个参数:该物体在顶点缓冲区中的起始位置(VStart),索引缓冲区中的起始位置(IStart),以及索引总数(totalIndices)。看龙书中的一幅图就很容易理解了:
2.3设为线框模式绘制
在Render函数中创建一个栅格化状态ID3D11RasterizerState ,状态描述中FillMode设为D3D11_FILL_WIREFRAME即可
//设置为线框绘制模式
D3D11_RASTERIZER_DESC rsDesc;
ZeroMemory(&rsDesc, sizeof(rsDesc));
rsDesc.CullMode = D3D11_CULL_BACK;
rsDesc.DepthClipEnable = true;
//D3D11_FILL_WIREFRAME以线框模式绘制,D3D11_FILL_SOLID是以实体模式绘制
rsDesc.FillMode = D3D11_FILL_WIREFRAME;
rsDesc.FrontCounterClockwise = false;
ID3D11RasterizerState *rsState(nullptr);
m_pd3dDevice->CreateRasterizerState(&rsDesc, &rsState);
m_pImmediateContext->RSSetState(rsState);
源码下载:http://files.cnblogs.com/files/zhangbaochong/GeoDrawDemo.zip