3 设置网格数的大小_DX12绘制篇:习题(3)

今天我们来完成第七章的课后习题。虽然只有3道题,但是涉及的知识还是挺多的,并且第三题的加载骷髅网格,是后期都会用到的基础场景,但后面章节并没有说如何加载,所以那题非常重要。

第1题:修改"Shapes"例程,以ProceduralGeometry::CreateGeoSphere方法替换程序中的ProceduralGeometry::CreateSphere方法,并分别尝试使用0、1、2、3这4种细分等级。

这题主要考察GeoSphere的创建函数中numSubdivisions参数的使用。细分越高,模型越平滑,细分的具体算法我们先不管,函数定义如下,可以看到第一个参数是球的半径,第二个参数是细分级别,我们只需在BuildGeometry函数中创建Geosphere的时候给具体数值即可。

MeshData ProceduralGeometry::CreateGeosphere(float radius, uint32 numSubdivisions)
{
        ......
}

void ShapesApp::BuildGeometry()
{
	......
	ProceduralGeometry::MeshData sphere = proceGeo.CreateGeosphere(0.5f, 3);
        ......
}

编译运行,效果如下。

80e2df931f9ccbf9b0ea636c4a6290d3.png

第2题:修改"Shapes"程序,使用16个根常量取代描述表,以此设置每个物体的世界矩阵

首先我们修改构建根签名代码,使用根常量绑定0号寄存器(即world矩阵数据),并设置16个32位数据,而1号寄存器还是不变(即viewProj矩阵),使用描述符表绑定。

void ShapesApp::BuildRootSignature()
{
	CD3DX12_DESCRIPTOR_RANGE cbvTable1;
	cbvTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, //描述符类型
		1, //描述符表数量
		1);//描述符所绑定的寄存器槽号

	//根参数可以是描述符表、根描述符、根常量
	CD3DX12_ROOT_PARAMETER slotRootParameter[2];
	slotRootParameter[0].InitAsConstants(16, 0);
	slotRootParameter[1].InitAsDescriptorTable(1, &cbvTable1);

	//根签名由一组根参数构成
	CD3DX12_ROOT_SIGNATURE_DESC rootSig(2, //根参数的数量
		slotRootParameter, //根参数指针
		0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
	//用单个寄存器槽来创建一个根签名,该槽位指向一个仅含有单个常量缓冲区的描述符区域
	ComPtr<ID3DBlob> serializedRootSig = nullptr;
	ComPtr<ID3DBlob> errorBlob = nullptr;
	HRESULT hr = D3D12SerializeRootSignature(&rootSig, D3D_ROOT_SIGNATURE_VERSION_1, &serializedRootSig, &errorBlob);

	if (errorBlob != nullptr)
	{
		OutputDebugStringA((char*)errorBlob->GetBufferPointer());
	}
	ThrowIfFailed(hr);

	ThrowIfFailed(d3dDevice->CreateRootSignature(0,
		serializedRootSig->GetBufferPointer(),
		serializedRootSig->GetBufferSize(),
		IID_PPV_ARGS(&rootSignature)));
}

然后将Update函数里关于world矩阵更新的代码移到DrawRenderItems绘制函数中(方便观察)。可以看到我们并不需要用CopyData函数将数据拷贝到GPU缓存,直接设置根常量即可将数据传至GPU,即SetGraphicsRoot32BitConstants函数,由于根常量数组就这一个,所以偏移为0。

void ShapesApp::DrawRenderItems()
{
	//将智能指针数组转换成普通指针数组
	std::vector<RenderItem*> ritems;
	for (auto& e : allRitems)
		ritems.push_back(e.get());

	//遍历渲染项数组
	for (size_t i = 0; i < ritems.size(); i++)
	{
		auto ritem = ritems[i];

                //设置定点索引缓冲区
		......
		
                //通过根常量更新数据		
		world = ritem->world;
		XMMATRIX w = XMLoadFloat4x4(&world);
		//XMMATRIX赋值给XMFLOAT4X4
		XMStoreFloat4x4(&objConstants.world, XMMatrixTranspose(w));
		//将数据拷贝至GPU缓存
		//currFrameResources->objCB->CopyData(ritem->objCBIndex, objConstants);
		
		cmdList->SetGraphicsRoot32BitConstants(0, //根参数的索引
			16, &objConstants, 0);

		//绘制顶点(通过索引缓冲区绘制)
		......
	}
}

