[DirectX12学习笔记] 用Direct3D绘图 Part.2

  • 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。

用DX12画一些基础形状


Frame Resource

之前做的都是每一帧都flush掉command list,但是这样的话效率很低,我们希望在gpu工作的时候cpu能够接着发下一帧的命令,但是由于command allocator如果被reset掉的话gpu就找不到当前帧需要的资源了,所以每一帧要有自己的command list allocator、自己的constant buffer和一个记录的fence值,用来标识这个frame resource对应的哪个fence。

CPU更新资源的时候,要先等这个frame resource被释放,才能更新这个frame resource里的资源。如下

// Cycle through the circular frame resource array.
    mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;
    mCurrFrameResource = mFrameResources[mCurrFrameResourceIndex].get();

    // Has the GPU finished processing the commands of the current frame resource?
    // If not, wait until the GPU has completed commands up to this fence point.
    if(mCurrFrameResource->Fence != 0 && mFence->GetCompletedValue() < mCurrFrameResource->Fence)
    {
        HANDLE eventHandle = CreateEventEx(nullptr, false, false, EVENT_ALL_ACCESS);
        ThrowIfFailed(mFence->SetEventOnCompletion(mCurrFrameResource->Fence, eventHandle));
        WaitForSingleObject(eventHandle, INFINITE);
        CloseHandle(eventHandle);
    }

	UpdateObjectCBs(gt);
	UpdateMainPassCB(gt);

同样的,每帧渲染结束后,不再是flush掉command list,取而代之的是要signal一下fence。

注意由于gpu一般远比cpu忙碌(这也是我们希望的结果,我们不希望gpu来等cpu),所以一般是cpu依然会等待gpu,cpu设置为可以超过gpu几帧(在这里设置为3帧,我们也不希望cpu超过gpu太多),所以这样做只能避免在gpu完成上一帧时,而cpu还没发完下一帧的命令,故每帧开头gpu会等待cpu,用frame resource是为了避免这个,并不能避免cpu等待gpu。

Render Item

如果要渲染大量物体,自己封装一个保存要渲染物体的信息的类是很方便的,这个render item类里面保存了cb的index,vb和ib的start location和index count,还有拓扑方式、世界矩阵,以及一个dirty值来标识这个物体经历了变动(这个dirty值在书上这章还没用,具体有什么用有待以后补全)。

Pass Constants

每一帧的update里面要更新render item数量个的object cb和 pass数量的pass cb,然后在draw里面传入,具体代码不再罗列。

创建几何体

