关闭

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

102人阅读 评论(0) 收藏 举报
分类:

本节教程是关于利用基于四叉树的空间分区算法和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游戏,从游戏的需求来完善自己的引擎是比较好的。



源代码链接如下:


0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:34951次
    • 积分:1271
    • 等级:
    • 排名:千里之外
    • 原创:93篇
    • 转载:42篇
    • 译文:0篇
    • 评论:3条
    最新评论