由于passCB还存在CBV堆中,所以最后要修改堆中地址。这时地址只受帧资源影响。

//创建passCBV
for (int frameIndex = 0; frameIndex < frameResourcesCount; frameIndex++)
{
	......

	//int heapIndex = objectCount * frameResourcesCount + frameIndex;
	int heapIndex = frameIndex;

	......
}

同理设置Draw函数中的passCB堆中地址。

//设置根描述符表2
//int passCbvIndex = (int)allRitems.size() * frameResourcesCount + currFrameResourcesIndex;
int passCbvIndex = currFrameResourcesIndex;
auto passCbvHandle = CD3DX12_GPU_DESCRIPTOR_HANDLE(cbvHeap->GetGPUDescriptorHandleForHeapStart());
passCbvHandle.Offset(passCbvIndex, cbv_srv_uavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(1, //根参数的起始索引
	passCbvHandle);

编译运行,效果一样,但是现在的world矩阵数据是由16个根常量绑定的。

0377cf82f2c1be1d6e7a9f3e77302cb5.png

总结:根常量绑定数据是最便捷的,不需要创建CBV堆,不需要创建CBV,甚至不需要使用CopyData函数,缺点是太占内存,一个32位根常量占1DWORD,16个就是16DWORD。

3.在本书配套资源中,有一个名为Models/Skull.txt的文件。此文件含有骷髅头网格所需的顶点列表和索引列表。通过使用编辑器来查阅此文件,并修改“shapes”演示程序来加载和渲染此骷髅头网格。

首先我们打开Models/Skull.txt文件,记事本太卡,我直接用vscode打开。结构如下,前两行标注了顶点数和三角形数,后面两个大括号里包含了顶点列表和索引列表。

46e26cdeb1bc4e78293b966abdab74db.png

打开VertexList,我们看到一行有6个浮点数,其实左边三个对应pos(顶点坐标),右边三个对应normal(顶点法线)。

f9962082c68aedb50f4ca73c5fa4ad3d.png

打开TriangleList,我们看到一行有3个UINT类型数据,其实就是索引了,每个三角形包含3个索引。

3065e3e035f1883b1164d46dbffe6081.png

所以我们需要将这些数据读取到程序中,并用我们的数据结构去封装它,最后绘制出来。我们新建一个BuildSkullGeometry函数来构建网格。可以看到我们使用ifstream来读取txt文件中的数据,并将其最终存入顶点缓冲区和索引缓冲区中。因为现阶段我们的Vertex结构中只有pos和color两个属性,因此将normal数据都忽略掉(不读取)。因为skull网格和shapes网格是分别存入不同的顶点和索引缓存的,所以绘制三参数的两个地址都为0。注意:因为这个骷髅网格索引数超过了65536,所以我们要用DXGI_FORMAT_R32_UINT格式的索引数据类型。

void ShapesApp::BuildSkullGeometry()
{
	std::ifstream fin("Models/skull.txt");//读取骷髅网格文件

	if (!fin)//如果读取失败则弹框警告
	{
		MessageBox(0, L"Models/skull.txt not found", 0, 0);
		return;
	}

	UINT vertexCount = 0;
	UINT triangleCount = 0;
	std::string ignore;

	fin >> ignore >> vertexCount;//读取vertexCount并赋值
	fin >> ignore >> triangleCount;//读取triangleCount并赋值
	fin >> ignore >> ignore >> ignore >> ignore;//整行不读

	std::vector<Vertex> vertices(vertexCount);//初始化顶点列表
	//顶点列表赋值
	for (UINT i = 0; i < vertexCount; i++)
	{
		fin >> vertices[i].Pos.x >> vertices[i].Pos.y >> vertices[i].Pos.z;//读取顶点坐标
		fin >> ignore >> ignore >> ignore;//normal不读取
		vertices[i].Color = XMCOLOR(DirectX::Colors::CadetBlue);//设置顶点色
	}

	fin >> ignore;
	fin >> ignore;
	fin >> ignore;

	std::vector<std::int32_t> indices(triangleCount * 3);//初始化索引列表
	//索引列表赋值
	for (UINT i = 0; i < triangleCount; i++)
	{
		fin >> indices[i * 3 + 0] >> indices[i * 3 + 1] >> indices[i * 3 + 2];
	}

	fin.close();//关闭输入流

	const UINT vbByteSize = vertices.size() * sizeof(Vertex);//顶点缓存大小
	const UINT ibByteSize = indices.size() * sizeof(std::int32_t);//索引缓存大小

	auto geo = std::make_unique<MeshGeometry>();
	geo->name = "skullGeo";

	//将顶点和索引数据复制到CPU系统内存上
	ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->vertexBufferCpu));
	CopyMemory(geo->vertexBufferCpu->GetBufferPointer(), vertices.data(), vbByteSize);
	ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->indexBufferCpu));
	CopyMemory(geo->indexBufferCpu->GetBufferPointer(), indices.data(), ibByteSize);

	//将顶点和索引数据从CPU内存复制到GPU缓存上
	geo->vertexBufferGpu = ToolFunc::CreateDefaultBuffer(d3dDevice.Get(), 
		cmdList.Get(), vbByteSize, vertices.data(), geo->vertexBufferUploader);
	geo->indexBufferGpu = ToolFunc::CreateDefaultBuffer(d3dDevice.Get(),
		cmdList.Get(), ibByteSize, indices.data(), geo->indexBufferUploader);

	geo->vertexByteStride = sizeof(Vertex);
	geo->vertexBufferByteSize = vbByteSize;
	geo->indexBufferByteSize = ibByteSize;
	geo->indexFormat = DXGI_FORMAT_R32_UINT;

	//绘制三参数
	SubmeshGeometry skullSubmesh;
	skullSubmesh.baseVertexLocation = 0;
	skullSubmesh.startIndexLocation = 0;
	skullSubmesh.indexCount = (UINT)indices.size();

	geo->DrawArgs["skull"] = skullSubmesh;

	geometries["skullGeo"] = std::move(geo);
}

