D3D11进阶教程六之四叉树(QuadTree)

原创 2017年01月03日 10:32:07

本节教程是关于利用基于四叉树的空间分区算法和FrustumCulling算法来实现地形的优化,程序的结构如下:






第一,四叉树

前面的教程渲染的对象为一个地形,如下面所示:




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

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



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




第二,碰见问题和总结。


(1)自学directx11以来,碰上过这个问题两三次了,并且犯了这个错,也难以Debug出来,这个错误就是把IASetVertexBuffers误写成了IAGetVertexBuffers, 把IASetIndexBuffer误写成IAGetIndexBuffer等等。

(2)看到很多搞实时渲染的前辈都这样总结,在写一个图形引擎时不要一开始就考虑优化,你不应该过早的优化自己的图形引擎直到你碰上了性能问题。 其实这点换算到构架的设计也许也是正确的,在写一个图形引擎时不要一开始就过度的设计,碰上问题才重构。也许得拿自己的引擎写一个RPG游戏,从游戏的需求来完善自己的引擎是比较好的。



源代码链接如下:
http://download.csdn.net/detail/qq_29523119/9727342


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

HTML5实现3D和2D可视化QuadTree四叉树碰撞检测

QuadTree四叉树顾名思义就是树状的数据结构,其每个节点有四个孩子节点,可将二维平面递归分割子区域。QuadTree常用于空间数据库索引,3D的椎体可见区域裁剪,甚至图片分析处理,我们今天介绍的是...

2D空间中使用Quadtree四叉树进行碰撞检测优化

很多游戏中都需要使用碰撞检测算法检测两个物体的碰撞,但通常这些碰撞检测算法都很耗时很容易拖慢游戏的速度。这里我们学习一下使用四叉树来对碰撞检测进行优化,优化的根本是碰撞检测时跳过那些明显离得很远的物体...

Silverlight.XNA(C#)跨平台3D游戏研发手记:(二)四叉树遍历与人工智能A*算法在SLG移动路径范围测算中的应用

除战斗范围设定外,说到SLG中最有趣而经典的算法莫过于角色可移动范围的测算与寻路。当前成熟的SLG商业游戏中,以《火焰纹章》、《高级大战争》等系列新作为代表,它们在传统的基础上将可移动范围进行了拓展,...

D3D11_QuadTree

  • 2017-01-03 10:29
  • 706KB
  • 下载

HT for Web可视化QuadTree四叉树碰撞检测

QuadTree四叉树顾名思义就是树状的数据结构,其每个节点有四个孩子节点,可将二维平面递归分割子区域。QuadTree常用于空间数据库索引,3D的椎体可见区域裁剪,甚至图片分析处理,我们今天介绍的是...

基于四叉树(QuadTree)的LOD地形实现

实现基于四叉树的LOD地形时,我遇到的主要问题是如何修补地形裂缝。        本段我将描述使用LOD地形的优势,我实现LOD地形的思路,实现LOD地形核心模块的详细过程,以及修补地形裂缝的思路。...
  • Gypsyy
  • Gypsyy
  • 2012-08-20 23:45
  • 8761

基于四叉树(QuadTree)的LOD地形实现

实现基于四叉树的LOD地形时,我遇到的主要问题是如何修补地形裂缝。        本段我将描述使用LOD地形的优势,我实现LOD地形的思路,实现LOD地形核心模块的详细过程,以及修补地形裂缝的思路。 ...

四叉树和八叉树剔除Quadtree and Octree Culling Alternative

Introduction Traditionally computer graphic applications - particularly applications requiring re...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)