GeometryGenerator.h/.cpp里面都有,创建基础几何体的vb和ib代码如下

    GeometryGenerator geoGen;
	GeometryGenerator::MeshData box = geoGen.CreateBox(1.5f, 0.5f, 1.5f, 3);
	GeometryGenerator::MeshData grid = geoGen.CreateGrid(20.0f, 30.0f, 60, 40);
	GeometryGenerator::MeshData sphere = geoGen.CreateSphere(0.5f, 20, 20);
	GeometryGenerator::MeshData cylinder = geoGen.CreateCylinder(0.5f, 0.3f, 3.0f, 20, 20);

	//
	// We are concatenating all the geometry into one big vertex/index buffer.  So
	// define the regions in the buffer each submesh covers.
	//

	// Cache the vertex offsets to each object in the concatenated vertex buffer.
	UINT boxVertexOffset = 0;
	UINT gridVertexOffset = (UINT)box.Vertices.size();
	UINT sphereVertexOffset = gridVertexOffset + (UINT)grid.Vertices.size();
	UINT cylinderVertexOffset = sphereVertexOffset + (UINT)sphere.Vertices.size();

	// Cache the starting index for each object in the concatenated index buffer.
	UINT boxIndexOffset = 0;
	UINT gridIndexOffset = (UINT)box.Indices32.size();
	UINT sphereIndexOffset = gridIndexOffset + (UINT)grid.Indices32.size();
	UINT cylinderIndexOffset = sphereIndexOffset + (UINT)sphere.Indices32.size();

    // Define the SubmeshGeometry that cover different 
    // regions of the vertex/index buffers.

	SubmeshGeometry boxSubmesh;
	boxSubmesh.IndexCount = (UINT)box.Indices32.size();
	boxSubmesh.StartIndexLocation = boxIndexOffset;
	boxSubmesh.BaseVertexLocation = boxVertexOffset;

	SubmeshGeometry gridSubmesh;
	gridSubmesh.IndexCount = (UINT)grid.Indices32.size();
	gridSubmesh.StartIndexLocation = gridIndexOffset;
	gridSubmesh.BaseVertexLocation = gridVertexOffset;

	SubmeshGeometry sphereSubmesh;
	sphereSubmesh.IndexCount = (UINT)sphere.Indices32.size();
	sphereSubmesh.StartIndexLocation = sphereIndexOffset;
	sphereSubmesh.BaseVertexLocation = sphereVertexOffset;

	SubmeshGeometry cylinderSubmesh;
	cylinderSubmesh.IndexCount = (UINT)cylinder.Indices32.size();
	cylinderSubmesh.StartIndexLocation = cylinderIndexOffset;
	cylinderSubmesh.BaseVertexLocation = cylinderVertexOffset;

	//
	// Extract the vertex elements we are interested in and pack the
	// vertices of all the meshes into one vertex buffer.
	//

	auto totalVertexCount =
		box.Vertices.size() +
		grid.Vertices.size() +
		sphere.Vertices.size() +
		cylinder.Vertices.size();

	std::vector<Vertex> vertices(totalVertexCount);

	UINT k = 0;
	for(size_t i = 0; i < box.Vertices.size(); ++i, ++k)
	{
		vertices[k].Pos = box.Vertices[i].Position;
        vertices[k].Color = XMFLOAT4(DirectX::Colors::DarkGreen);
	}

	for(size_t i = 0; i < grid.Vertices.size(); ++i, ++k)
	{
		vertices[k].Pos = grid.Vertices[i].Position;
        vertices[k].Color = XMFLOAT4(DirectX::Colors::ForestGreen);
	}

	for(size_t i = 0; i < sphere.Vertices.size(); ++i, ++k)
	{
		vertices[k].Pos = sphere.Vertices[i].Position;
        vertices[k].Color = XMFLOAT4(DirectX::Colors::Crimson);
	}

	for(size_t i = 0; i < cylinder.Vertices.size(); ++i, ++k)
	{
		vertices[k].Pos = cylinder.Vertices[i].Position;
		vertices[k].Color = XMFLOAT4(DirectX::Colors::SteelBlue);
	}

	std::vector<std::uint16_t> indices;
	indices.insert(indices.end(), std::begin(box.GetIndices16()), std::end(box.GetIndices16()));
	indices.insert(indices.end(), std::begin(grid.GetIndices16()), std::end(grid.GetIndices16()));
	indices.insert(indices.end(), std::begin(sphere.GetIndices16()), std::end(sphere.GetIndices16()));
	indices.insert(indices.end(), std::begin(cylinder.GetIndices16()), std::end(cylinder.GetIndices16()));

    const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);
    const UINT ibByteSize = (UINT)indices.size()  * sizeof(std::uint16_t);

	auto geo = std::make_unique<MeshGeometry>();
	geo->Name = "shapeGeo";

	ThrowIfFailed(D3DCreateBlob(vbByteSize, &geo->VertexBufferCPU));
	CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);

	ThrowIfFailed(D3DCreateBlob(ibByteSize, &geo->IndexBufferCPU));
	CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);

	geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
		mCommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);

	geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(md3dDevice.Get(),
		mCommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);

	geo->VertexByteStride = sizeof(Vertex);
	geo->VertexBufferByteSize = vbByteSize;
	geo->IndexFormat = DXGI_FORMAT_R16_UINT;
	geo->IndexBufferByteSize = ibByteSize;

	geo->DrawArgs["box"] = boxSubmesh;
	geo->DrawArgs["grid"] = gridSubmesh;
	geo->DrawArgs["sphere"] = sphereSubmesh;
	geo->DrawArgs["cylinder"] = cylinderSubmesh;

	mGeometries[geo->Name] = std::move(geo);