然后我们构建渲染项,将各属性赋值,并最终存入总渲染项中。

void ShapesApp::BuildRenderItem()
{	
        ......

        auto skullRitem = std::make_unique<RenderItem>();
	XMStoreFloat4x4(&skullRitem->world, XMMatrixScaling(0.5f, 0.5f, 0.5f) * XMMatrixTranslation(0.0f, 1.0f, 0.0f));
	skullRitem->objCBIndex = 2;//skull常量数据(world矩阵)在objConstantBuffer索引1上
	skullRitem->primitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
	skullRitem->geo = geometries["skullGeo"].get();
	skullRitem->indexCount = skullRitem->geo->DrawArgs["skull"].indexCount;
	skullRitem->baseVertexLocation = skullRitem->geo->DrawArgs["skull"].baseVertexLocation;
	skullRitem->startIndexLocation = skullRitem->geo->DrawArgs["skull"].startIndexLocation;
	allRitems.push_back(std::move(skullRitem));

        ......
}

编译运行。可以看到骷髅网格已经绘制在窗口中了。

d51d843eb2f227490f01867b1261a75a.png

线框模式。

5fae6baf40cb3fc4c04c75c9cf13d17c.png

至此,绘制几何体章节全部结束。接下来我们将进入《光照篇》,为我们的场景加入外部灯光。

今天先到这里吧,各位下篇见。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值