地形渲染之四叉树(QuadTree)

本文介绍了如何利用四叉树对大型地形进行优化,以减少渲染时的计算量。通过设定递归终止条件,将地形分割成多个小块,当三角形数量超过阈值时进一步细分。在渲染时,使用FrustumCulling技术只渲染可见的四叉树节点,提高效率。示例代码展示了四叉树构建和渲染过程,同时提供了不同可视区域下的渲染效果。
摘要由CSDN通过智能技术生成

地形的四叉树

对象为一个地形,如下面所示:

这里我们利用四叉树进行对地形进行分割,在此之前我分享两篇关于四叉树的技术博客:四叉树与八叉树四叉树

这里我们以地形的三角形数量为条件进行四叉树的构建,如下面图所示:

当然这里仅仅是一个类比,最终进行空间分区可能得到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)当可视区域为地形的一部分时:

源码链接

D3D11_QuadTree-游戏开发代码类资源-CSDN下载

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
在MATLAB中,你可以使用四叉树法(Quadtree)来进行建模。四叉树是一种将二维空间划分为四个象限的树结构,常用于空间索引和区域分割。 以下是一个简单的示例,展示如何使用四叉树法对二维数据进行建模: ```matlab % 创建一个包含二维数据的矩阵 data = rand(100, 2); % 定义四叉树的初始区域边界 x_min = min(data(:, 1)); x_max = max(data(:, 1)); y_min = min(data(:, 2)); y_max = max(data(:, 2)); boundary = [x_min, x_max, y_min, y_max]; % 创建四叉树对象 quadtree = Quadtree(boundary); % 将数据插入四叉树 for i = 1:size(data, 1) point = data(i, :); quadtree.insert(point); end % 可视化四叉树 figure; hold on; quadtree.plot(); % 绘制原始数据点 plot(data(:, 1), data(:, 2), 'ro'); axis equal; ``` 在这个示例中,我们首先创建了一个包含随机二维数据的矩阵。然后,定义了四叉树的初始区域边界,即数据的最小和最大边界。接下来,我们创建了一个四叉树对象,并使用`insert`方法将数据点插入到四叉树中。最后,使用`plot`方法可视化四叉树,并绘制原始数据点。 需要注意的是,上述示例中的`Quadtree`是一个自定义的类,你需要自行实现或者使用第三方库来创建该类。四叉树的实现可以根据具体需求进行调整和优化,可以包括查找、遍历、剪枝等操作。 希望这个示例能对你理解如何在MATLAB中使用四叉树进行建模提供帮助。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值