然后这只是创建了MeshGeometry,Render Item包含了MeshGeometry和一些别的参数,故还要用这些MeshGeometry创建同样数量的Render Item。

	auto boxRitem = std::make_unique<RenderItem>();
	XMStoreFloat4x4(&boxRitem->World, XMMatrixScaling(2.0f, 2.0f, 2.0f)*XMMatrixTranslation(0.0f, 0.5f, 0.0f));
	boxRitem->ObjCBIndex = 0;
	boxRitem->Geo = mGeometries["shapeGeo"].get();
	boxRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
	boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount;
	boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation;
	boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation;
	mAllRitems.push_back(std::move(boxRitem));

    auto gridRitem = std::make_unique<RenderItem>();
    gridRitem->World = MathHelper::Identity4x4();
	gridRitem->ObjCBIndex = 1;
	gridRitem->Geo = mGeometries["shapeGeo"].get();
	gridRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
    gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
    gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
    gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
	mAllRitems.push_back(std::move(gridRitem));

	UINT objCBIndex = 2;
	for(int i = 0; i < 5; ++i)
	{
		auto leftCylRitem = std::make_unique<RenderItem>();
		auto rightCylRitem = std::make_unique<RenderItem>();
		auto leftSphereRitem = std::make_unique<RenderItem>();
		auto rightSphereRitem = std::make_unique<RenderItem>();

		XMMATRIX leftCylWorld = XMMatrixTranslation(-5.0f, 1.5f, -10.0f + i*5.0f);
		XMMATRIX rightCylWorld = XMMatrixTranslation(+5.0f, 1.5f, -10.0f + i*5.0f);

		XMMATRIX leftSphereWorld = XMMatrixTranslation(-5.0f, 3.5f, -10.0f + i*5.0f);
		XMMATRIX rightSphereWorld = XMMatrixTranslation(+5.0f, 3.5f, -10.0f + i*5.0f);

		XMStoreFloat4x4(&leftCylRitem->World, rightCylWorld);
		leftCylRitem->ObjCBIndex = objCBIndex++;
		leftCylRitem->Geo = mGeometries["shapeGeo"].get();
		leftCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		leftCylRitem->IndexCount = leftCylRitem->Geo->DrawArgs["cylinder"].IndexCount;
		leftCylRitem->StartIndexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;
		leftCylRitem->BaseVertexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;

		XMStoreFloat4x4(&rightCylRitem->World, leftCylWorld);
		rightCylRitem->ObjCBIndex = objCBIndex++;
		rightCylRitem->Geo = mGeometries["shapeGeo"].get();
		rightCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		rightCylRitem->IndexCount = rightCylRitem->Geo->DrawArgs["cylinder"].IndexCount;
		rightCylRitem->StartIndexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;
		rightCylRitem->BaseVertexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;

		XMStoreFloat4x4(&leftSphereRitem->World, leftSphereWorld);
		leftSphereRitem->ObjCBIndex = objCBIndex++;
		leftSphereRitem->Geo = mGeometries["shapeGeo"].get();
		leftSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		leftSphereRitem->IndexCount = leftSphereRitem->Geo->DrawArgs["sphere"].IndexCount;
		leftSphereRitem->StartIndexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
		leftSphereRitem->BaseVertexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;

		XMStoreFloat4x4(&rightSphereRitem->World, rightSphereWorld);
		rightSphereRitem->ObjCBIndex = objCBIndex++;
		rightSphereRitem->Geo = mGeometries["shapeGeo"].get();
		rightSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
		rightSphereRitem->IndexCount = rightSphereRitem->Geo->DrawArgs["sphere"].IndexCount;
		rightSphereRitem->StartIndexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
		rightSphereRitem->BaseVertexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;

		mAllRitems.push_back(std::move(leftCylRitem));
		mAllRitems.push_back(std::move(rightCylRitem));
		mAllRitems.push_back(std::move(leftSphereRitem));
		mAllRitems.push_back(std::move(rightSphereRitem));
	}

	// All the render items are opaque.
	for(auto& e : mAllRitems)
		mOpaqueRitems.push_back(e.get());

shape demo

用GeometryGenerator创建的一些简单几何体,线框模式渲染效果如下。

在这里插入图片描述

用DX12画一个简单的风景图案

三种Root Parameter

之前使用过descriptor table这种root parameter,事实上有三种root parameter,有Descriptor Table、Root Descriptor、Root Constant这三种,Descriptor Table需要传入一系列堆里的cbv,Root Descriptor则只需要传入一个不用在堆里的cbv,而Root Constant只需要传入一个常数即可,然而如果分别传很多个不在堆里的cbv或者直接传很多个常量的话,开销会很大,所以如果传入的数据很多,要省开销的话,就最好放在一个descriptor table里。开销如下:

  1. Descriptor Table : 1 DWORD
  2. Root Descriptor : 2 DWORDS
  3. Root Constant : 1 DWORD per 32-bit constant
Descriptor Table

之前已经用过Descriptor Table了,故不再赘述,注意如果要用一个Descriptor Table来声明多个view输入,那么这些view必须在heap中连续存储,不想连续存储的话,就要用多个Descriptor Table。一个例子如下:

//Create a table with 2 CBVs, 3 SRVs, and 1 UAV.
CD3DX12_DEXCRIPTOR_RANGE descRange[3];
descRange[0].Init(
	D3D12_DESCRIPTOR_RANGE_TYPE_CBV, // descriptor type
	2, // decriptor count
	0, // base shader register arguments are bound to for this root parameter
	0, // register space
	2);// offset from start of table
descRange[1].Init(
	D3D12_DESCRIPTOR_RANGE_TYPE_SRV, // descriptor type
	3, // decriptor count
	0, // base shader register arguments are bound to for this root parameter
	0, // register space
	2);// offset from start of table
descRange[1].Init(
	D3D12_DESCRIPTOR_RANGE_TYPE_UAV, // descriptor type
	1, // decriptor count
	0, // base shader register arguments are bound to for this root parameter
	0, // register space
	2);// offset from start of table

slotRootParameter[0].InitAsDescriptorTable(
	3, descRange, D3D12_SHADER_VISIBILITY_ALL);

参数中的base shader register,例如cbv的这个参数如果是0,则是从b0开始的两个,即b0和b1,如果是1,则是从b1开始,即b1和b2。而space一般在shader中不指定的话默认是0,例如可以有
Texture2D gNormalMap : register(t0, space0);
Texture2D gNormalMap : register(t0, space1);
而前者相当于Texture2D gNormalMap : register(t0),因为不指定就默认是space0。

Root Descriptor

声明RootSignature时,用
slotRootParameter[0].InitAsConstantBufferView(UINT shaderRegister);
slotRootParameter[0].InitAsShaderResourceView(UINT shaderRegister);
slotRootParameter[0].InitAsUnorderedAccessView(UINT shaderRegister);
这三个来声明CBV/SRV/UAV,参数意思是寄存器的index,例如声明cbv时参数如果是0则是b0。

渲染时要传入参数的时候,用cmdList->SetGraphicsRootConstantBufferView,例如

UINT objCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(ObjectConstants));
D3D12_GPU_VIRTUAL_ADDRESS objCBAddress = 
	objectCB->GetGPUVirtualAddress();
// Offset to the constants for this object in the buffer.
objCBAddress += ri->ObjCBIndex * objCBByteSize;
cmdList->SetGraphicsRootConstantBufferView(
	0, // root parameter index
	objCBAddress);

注意,如果用Root Descriptor的话就不需要额外再创建CBV了,传值的时候直接传gpu地址就行,这里的这个Root Parameter本身就是CBV了。

Root Constant

声明的时候用
slotRootParameter[0].InitAsConstants(num32BitValues, shaderRegister);
其中num是32位数的个数,shaderRegister如果是0就是指b0。

传入参数的时候,用ID3D12GraphicsCommandList::SetGraphicsRoot32BitConstants(
UINT RootParameterIndex,
UINT Num32BitValuesToSet,
const void *pSrcData,
UINT DestOffsetIn32BitValues);
第一个参数是root parameter的index,第二个是要传入的constant数的数量,第三个则是连续存储32位constant的首地址指针,第四个则是offset,比如可以分两次传入,第一次的offset是0,第二次的offset位第一次的结尾,即从第一次传入后开始接着写入,具体见以下例子。

例子如下

// Root signature definition
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsContants(12,0);

...

// set the constants to register b0.
auto weights = CalcGaussWeights(2.5f);
int blurRadius = (int)weights.size() / 2;

cmdList->SetGraphicsRoot32BitConstants(0, 1, &blurRadius, 0);
cmdList->SetGraphicsRoot32BitConstants(0, (UINT)weights.size(),
	weights.data(), 1); //注意这个1是指位移1个32位constant。

// HLSL code
cbuffer cbSettings : register(b0)
{
	int gBlurRadius;
	float w0;
	float w1;
	float w2;
	float w3;
	float w4;
	float w5;
	float w6;
	float w7;
	float w8;
	float w9;
	float w10;
}

动态Vertex Buffer

可以这样写:

auto currWavesVB = mCurrFrameResource->WavesVB.get();
for(int i=0; i< mWaves->VertexCount(); i++)
{
	Vertex v;
	v.Pos = mWaves->Position(i);
	v.Color = XMFLOAT4(DirectX::Colors::Blue);
	currWavesVB->CopyData(i,v);
}
mWavesRitem->Geo->VertexBufferGPU = currWavesVB->Resource();

渲染风景画

利用动态vb渲染波浪的图案,利用一个简单的grid变化高度来形成山脉的形状,就可以制作出一幅简单的风景画。
这个demo中用root descriptor代替了descriptor table所以不再需要建立cbv的heap。
最后渲染结果如下图。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值