地形的四叉树
对象为一个地形,如下面所示:
这里我们利用四叉树进行对地形进行分割,在此之前我分享两篇关于四叉树的技术博客:四叉树与八叉树和四叉树。
这里我们以地形的三角形数量为条件进行四叉树的构建,如下面图所示:
当然这里仅仅是一个类比,最终进行空间分区可能得到1块,也可能是4块,也可能是16块,也可能是64块,等等等。。。。。其实这取决于你将整个地形分区的递归终止条件,
这里可以参考一下技术博客:四叉树
这里我们以构建地形的三角形数量为递归条件,刚开始整个地形的三角形数量有133128左右,假设递归终止条件为“”三角形数量小于或者等于10000“”
代码如下面所示:
(1)递归构建四叉树
const int MAX_TRIANGLE = 10000;
struct NodeType
{
float positionX, positionZ, width; //本块区域中心位置(忽略Y,因为地形建立在XZ平面)
int trangleCount; //每个节点所属的区域总共的三角形数量
ID3D11Buffer* vertexBuffer;
ID3D11Buffer* indexBuffer;
NodeType* ChildNode[4]; //四个子节点
};
void QuadTreeClass::CreateTreeNode(NodeType* ParentNode, float positionX, float positionZ, float width, ID3D11Device* d3dDevice)
{
int numTriangles, count, VertexCount, index, vertexIndex;
float offsetX, offsetZ;
Vertex* vertices;
unsigned long* indices;
bool result;
//初始化树节点的值
ParentNode->positionX = positionX;
ParentNode->positionZ = positionZ;
ParentNode->width = width;
ParentNode->indexBuffer = NULL;
ParentNode->vertexBuffer = NULL;
ParentNode->trangleCount = 0;
ParentNode->ChildNode[0] = NULL;
ParentNode->ChildNode[1] = NULL;
ParentNode->ChildNode[2] = NULL;
ParentNode->ChildNode[3] = NULL;
//计算在这个节点里三角形的数量
numTriangles = CountTriangles(positionX, positionZ, width);
//如果这个节点三角形数量为0
if (numTriangles == 0)
{
return;
}
//如果这个节点存在太多的三角形,则划分为四个同等大小的子树节点
else if (numTriangles > MAX_TRIANGLE)
{
for (int i = 0; i < 4; ++i)
{
//求出偏移量
offsetX = (((i % 2) < 1) ? -1.0f : 1.0f) * (width / 4.0f);
offsetZ = (((i % 4) < 2) ? -1.0f : 1.0f) * (width / 4.0f);
//利用偏移量计算子节点的中心点,进而计算子节点的三角形数量
count = CountTriangles((positionX + offsetX), (positionZ + offsetZ), width / 2.0f);
//如果子节点三角形数量大于0,则创建该子节点,并且继续递归下去
if (count > 0)
{
//给该子节点分配内存
ParentNode->ChildNode[i] = new NodeType;
//把该子节点当作根节点,递归创建树的节点
CreateTreeNode(ParentNode->ChildNode[i], (positionX + offsetX), (positionZ + offsetZ), width / 2.0f, d3dDevice);
}
}
//退出函数
return;
}
//如果该节点所属区域的三角形数量既不为0也不大于MAX_TRIANGLE,则直接为该节点分配顶点缓存和索引缓存
else if (numTriangles <= MAX_TRIANGLE)
{
ParentNode->trangleCount = numTriangles;
//计算顶点的数量
VertexCount = 3 * numTriangles;
//创建顶点数据数组
vertices = new Vertex[VertexCount];
if (!vertices)
{
return;
}
//创建索引数据数组
indices = new unsigned long[VertexCount];
if (!indices)
{
return;
}
//初始化index
index = 0;
//遍历在顶点列表的所有的三角形(每三个顶点为一个三角形)
//比如三角形0就是mVertexList的顶点0,1,2构成的三角形
for (int i = 0; i < mTriangleCount; ++i)
{
//确认这个三角形是否在节点内
result = IsTriangleContained(i, positionX, positionZ, width);
if (result)
{
//计算三角形在顶点列表mVertexList的索引开始
vertexIndex = 3 * i;
//从顶点列表mVertexList获取顶点属性
vertices[index].pos = mVertexList[vertexIndex]->pos;
vertices[index].normal = mVertexList[vertexIndex]->normal;
vertices[index].Tex = mVertexList[vertexIndex]->Tex;
indices[index] = index;
index++;
vertexIndex++;
vertices[index].pos = mVertexList[vertexIndex]->pos;
vertices[index].normal = mVertexList[vertexIndex]->normal;
vertices[index].Tex = mVertexList[vertexIndex]->Tex;
indices[index] = index;
index++;
vertexIndex++;
vertices[index].pos = mVertexList[vertexIndex]->pos;
vertices[index].normal = mVertexList[vertexIndex]->normal;
vertices[index].Tex = mVertexList[vertexIndex]->Tex;
indices[index] = index;
index++;
}
}
//第一,填充(顶点)缓存形容结构体和子资源数据结构体,并创建顶点缓存
D3D11_BUFFER_DESC vertexBufferDesc;
vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT; //静态缓存
vertexBufferDesc.ByteWidth = sizeof(Vertex) * VertexCount;
vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
vertexBufferDesc.CPUAccessFlags = 0;
vertexBufferDesc.MiscFlags = 0;
vertexBufferDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA vertexData;
vertexData.pSysMem = vertices;
vertexData.SysMemPitch = 0;
vertexData.SysMemSlicePitch = 0;
if (FAILED(d3dDevice->CreateBuffer(&vertexBufferDesc, &vertexData, &ParentNode->vertexBuffer)))
{
MessageBox(NULL, L"dfsfds", L"dsadsf", MB_OK);
}
//第二,填充(索引)缓存形容结构体和子资源数据结构体,并创建索引缓存
D3D11_BUFFER_DESC indexBufferDesc;
indexBufferDesc.Usage = D3D11_USAGE_DEFAULT; //静态缓存
indexBufferDesc.ByteWidth = sizeof(unsigned long) * VertexCount;
indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
indexBufferDesc.CPUAccessFlags = 0;
indexBufferDesc.MiscFlags = 0;
indexBufferDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA indexData;
indexData.pSysMem = indices;
indexData.SysMemPitch = 0;
indexData.SysMemSlicePitch = 0;
if (FAILED(d3dDevice->CreateBuffer(&indexBufferDesc, &indexData, &ParentNode->indexBuffer)))
{
MessageBox(NULL, L"dfsfds", L"dsadsf", MB_OK);
}
//释放顶点数组和索引数组(这时数据已经载入缓存,不需要这些数组了)
delete[] vertices;
vertices = NULL;
delete[]indices;
indices = NULL;
return;
}
}
如上面代码所示,我们仅仅会在最底一层的树节点,也就是叶子进行顶点缓存和索引缓存的构建,这点我就不解释了。
上面说过整个地形三角形数量为133128,以“节点的三角形数量小于或者10000”为递归终止条件,则构建的四叉树如下面所示:
得注意上面的图是个举例,构建的节点的三角形数量不可能完完全全跟上面一样,仅供参考。
(2)运用FrustumCulling技术,递归渲染地形四叉树的节点(FrustumCulling,参考技术博客D3D11教程三十七之FrustumCulling(视截体裁剪)上半节教程)
void QuadTreeClass::RenderNode(NodeType* node, FrustumClass* frustum, ID3D11DeviceContext* deviceContext, TerrainShaderClass* TerrainShader)
{
bool result;
int count, indexCount;
unsigned int stride, offset;
//通过核对地形构建的立方体来核对地形是否在视截体内
result = frustum->CheckCube(node->positionX, 0.0f, node->positionZ,(node->width / 2.0f));
//这个地形跟视截体无交点,则退出递归
if (!result)
{
return;
}
//初始化count
count = 0;
//如果这个地形跟视截体有交点,则递归渲染子树节点
for (int i = 0; i < 4; ++i)
{
if (node->ChildNode[i] != NULL)
{
++count;
RenderNode(node->ChildNode[i], frustum, deviceContext, TerrainShader);
}
}
//如果存在子节点,则无需进行下面设置设置顶点缓存和索引缓存的步骤,因为按照程序的功能,顶点缓存和索引缓存仅仅在最低的树节点进行构建。
if (count != 0)
{
return;
}
//把顶点数据和索引数据送入3D渲染流水线
stride = sizeof(Vertex);
offset = 0;
//设置顶点缓存,IASetVertexBuffers和IAGetVertexBuffers坑死人,注意Get和Set
deviceContext->IASetVertexBuffers(0, 1, &node->vertexBuffer, &stride, &offset);
//设置索引缓存
deviceContext->IASetIndexBuffer(node->indexBuffer, DXGI_FORMAT_R32_UINT,0);
//设置拓扑
deviceContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
//求出索引数量
indexCount = node->trangleCount * 3;
//用TerrainShader渲染地形,这里只要是用于设置索引数量
TerrainShader->RenderShader(deviceContext, indexCount);
//增加可能渲染的三角形数量
mDrawCount += node->trangleCount;
}
程序运行图:
(1)当可视区域为整个地形时:
(2)当可视区域为地形的一部分时: