当前位置: 首页 > backend >正文

第七章 利用Direct3D绘制几何体

帧资源

CPU(除了要执行其它必要的工作)需要构建i提交命令列表,而GPU则负责处理命令队列中的各种命令。我们的目标是让CPU和GPU持续的工作,从而充分的利用硬件资源。

为此提出了一个方案:以CPU每帧都需要更新的资源作为基本元素,创建一个环形数组。我们称这些资源为帧资源,而这种循环数组通常是由3个帧资源元素所构成。该方案的思路是:在处理第n帧的时候,CPU将周而复始地从帧资源数组中获取下一个可用的帧资源。趁着GPU还在处理此前帧之时,CPU将为第n帧更新资源,并构建提交对应的命令队列。随后,CPU会继续针对第n+1帧执行同样的工作流程,并不断重复下去。如果帧资源数组共有3个元素,则命令CPU比GPU提前处理两帧。

在本章中的例子,只有常量缓冲区需要每帧更新,所以程序中的帧资源类中只含有常量缓冲区。

  • 这里也是通过fence去维持。也就是说每个帧资源都会有一个fence的量,在每帧update的时候,如果这个帧资源所对应的resource早就被gpu用完了,那么就可以更新,此时gpu在用这类资源的其它resource。
  • 同时这里的每帧的帧资源都存储着不同物体的常量缓冲区,也就是每帧都维护一个帧资源和所有渲染项资源
#pragma once
#include "UploadBuffer.h"
#include "review/BoxApp.h"class FrameResource
{
public:FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount);FrameResource(const FrameResource&) = delete;FrameResource& operator=(const FrameResource& rhs) = delete;~FrameResource();// 在GPU处理与此命令分配器相关的命令之前,我们不能对它进行重置。//所以每一帧都要有它们的命令分配器Microsoft::WRL::ComPtr<ID3D12CommandAllocator> CmdListAlloc;//在GPU执行完引用此常量缓冲区的命令之前,我们不能对它进行更新。//因此每一帧都要有它们自己的常量缓冲区std::unique_ptr<UploadBuffer<PassConstant>> PassCB = nullptr;std::unique_ptr<UploadBuffer<ObjectConsts>> ObjectCB = nullptr;//通过围栏值将命令标记到此围栏点,这使得我们可以检测GPU是否还在使用这些帧资源UINT Fence = 0;
};
#include "D3DUtil.h"
#include "FrameResource.h"FrameResource::FrameResource(ID3D12Device* device, UINT passCount, UINT objectCount)
{ThrowIfFailed(device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(CmdListAlloc.GetAddressOf())));PassCB = std::make_unique<UploadBuffer<PassContant>>(device, passCount, true);ObjectCB = std::make_unique<UploadBuffer<ObjectConsts>>(device, objectCount, true);
}
FrameResource::~FrameResource()
{}

我们可以实例化3个帧资源,这里mAllRitems就是场景中的所有渲染项

const int gNumFrameResources = 3;
std::vector<std::unique_ptr<FrameResource>> mFrameResources;
FrameResource* mCurrentFrameResource = nullptr;
int mCurrFrameResourceIndex = 0;void ShapesApp::BuildFrameResources()
{for (int i = 0; i < gNumFrameResources; i++){mFrameResources.push_back(std::make_unique<FrameResource>(device, 1, (UINT)mAllRitems.size()));}
}
void ShapesApp::Update(const GameTimer& gt)
{mCurrFrameResourceIndex = (mCurrFrameResourceIndex + 1) % gNumFrameResources;mCurrentFrameResource = mFrameResources[mCurrFrameResourceIndex].get();//gpu端是否执行完处理当前帧资源的所有命令?//如果没有就让CPU等待if (mCurrentFrameResource->Fence != 0 && m_Fence->GetCompletedValue() < mCurrentFrameResource->Fence){HANDLE eventHandle = CreateEventEx(nullptr, nullptr, false, EVENT_ALL_ACCESS);ThrowIfFailed(m_Fence->SetEventOnCompletion(mCurrentFrameResource->Fence, eventHandle));WaitForSingleObject(eventHandle, INFINITE);CloseHandle(eventHandle);}...更新资源
}

下面这个在对应的draw中去设定,其目的在于什么呢。fence的值决定了现在正在使用的frame的资源。也就是正在渲染的。

mCurrentFrameResource->Fence = ++m_CurrentFence;
m_CommandQueue->Signal(m_Fence.Get(), m_CurrentFence);

这个情况虽然还是无法避免等待情况的发生,但是我们希望的是CPU会有空闲的时间,因为CPU的空闲时间总是可以被游戏的其它部分利用,如AI,物理模拟等业务。

渲染项

绘制一个物体需要设置多种参数,例如绑定顶点缓冲区和索引缓冲区、绑定与物体有关的常量数据,设定图元以及指定DrawIndexedInstanced方法的参数。随着场景中所绘制物体的逐渐增多,如果我们能创建一个轻量级结构来存储绘制物体所需的数据,那是极好的。

由于每个物体的特征不同,绘制过程所需的数据也会有所变化,因此该结构中的数据也会因具体程序而异。我们把单次绘制调用过程中,需要向渲染流水线提交的数据集称为渲染项。对于当前演示程序而言,渲染项RenderItem结构如下:

struct RenderItem
{
RenderItem() = default;
//描述物体局部空间相对于世界空间的世界矩阵
//它定义了物体位于世界空间中的位置、朝向以及大小
DirectX::XMFLOAT4X4 World = MathHelper::Identity4x4();//用已更行标记(dirty flag)来表示物体的相关数据已发生改变,这意味着我们此时需要更新常量缓冲区。
//由于每个FrameResource中都有一个物体常量缓冲区,所以我们必须对每个FrameResource都进行更新
//即,当我们修改物体数据的时候,应当按NumFramesDirty=gNumFrameResource进行设置
//从而每个帧资源都得到更新
int NumFrameDirty = gNumFrameResources; // 这里的意思就是物体primitive更新以后,那么所有的帧资源都应该更新
//该索引指向的GPU常量缓冲区对应于当前渲染项中的物体常量缓冲区
UINT ObjCBIndex  =-1;
//此渲染项参与绘制的几何体。注意,绘制一个几何体可能会用到多个渲染项
MeshGeometry* Geo = nullptr;//DrawIndexedInstanced方法的参数
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
int BaseVertexLocation = 0;};

我们应用程序将根据各渲染项的绘制目的,把它们保存在不同的向量里。即按照不同的PSO所需的渲染项,将它们划分到不同的向量中。

渲染过程中所用到的常量数据

我们在FrameResource类中引进了一个新的常量缓冲区:

std::unique_ptr<UploadBuffer<PassConstant>> PassCB = nullptr;

随着功能的复杂的不断增加,该缓冲区中存储的数据内容(例如观察者,观察矩阵等)会根据特定的渲染过程而确定下来(比如ue中的pass::process的过程)(这里是设定了一个)。其中也包含了与游戏计时有关的信息,它们是着色器程序中要访问的极有用的数据。

注意,可能演示程序不会用到所有的常量数据,但是它们的存在会使工作变得更加方便,而且提供这些额外的数据只需要少量的开销。例如,虽然我们现在无须知道渲染目标的尺寸,但当实现某些后期处理效果之时,这个信息将会派上用场。

对应的数据在cbuffer中的定义:

cbuffer cbPass : register(b0)
{
float4x4 gView;
float4x4 gInvView;
float4x4 gProj;
float4x4 gInvProj;
float4x4 gViewProj;
float4x4 gInvViewProj;
float3 gEyePosW;
float cbPerObjectPad1;
float2 gRenderTargetSize;
float2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
}

而对于物体的缓冲区而言,目前只有一个world的矩阵和它相关。

cbuffer cbPerObject : register(b0)
{float4x4 gWorld;
};

我们做出上述调整的思路为:基于资源的更新频率对常量数据进行分组。在每次渲染过程中(render pass),只需将本次所用的常量(cbPass)更新一次;而每当某个物体的世界矩阵发生改变时,只需更新该物体的相关常量(cbPerObject)即可。比如一个静态的物体,那么我们只需要更新一次它的常量(cbPerObject)即可。

在下面的演示程序中,将通过下列方法来更新渲染过程常量缓冲区以及物体常量缓冲区。在绘制每一帧画面时,者两个方法都被Update函数调用一次。

struct MainPassConst
{
DirectX::XMFLOAT4X4 gView;
DirectX::XMFLOAT4X4 gInvView;
DirectX::XMFLOAT4X4 gProj;
DirectX::XMFLOAT4X4 gInvProj;
DirectX::XMFLOAT4X4 gViewProj;
DirectX::XMFLOAT4X4 gInvViewProj;
DirectX::XMFLOAT3 gEyePosW;
float cbPerObjectPad1;
DirectX::XMFLOAT2 gRenderTargetSize;
DirectX::XMFLOAT2 gInvRenderTargetSize;
float gNearZ;
float gFarZ;
float gTotalTime;
float gDeltaTime;
};
void ShapesApp::UpdateObjectCBs(const GameTimer& gt)
{// update renderItemfor (auto& r : mAllRitems){if (r->NumFrameDirty){r->NumFrameDirty--;DirectX::XMMATRIX world = DirectX::XMLoadFloat4x4(&r->World);ObjectConsts objConstant;DirectX::XMStoreFloat4x4(&objConstant.World, DirectX::XMMatrixTranspose(world));m_CurrentFrameResource->ObjectCB->CopyData(r->ObjCBIndex, objConstant);}}
}void ShapesApp::UpdateMainPassCB(const GameTimer& gt)
{DirectX::XMMATRIX view = DirectX::XMLoadFloat4x4(&m_View);DirectX::XMMATRIX proj = DirectX::XMLoadFloat4x4(&m_Proj);DirectX::XMMATRIX viewProj = DirectX::XMMatrixMultiply(view, proj);DirectX::XMMATRIX invView = DirectX::XMMatrixInverse(&DirectX::XMMatrixDeterminant(view), view);DirectX::XMMATRIX invProj = DirectX::XMMatrixInverse(&DirectX::XMMatrixDeterminant(proj), proj);DirectX::XMMATRIX invViewProj = DirectX::XMMatrixInverse(&DirectX::XMMatrixDeterminant(viewProj), viewProj);DirectX::XMStoreFloat4x4(&m_MainPassCB.View, DirectX::XMMatrixTranspose(view));DirectX::XMStoreFloat4x4(&m_MainPassCB.InvView, DirectX::XMMatrixTranspose(invView));DirectX::XMStoreFloat4x4(&m_MainPassCB.Proj, DirectX::XMMatrixTranspose(proj));DirectX::XMStoreFloat4x4(&m_MainPassCB.InvProj, DirectX::XMMatrixTranspose(invProj));DirectX::XMStoreFloat4x4(&m_MainPassCB.ViewProj, DirectX::XMMatrixTranspose(viewProj));DirectX::XMStoreFloat4x4(&m_MainPassCB.InvViewProj, DirectX::XMMatrixTranspose(invViewProj));m_MainPassCB.EyePosW = m_EyePos;m_MainPassCB.RenderTargetSize = DirectX::XMFLOAT2(static_cast<float>(m_ClientWidth), static_cast<float>(m_ClientHeight));m_MainPassCB.InvRenderTargetSize = DirectX::XMFLOAT2(static_cast<float>(1.0f / m_ClientWidth), static_cast<float>(1.0f / m_ClientHeight));m_MainPassCB.NearZ = 1.0f;m_MainPassCB.FarZ = 1000.0f;m_MainPassCB.TotalTime = gt.TotalTime();m_MainPassCB.DeltaTime = gt.DeltaTime();auto currPassCB = m_CurrentFrameResource->PassCB.get();currPassCB->CopyData(0, m_MainPassCB);
}

顶点着色器也需要进行相应的变换:

现在着色器所期望的输入资源发生了改变,因此我们需要相应地调整根签名来使之获取所需的两个描述符表(此时,我们着色器程序需要获取两个描述符表)因为这两个CBV的更新频率不同,渲染过程的CBV 每个pass设置一次,而物体的cbv则针对每个渲染项进行配置。

不同形状的几何体

下面将展示如何创建不同形状的几何体,如椭球体、球体、柱体。我们将程序性几何体(procedual geometry,大概意思就是根据用户提供的参数以程序自动生成对应的几何体)的生成代码放入GeometryGenerator类中。

#pragma onceclass GeometryGenerator
{
public:using uint16 = std::uint16_t;using uint32 = std::uint32_t;struct Vertex{Vertex(){}Vertex(const DirectX::XMFLOAT3& p,const DirectX::XMFLOAT3& n,const DirectX::XMFLOAT3& t,const DirectX::XMFLOAT2& uv) :Position(p),Normal(n),TangentU(t),TexC(uv){}Vertex(float px, float py, float pz,float nx, float ny, float nz,float tx, float ty, float tz,float u, float v):Position(px, py, pz),Normal(nx, ny, nz),TangentU(tx, ty, tz),TexC(u, v) {}DirectX::XMFLOAT3 Position;DirectX::XMFLOAT3 Normal;DirectX::XMFLOAT3 TangentU;DirectX::XMFLOAT2 TexC;};struct MeshData{std::vector<Vertex> Vertices;std::vector<uint32> Indices32;std::vector<uint32>& GetIndices16(){m_Indices.resize(Indices32.size());for (size_t i = 0; i < Indices32.size(); i++){m_Indices[i] = static_cast<uint16>(Indices32[i]);}}private:std::vector<uint16> m_Indices;};MeshData CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount);
};

生成柱体网络

在定义一个柱体时,需要指定其顶,底面半径,高度,切片数量,以及堆叠层数。

这里生成uv的思路是,将这个圆柱展开,U的变化也就是T的值为圆环上位置变化的切线。而V的变换也就是T的值为从底端斜向上指向顶端

GeometryGenerator::MeshData GeometryGenerator::CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount)
{MeshData meshData;// 构建对叠层float stackHeight = height / stackCount;// 计算从上至下每个相邻分层时所需的半径增量float radiusStep = (topRadius - bottomRadius) / stackCount;uint32 ringCount = stackCount + 1;for (uint32 i = 0; i < ringCount; i++){float y = -0.5f * height + i * stackHeight;float r = bottomRadius + i * radiusStep;//环上各个顶点float dTheta = 2.0f * DirectX::XM_PI / sliceCount;for (uint32 j = 0; j <= sliceCount; j++){Vertex vertex;float c = cosf(dTheta * j);float s = sinf(dTheta * j);vertex.Position = DirectX::XMFLOAT3(c * r, y, s * r);vertex.TexC.x = (float)j / sliceCount;vertex.TexC.y = 1.0f - i / stackCount; //从下面开始的vertex.TangentU = DirectX::XMFLOAT3(-s, 0.0f, c); //位置的变化切线为ufloat dr = bottomRadius - topRadius;DirectX::XMFLOAT3 bitangent(c * dr, - height, s * r);DirectX::XMVECTOR T = DirectX::XMLoadFloat3(&vertex.TangentU);DirectX::XMVECTOR B = DirectX::XMLoadFloat3(&bitangent);DirectX::XMVECTOR N = DirectX::XMVector3Normalize(DirectX::XMVector3Cross(T ,B));DirectX::XMStoreFloat3(&vertex.Normal , N);meshData.Vertices.push_back(vertex);}}
}

从上述代码中可以看出,每个环的第一个顶点与最后一个顶点在位置上是重合的,但是二者的纹理坐标不是相同的。只有这样做才能保证在圆台上绘制出正确的纹理。

通过上面的方式,我们已经生成了所有的顶点位置了,接下来就是对应的indices的生成。

首先是侧面的三角形的生成,我们通过图像可以得到:\triangle ABC = (i\cdot n + j, (i + 1) * n + j, (i + 1) * n + j + 1) (n为slicecount)

\triangle ACD = (i \cdot n + j, (i + 1) * n + j + 1, i \cdot n + j + 1)

//+1是希望每环的第一个顶点和最后一个顶点重合,这是因为它们的纹理坐标不相同
uint32 ringVertexCount = sliceCount + 1;
for (uint32 i = 0; i < stackCount; i++){for (uint32 j = 0; j < sliceCount; j++){meshData.Indices32.push_back(i * ringVertexCount + j);meshData.Indices32.push_back((i + 1) * ringVertexCount + j);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + 1);meshData.Indices32.push_back(i * ringVertexCount + j);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + 1);meshData.Indices32.push_back(i * ringVertexCount + j + 1);}}

最后是生成圆台的两个端面:

GeometryGenerator::MeshData GeometryGenerator::CreateCylinder(float bottomRadius, float topRadius, float height, uint32 sliceCount, uint32 stackCount)
{MeshData meshData;// 构建对叠层float stackHeight = height / stackCount;// 计算从上至下每个相邻分层时所需的半径增量float radiusStep = (topRadius - bottomRadius) / stackCount;uint32 ringCount = stackCount + 1;for (uint32 i = 0; i < ringCount; i++){float y = -0.5f * height + i * stackHeight;float r = bottomRadius + i * radiusStep;//环上各个顶点float dTheta = 2.0f * DirectX::XM_PI / sliceCount;for (uint32 j = 0; j <= sliceCount; j++){Vertex vertex;float c = cosf(dTheta * j);float s = sinf(dTheta * j);vertex.Position = DirectX::XMFLOAT3(c * r, y, s * r);vertex.TexC.x = (float)j / sliceCount;vertex.TexC.y = 1.0f - i / stackCount; //从下面开始的vertex.TangentU = DirectX::XMFLOAT3(-s, 0.0f, c); //位置的变化切线为ufloat dr = bottomRadius - topRadius;DirectX::XMFLOAT3 bitangent(c * dr, - height, s * r);DirectX::XMVECTOR T = DirectX::XMLoadFloat3(&vertex.TangentU);DirectX::XMVECTOR B = DirectX::XMLoadFloat3(&bitangent);DirectX::XMVECTOR N = DirectX::XMVector3Normalize(DirectX::XMVector3Cross(T ,B));DirectX::XMStoreFloat3(&vertex.Normal , N);meshData.Vertices.push_back(vertex);}}//+1是希望每环的第一个顶点和最后一个顶点重合,这是因为它们的纹理坐标不相同uint32 ringVertexCount = sliceCount + 1;for (uint32 i = 0; i < stackCount; i++){for (uint32 j = 0; j < sliceCount; j++){meshData.Indices32.push_back(i * ringVertexCount + j);meshData.Indices32.push_back((i + 1) * ringVertexCount + j);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + 1);meshData.Indices32.push_back(i * ringVertexCount + j);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + 1);meshData.Indices32.push_back(i * ringVertexCount + j + 1);}}BuildCyclinderTopCap(bottomRadius, topRadius, height, sliceCount, stackCount, meshData);BuildCyclinderBottomCap(bottomRadius, topRadius, height, sliceCount, stackCount, meshData);
}void GeometryGenerator::BuildCyclinderTopCap(float bottomRadius, float topRadius, float height, uint32 sliceCount,
uint32 stackCount, MeshData& meshData)
{uint32 baseIndex = stackCount * (sliceCount + 1);meshData.Vertices.push_back(Vertex(0.0f, 0.5f * height, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 0.0f));uint32 centerIndex = (uint32)meshData.Vertices.size() - 1;for (uint32 i = 0; i < sliceCount; i++){meshData.Indices32.push_back(centerIndex);meshData.Indices32.push_back(baseIndex + i + 1);meshData.Indices32.push_back(baseIndex + i);}
}void GeometryGenerator::BuildCyclinderBottomCap(float bottomRadius, float topRadius, float height, uint32 sliceCount,
uint32 stackCount, MeshData& meshData)
{uint32 baseIndex = 0;meshData.Vertices.push_back(Vertex(0.0f, 0.5f * height, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, 1.0f));uint32 centerIndex = (uint32)meshData.Vertices.size() - 1;for (uint32 i = 0; i < sliceCount; i++){meshData.Indices32.push_back(centerIndex);meshData.Indices32.push_back(baseIndex + i + 1);meshData.Indices32.push_back(baseIndex + i);}
}

生成球体网络

预定义一个球体,就要指定其半径、切片数量及其堆叠层数,如下图所示,除了每个环上的半径是依三角函数非线性变化,生成球体的算法和圆台非常相似,顶点就是一层层的圆环。我们只需要根据比例的缩放半径。

GeometryGenerator::MeshData GeometryGenerator::CreateSphere(float radius, uint32 sliceCount, uint32 stackCount)
{if (stackCount % 2 != 0){assert("stackCount must be odd");}MeshData meshData;float Height = radius * 2.0f;float stackHeight = Height / (float)((stackCount + 2)); // 到上下顶点的高度uint32 ringCount = stackCount + 3;uint32 maxRadiusRingIndex = (stackCount / 2) + 1; //最大的r的环的下标for (uint32 i = 0; i < ringCount; i++){float y = i * stackHeight -0.5f * Height;if (i == 0 || i == ringCount - 1){meshData.Vertices.push_back(Vertex(0.0f, y, 0.0f, 0.0f, (i == 0?-1.0f : 1.0f), 0.0f, 1.0f, 0.0f, 0.0f, 0.5f, (i == 0? 1.0f : 0.0f)));}else{float r = std::sqrtf(radius * radius - y * y);float dTheta = 2.0f * DirectX::XM_PI / sliceCount;for (int j = 0; j <= sliceCount; j++){Vertex vertex;float c = cosf(j * dTheta);float s = sinf(j * dTheta);vertex.Position = DirectX::XMFLOAT3(c * r, y, s * r);vertex.TexC.x = (float) j / (float)sliceCount;vertex.TexC.y = 1.0f - (float)i / (float)(ringCount - 1);vertex.TangentU = DirectX::XMFLOAT3(-s, 0.0f, c);DirectX::XMFLOAT3 bitangent;if (i < maxRadiusRingIndex){bitangent = DirectX::XMFLOAT3(-c * r, -Height, -s * r);}else if (i == maxRadiusRingIndex){bitangent = DirectX::XMFLOAT3(0.0f, -Height, 0.0f);}else{bitangent = DirectX::XMFLOAT3(c * r, -Height, s * r);}DirectX::XMVECTOR T = DirectX::XMLoadFloat3(&vertex.TangentU);DirectX::XMVECTOR B = DirectX::XMLoadFloat3(&bitangent);DirectX::XMVECTOR N = DirectX::XMVector3Normalize(DirectX::XMVector3Cross(T, B));DirectX::XMStoreFloat3(&vertex.Normal, N);meshData.Vertices.push_back(vertex);}}}// 先记录侧面uint32 ringVertexCount = sliceCount + 1;uint32 baseIndex = 1; // 最下面的点for (uint32 i = 0; i < stackCount; i++){for (int j = 0; j < sliceCount; j++){meshData.Indices32.push_back(i * ringVertexCount + j + baseIndex);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + baseIndex);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + 1 + baseIndex);meshData.Indices32.push_back(i * ringVertexCount + j + baseIndex);meshData.Indices32.push_back((i + 1) * ringVertexCount + j + 1 + baseIndex);meshData.Indices32.push_back(i * ringVertexCount + j + 1 + baseIndex);}}//然后记录顶面和底面for (uint32 i = 0; i < sliceCount; i++){meshData.Indices32.push_back(i + baseIndex);meshData.Indices32.push_back(0);meshData.Indices32.push_back(i + baseIndex + 1);}baseIndex = 1 + stackCount * ringVertexCount; //top ring beginuint32 topVertexIndex = (uint32)meshData.Vertices.size() - 1;for (uint32 i = 0; i < sliceCount; i++){meshData.Indices32.push_back(i + baseIndex);meshData.Indices32.push_back(topVertexIndex);meshData.Indices32.push_back(i + baseIndex + 1);}
}

生成几何球体网络

上面的球体构建的三角形的面积并不相同,几何球体利用面积相同且边长相等的三角形来逼近球体

为了生成几何球体,我们以一个正二十面体作为基础,细分其上的三角形,再根据给定的半径向球面投影新生成的顶点。反复重复这个过程,便可以提高该几何球体的曲面细分程度。

上图展示了如何将一个三角形细分为4个大小相等的小三角形。不能发现,新生成的顶点都位于原始三角形边上的中点。先将顶点投影到单位球面上,再利用r进行标量乘法:v` = r \frac{v}{||v||}

就可以把顶点投影到半径为r的球体上。这里的意思就是细分后的顶点,此时球心在原点,就可以根据这个方式投影到需要的球面上完成细分。

GeometryGenerator::MeshData GeometryGenerator::CreateGeosphere(float radius, uint32 numSubdivisions)
{MeshData meshData;numSubdivisions = std::min<uint32>(numSubdivisions, 6u);const float x = 0.525731f;const float z = 0.850651f;DirectX::XMFLOAT3 pos[12] ={DirectX::XMFLOAT3(-x, 0.0f, z), DirectX::XMFLOAT3(x, 0.0f, z),DirectX::XMFLOAT3(-x, 0.0f, -z), DirectX::XMFLOAT3(x, 0.0f, -z),DirectX::XMFLOAT3(0.0f, z, x), DirectX::XMFLOAT3(0.0f, z, -x),DirectX::XMFLOAT3(0.0f, -z, x), DirectX::XMFLOAT3(0.0f, -z, -x),DirectX::XMFLOAT3(z, x, 0.0f), DirectX::XMFLOAT3(-z, x, 0.0f),DirectX::XMFLOAT3(z, -x, 0.0f), DirectX::XMFLOAT3(-z, x, 0.0f)
};uint32 k[60] = {1,4,0, 4,9,0, 4,5,9, 8,5,4, 1,8,4,1,10,8, 10,3,8, 8,3,5, 3,2,5, 3,7,2,3,10,7, 10,6,7, 6,11,7, 6,0,11, 6,1,0,10,1,6, 11,0,9, 2,11,9, 5,2,9, 11,2,7
};meshData.Vertices.resize(12);meshData.Indices32.assign(&k[0], &k[60]);for (uint32 i = 0; i < 12; i++){meshData.Vertices[i].Position = pos[i];}for (uint32 i = 0; i < numSubdivisions; i++){Subdivide(meshData);}for (uint32 i = 0; i < meshData.Vertices.size(); i++){DirectX::XMVECTOR n = DirectX::XMVector3Normalize(DirectX::XMLoadFloat3(&meshData.Vertices[i].Position));DirectX::XMVECTOR p = DirectX::XMVectorMultiply(n, DirectX::XMVectorSet(radius, radius, radius, 0));DirectX::XMStoreFloat3(&meshData.Vertices[i].Position, p);DirectX::XMStoreFloat3(&meshData.Vertices[i].Normal, n);//根据球面坐标推导出纹理坐标float theta = atan2f(meshData.Vertices[i].Position.z, meshData.Vertices[i].Position.x);if (theta < 0.0f)theta += DirectX::XM_2PI;float phi = acosf(meshData.Vertices[i].Position.y / radius);meshData.Vertices[i].TexC.x = theta / DirectX::XM_2PI;meshData.Vertices[i].TexC.y = phi / DirectX::XM_PI;meshData.Vertices[i].TangentU.x = -radius * sinf(theta) * sinf(phi);meshData.Vertices[i].TangentU.y = 0.0f;meshData.Vertices[i].TangentU.z = radius * cosf(theta) * sinf(phi);DirectX::XMVECTOR T = DirectX::XMLoadFloat3(&meshData.Vertices[i].TangentU);DirectX::XMStoreFloat3(&meshData.Vertices[i].TangentU, DirectX::XMVector3Normalize(T));}return meshData;
}void GeometryGenerator::Subdivide(MeshData& meshData)
{uint32 triangleCount = meshData.Indices32.size();uint32 baseIndex = meshData.Vertices.size();assert(triangleCount > 0 && triangleCount % 3 == 0);meshData.Indices32.clear();for (uint32 i = 0; i < triangleCount; i+=3){uint32 P0Index = meshData.Indices32[i];uint32 P1Index = meshData.Indices32[i + 1];uint32 P2Index = meshData.Indices32[i + 2];DirectX::XMVECTOR P0 = DirectX::XMLoadFloat3(&meshData.Vertices[P0Index].Position);DirectX::XMVECTOR P1 = DirectX::XMLoadFloat3(&meshData.Vertices[P1Index].Position);DirectX::XMVECTOR P2 = DirectX::XMLoadFloat3(&meshData.Vertices[P2Index].Position);DirectX::XMVECTOR M0 = DirectX::XMVectorDivide(DirectX::XMVectorAdd(P0, P1), DirectX::XMVectorSet(2.0f, 2.0f, 2.0f, 1.0f));DirectX::XMVECTOR M1 = DirectX::XMVectorDivide(DirectX::XMVectorAdd(P1, P2), DirectX::XMVectorSet(2.0f, 2.0f, 2.0f, 1.0f));DirectX::XMVECTOR M2 = DirectX::XMVectorDivide(DirectX::XMVectorAdd(P2, P0), DirectX::XMVectorSet(2.0f, 2.0f, 2.0f, 1.0f));Vertex v0;Vertex v1;Vertex v2;DirectX::XMStoreFloat3(&v0.Position, M0);DirectX::XMStoreFloat3(&v1.Position, M1);DirectX::XMStoreFloat3(&v2.Position, M2);meshData.Vertices.push_back(v0);meshData.Vertices.push_back(v1);meshData.Vertices.push_back(v2);uint32 M0Index = baseIndex;uint32 M1Index = baseIndex + 1;uint32 M2Index = baseIndex + 2;meshData.Indices32.push_back(M0Index);meshData.Indices32.push_back(P1Index);meshData.Indices32.push_back(M1Index);meshData.Indices32.push_back(P0Index);meshData.Indices32.push_back(M0Index);meshData.Indices32.push_back(M2Index);meshData.Indices32.push_back(M2Index);meshData.Indices32.push_back(M0Index);meshData.Indices32.push_back(M1Index);meshData.Indices32.push_back(M2Index);meshData.Indices32.push_back(M1Index);meshData.Indices32.push_back(P2Index);baseIndex+=3;}
}

绘制多种几何体演示程序

顶点缓冲区和索引缓冲区

正如上面的演示程序所示,我们要绘制长方体、栅格、柱体及球体。尽管在示例中要绘制多个球体和柱体,但是实际上只需要创建一次对应的几何数据。然后通过渲染项中world矩阵去生成不同位置的几何。这实际上就是实例化,shader material这些是一模一样的。

通过将不同物体的顶点和索引合并起来,我们把所有几何体网络的顶点的索引都装进一个顶点缓冲区及一个索引缓冲区内。这意味着,在绘制一个物体时,我们只需绘制此物体位于顶点和索引这两种缓冲区中的数据子集。

为了通过DrawIndexInstanced方法绘制,我们需要掌握3中数据,它们分别是:待绘制物体在合并索引缓冲区中的起始索引、待绘制物体的索引数量,以及基准顶点地址。

这里首先得补充一下方体的几何信息的生成。

GeometryGenerator::MeshData GeometryGenerator::CreateBox(float width, float height, float depth)
{MeshData meshData;DirectX::XMFLOAT3 pos[8] = {//behineDirectX::XMFLOAT3(-width * 0.5f, -height * 0.5f, depth * 0.5f),DirectX::XMFLOAT3(-width * 0.5f, height * 0.5f, depth * 0.5f),DirectX::XMFLOAT3(width * 0.5f, height * 0.5f, depth * 0.5f),DirectX::XMFLOAT3(width * 0.5f, -height * 0.5f, depth * 0.5f),// frontDirectX::XMFLOAT3(-width * 0.5f, -height * 0.5f, -depth * 0.5f),DirectX::XMFLOAT3(-width * 0.5f, height * 0.5f, -depth * 0.5f),DirectX::XMFLOAT3(width * 0.5f, height * 0.5f, -depth * 0.5f),DirectX::XMFLOAT3(width * 0.5f, -height * 0.5f, -depth * 0.5f)
};uint32 indices[36] = {0,1,2, 0,2,3, //behine7,6,2, 7,2,3, //right4,5,6, 4,6,7, // front0,1,5, 0,5,4, //right5,1,2, 5,2,6, // top0,4,7, 0,7,3 //bottom
};meshData.Indices32.assign(&indices[0], &indices[36]);meshData.Vertices.resize(8);for (uint32 i = 0; i < 8; i++){meshData.Vertices[i].Position = pos[i];}return meshData;
}
void ShapesApp::BuildShapeGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData box = geoGen.CreateBox(1.5f, 0.5f, 1.5f);GeometryGenerator::MeshData sphere = geoGen.CreateSphere(0.5f, 20, 20);GeometryGenerator::MeshData cylinder = geoGen.CreateCylinder(0.5f, 0.3f, 3.0f, 20, 20);UINT boxVertexOffset = 0;UINT sphereVertexOffset = (UINT)box.Vertices.size();UINT cylinderVertexOffset = (UINT)sphere.Vertices.size() + sphereVertexOffset;UINT boxIndexOffset = 0;UINT sphereIndexOffset = (UINT)box.Indices32.size();UINT cylinderIndexOffset = (UINT)sphere.Indices32.size() + sphereIndexOffset;d3dUtil::SubmeshGeometry boxSubmesh;boxSubmesh.IndexCount = (UINT)box.Indices32.size();boxSubmesh.StartIndexLocation = boxIndexOffset;boxSubmesh.BaseVertexLocation = boxVertexOffset;d3dUtil::SubmeshGeometry sphereSubmesh;sphereSubmesh.IndexCount = (UINT)sphere.Indices32.size();sphereSubmesh.StartIndexLocation = sphereIndexOffset;sphereSubmesh.BaseVertexLocation = sphereVertexOffset;d3dUtil::SubmeshGeometry cylinderSubmesh;cylinderSubmesh.IndexCount = (UINT)cylinder.Indices32.size();cylinderSubmesh.StartIndexLocation = cylinderIndexOffset;cylinderSubmesh.BaseVertexLocation = cylinderVertexOffset;//提取出所需的顶点元素,再将所有网络的顶点装进一个顶点缓冲区auto totalVertexCount = box.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 = DirectX::XMFLOAT4(DirectX::Colors::DarkGreen);}for (size_t i = 0; i < sphere.Vertices.size(); i++, k++){vertices[k].Pos = sphere.Vertices[i].Position;vertices[k].Color = DirectX::XMFLOAT4(DirectX::Colors::ForestGreen);}for (size_t i = 0; i < cylinder.Vertices.size(); i++, k++){vertices[k].Pos = cylinder.Vertices[i].Position;vertices[k].Color = DirectX::XMFLOAT4(DirectX::Colors::Crimson);}std::vector<std::uint16_t> indices;indices.insert(indices.end(), box.GetIndices16().begin(), box.GetIndices16().end());indices.insert(indices.end(), sphere.GetIndices16().begin(), sphere.GetIndices16().end());indices.insert(indices.end(), cylinder.GetIndices16().begin(), cylinder.GetIndices16().end());const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);auto geo = std::make_unique<d3dUtil::MeshGeometry>();geo->Name = "shapeGeo";ThrowIfFailed(D3DCreateBlob(vbByteSize, geo->VertexBufferCPU.GetAddressOf()));CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);ThrowIfFailed(D3DCreateBlob(ibByteSize, geo->IndexBufferCPU.GetAddressOf()));CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);geo->VertexByteStride = sizeof(Vertex);geo->VertexByteSize = vbByteSize;geo->IndexBufferByteSize = ibByteSize;geo->IndexFormat = DXGI_FORMAT_R16_UINT;geo->DrawArgs["box"] = boxSubmesh;geo->DrawArgs["sphere"] = sphereSubmesh;geo->DrawArgs["cylinder"] = cylinderSubmesh;m_Geometries[geo->Name] = std::move(geo);
}

m_Geometries被定义为

std::unordered_map<std::string, std::unique_ptr<d3dUtil::MeshGeometry>> m_Geometries;

这是这本书的通用模式,为每个几何体,PSO、纹理和着色器等创建新的变量名是一件很麻烦的事情,所以使用无序映射表,并根据名称在常数时间内寻找和引用所需的对象。

渲染项

从上面可知,我们将所有的顶点和index都存在了一个缓冲区中。接下来我们需要根据渲染项来分别的得到这个场景中的不同的world matrix。

我们需要有一个类似的数组去存储所有的渲染项

std::vector<std::unique_ptr<RenderItem>> mAllRitems;

文中还设定了一个数组去存储所有的不透明物体

std::vector<std::unique_ptr<RenderItem>> mAllRitems;
std::vector<std::unique_ptr<RenderItem>> mOpaqueRitems;void ShapesApp::BuildRenderItems()
{std::unique_ptr<RenderItem> boxRitem = std::make_unique<RenderItem>();boxRitem->Geo = m_Geometries["shapeGeo"].get();DirectX::XMStoreFloat4x4(&boxRitem->World, DirectX::XMMatrixScaling(2.0f, 2.0f, 2.0f) *DirectX::XMMatrixTranslation(0.0f, 0.5f, 0.0f));boxRitem->IndexCount = boxRitem->Geo->DrawArgs["box"].IndexCount;boxRitem->BaseVertexLocation = boxRitem->Geo->DrawArgs["box"].BaseVertexLocation;boxRitem->StartIndexLocation = boxRitem->Geo->DrawArgs["box"].StartIndexLocation;boxRitem->ObjCBIndex = 0;boxRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;mAllRitems.push_back(std::move(boxRitem));UINT objCBIndex = 1;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>();DirectX::XMMATRIX leftCylWorld = DirectX::XMMatrixTranslation(-5.0f, 1.5f, -10.0f + i * 5.0f);DirectX::XMMATRIX rightCylWorld = DirectX::XMMatrixTranslation(5.0f, 1.5f, -10.0f + i * 5.0f);DirectX::XMMATRIX leftSphereWorld = DirectX::XMMatrixTranslation(-5.0f, 3.5f, -10.0f + i * 5.0f);DirectX::XMMATRIX rightSphereWorld = DirectX::XMMatrixTranslation(5.0f, 3.5f, -10.0f + i * 5.0f);DirectX::XMStoreFloat4x4(&leftCylRitem->World, leftCylWorld);leftCylRitem->ObjCBIndex = objCBIndex++;leftCylRitem->Geo = m_Geometries["shapeGeo"].get();leftCylRitem->IndexCount = leftCylRitem->Geo->DrawArgs["cylinder"].IndexCount;leftCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;leftCylRitem->BaseVertexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;leftCylRitem->StartIndexLocation = leftCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;DirectX::XMStoreFloat4x4(&rightCylRitem->World, rightCylWorld);rightCylRitem->Geo = m_Geometries["shapeGeo"].get();rightCylRitem->IndexCount = rightCylRitem->Geo->DrawArgs["cylinder"].IndexCount;rightCylRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;rightCylRitem->BaseVertexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].BaseVertexLocation;rightCylRitem->StartIndexLocation = rightCylRitem->Geo->DrawArgs["cylinder"].StartIndexLocation;rightCylRitem->ObjCBIndex = objCBIndex++;DirectX::XMStoreFloat4x4(&leftSphereRitem->World, leftSphereWorld);leftSphereRitem->Geo = m_Geometries["shapeGeo"].get();leftSphereRitem->IndexCount = leftSphereRitem->Geo->DrawArgs["sphere"].IndexCount;leftSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;leftSphereRitem->BaseVertexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;leftSphereRitem->StartIndexLocation = leftSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;leftSphereRitem->ObjCBIndex = objCBIndex++;DirectX::XMStoreFloat4x4(&rightSphereRitem->World, rightSphereWorld);rightSphereRitem->Geo = m_Geometries["shapeGeo"].get();rightSphereRitem->IndexCount = rightSphereRitem->Geo->DrawArgs["sphere"].IndexCount;rightSphereRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;rightSphereRitem->BaseVertexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;rightSphereRitem->StartIndexLocation = rightSphereRitem->Geo->DrawArgs["sphere"].StartIndexLocation;rightSphereRitem->ObjCBIndex = objCBIndex++;mAllRitems.push_back(std::move(leftCylRitem));mAllRitems.push_back(std::move(rightCylRitem));mAllRitems.push_back(std::move(leftSphereRitem));mAllRitems.push_back(std::move(rightSphereRitem));}for (auto& e : mAllRitems){mOpaqueRitems.push_back(std::move(e));}
}

帧资源和常量缓冲区

我们在之前创建了一个FrameResource,其中有帧资源的缓冲区还有渲染项的缓冲区。

std::unique_ptr<UploadBuffer<PassConstant>> PassCB = nullptr;
std::unique_ptr<UploadBuffer<ObjectConsts>> ObjectCB = nullptr;

如果有3个帧资源与n个渲染项,那么就应存在3n个物体常量缓冲区以及3个渲染过程常量缓冲区(pass constant buffer)。因此,我们也就需要创建3(n+1)个常量缓冲区视图(CBV)这样一来,我们还要修改CBV堆以容纳额外的描述符。

void ShapesApp::BuildConstHeadDescriptor()
{UINT objCount = (UINT)mOpaqueRitems.size();//我们需要为每个帧资源中的每个物体都创建一个cbv描述符//为了容纳每个帧资源中的渲染过程CBV而+1UINT numDescriptors = (objCount + 1) * gNumFrameResources;m_PassCbvOffset = gNumFrameResources * objCount;D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc{};cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;cbvHeapDesc.NodeMask = 0;cbvHeapDesc.NumDescriptors = numDescriptors;m_Device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(m_CbvDescriptorHeap.GetAddressOf()));
}

现在,我们就可以用下列代码来填充CBV堆,其中描述符0至描述符n-1包含了第0个帧资源的物体CBV,描述符n至描述符2n-1容纳了第1个帧资源的物体CBV,以此类推,描述符2n至描述符3n-1包含了第2个帧资源的物体CBV。最后,3n、3n+1以及3n+2分别存有第0个、第1个和第2个帧资源的渲染过程cbv

void ShapesApp::BuildConstantBufferViews()
{UINT objCBByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConsts));UINT objCount = (UINT)mOpaqueRitems.size();for (UINT i = 0; i < gNumFrameResources; i++){ID3D12Resource* objectCB = (ID3D12Resource*)*m_FrameResources[i]->ObjectCB;for (UINT j = 0; j < objCount; j++){D3D12_GPU_VIRTUAL_ADDRESS cbvAdress = objectCB->GetGPUVirtualAddress();cbvAdress += j * objCBByteSize;D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc;cbvDesc.BufferLocation = cbvAdress;cbvDesc.SizeInBytes = objCBByteSize;int heapIndex = i * objCount + j;auto handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(m_CbvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());handle.Offset(heapIndex, m_CbvSrvUavDescriptorSize);m_Device->CreateConstantBufferView(&cbvDesc, handle); }}UINT passCBByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(PassConstant));for (UINT frameIndex = 0; frameIndex < gNumFrameResources; frameIndex++){ID3D12Resource* passCB = (ID3D12Resource*)*m_FrameResources[frameIndex]->PassCB;D3D12_GPU_VIRTUAL_ADDRESS cbvAdress = passCB->GetGPUVirtualAddress();cbvAdress += frameIndex * passCBByteSize;D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc{};cbvDesc.BufferLocation = cbvAdress;cbvDesc.SizeInBytes = passCBByteSize;CD3DX12_CPU_DESCRIPTOR_HANDLE handle = CD3DX12_CPU_DESCRIPTOR_HANDLE(m_CbvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());handle.Offset(m_PassCbvOffset + frameIndex, m_CbvSrvUavDescriptorSize);m_Device->CreateConstantBufferView(&cbvDesc, handle);}
}

前文讲过,通过调用ID3DDescriptorHeap::GetCPUDescriptorHandleForHeapStart()方法,我们可以获得堆中第一个描述符的语柄。然而,我们当前堆内所存放的描述符已不止一个,所以仅使用此方法并不能找到其他描述符的语柄。

此时我们需要能够偏移到堆内的其它描述符处,为此需要了解到达堆内下一个相邻描述符的增量。这个增量的大小其实是由硬件来确定的,所以我们必须从设备上查询相关的信息。此外,该增量还依赖于堆的具体类型。

m_RtvDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_DsvDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
m_CbvSrvUavDescriptorSize = m_Device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

只要知道了相邻描述符之间的增量大小,就能通过两种CD3DX12_CPU_DESCRIPTOR_HANDLE::Offset偏移到第n个描述符的语病处

handle.Offset(heapIndex, m_CbvSrvUavDescriptorSize);
handle.Offset(m_PassCbvOffset + frameIndex, m_CbvSrvUavDescriptorSize);

绘制场景

我们现在已经完成了什么呢?

  1. 生成了数据,并生成了对应的geo也就是对应的顶点和索引缓冲区
  2. 生成了constant buffer,包含的帧资源有pass和物体的渲染项

接下来我们完成下面的一些东西

void BuildRootSignature();
void BuildShadersAndInputLayout();
void BuildPSO();
  1. RootSignature 申请对应的寄存器和shader 中的register绑定。这里要定义两个Parameter,因为我们渲染项不需要频繁的更新
void ShapesApp::BuildRootSignature()
{CD3DX12_DESCRIPTOR_RANGE passCbv;passCbv.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);CD3DX12_DESCRIPTOR_RANGE objCbv;objCbv.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 1);CD3DX12_ROOT_PARAMETER rootParameters[2];rootParameters[0].InitAsDescriptorTable(1, &passCbv);rootParameters[1].InitAsDescriptorTable(1, &objCbv);CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc(2, rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);ComPtr<ID3DBlob> error;ComPtr<ID3DBlob> rootSignature;HRESULT hr = D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1_0, rootSignature.GetAddressOf(), error.GetAddressOf());if (error != nullptr){OutputDebugStringA((char*)error->GetBufferPointer());}ThrowIfFailed(hr);m_Device->CreateRootSignature(0, rootSignature->GetBufferPointer(), rootSignature->GetBufferSize(), IID_PPV_ARGS(m_RootSignature.GetAddressOf()));
}
  1. 创建shader 和对应的D3D12_INPUT_ELEMENT_DESC
cbuffer passCBuffer : register(b0)
{
float4x4 View;
float4x4 InvView;
float4x4 Proj;
float4x4 InvProj;
float4x4 ViewProj;
float4x4 InvViewProj;
float3 EyePosW;
float cbPerObjectPad1;
float2 RenderTargetSize;
float2 InvRenderTargetSize;
float NearZ;
float FarZ;
float TotalTime;
float DeltaTime;
};
cbuffer objCBuffer : register(b1)
{
float4x4 worldProj;
};struct VertexOut
{
float4 pos : SV_Position;
float4 color : COLOR;
};struct VertexIn
{
float3 pos : POSITION;
float4 color : COLOR;
};VertexOut VS(VertexIn input)
{VertexOut output;float4 posW = mul(float4(input.pos, 1.0f), worldProj);output.pos = mul(posW, ViewProj);output.color = input.color;
}float4 PS(VertexOut PSInput) : SV_Target
{return PSInput.color;
}
void ShapesApp::BuildShadersAndInputLayout()
{m_InputElementDesc = {{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0},{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0}};m_VSShader = d3dUtil::CompileShader(L"source/shader/ShapeApp.usf", nullptr, "VS", "vs_5_0");m_PSShader = d3dUtil::CompileShader(L"source/shader/ShapeApp.usf", nullptr, "PS", "ps_5_0");
}
  1. 创建对应的pipeline state
std::unordered_map<std::string, ComPtr<ID3D12PipelineState>> m_PSOs;void ShapesApp::BuildPSO()
{D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};psoDesc.pRootSignature = m_RootSignature.Get();psoDesc.VS = CD3DX12_SHADER_BYTECODE(m_VSShader.Get());psoDesc.PS = CD3DX12_SHADER_BYTECODE(m_PSShader.Get());psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);psoDesc.SampleMask = UINT_MAX;psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);psoDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);psoDesc.InputLayout = {m_InputElementDesc.data(), (UINT)m_InputElementDesc.size()};psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;psoDesc.NumRenderTargets = 1;psoDesc.RTVFormats[0] = m_BackBufferFormat;psoDesc.DSVFormat = m_DepthStencilFormat;psoDesc.SampleDesc.Count = m4xMsaaState ? 4 : 1;psoDesc.SampleDesc.Quality = m4xMsaaState ? m4xMsaaQuality - 1 : 0;psoDesc.NodeMask = 0;ComPtr<ID3D12PipelineState> pipelineStaet;ThrowIfFailed(m_Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(pipelineStaet.GetAddressOf())));m_PSOs["ShapeApp"] = std::move(pipelineStaet);
}
bool ShapesApp::Initialize()
{if (!D3DApp::Initialize())return false;ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), nullptr));BuildShapeGeometry();BuildRenderItems();BuildConstHeadDescriptor();BuildFrameResources();BuildConstantBufferViews();BuildRootSignature();BuildShadersAndInputLayout();BuildPSO();ThrowIfFailed(m_CommandList->Close());ID3D12CommandList* cmdLists[] = { m_CommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists);FlushCommandQueue();return true;
}

开始绘制

void ShapesApp::OnResize()
{D3DApp::OnResize();DirectX::XMMATRIX p = DirectX::XMMatrixPerspectiveFovLH(0.5f * DirectX::XM_PI, AspectRatio(), 1.0f, 1000.0f);DirectX::XMStoreFloat4x4(&m_Proj, p);
}
void ShapesApp::Update(const GameTimer& gt)
{//循环往复地获取帧资源循环数组中的元素m_CurrentFrameResourceIndex = (m_CurrentFrameResourceIndex + 1) % gNumFrameResources;m_CurrentFrameResource = m_FrameResources[m_CurrentFrameResourceIndex].get();//gpu端是否已经执行完处理当前帧资源的所有命令呢?//如果还没有就令cpu等待,直到gpu完成命令的执行并抵达这个围栏点if (m_CurrentFrameResource->Fence != 0 && m_Fence->GetCompletedValue() < m_CurrentFrameResource->Fence){HANDLE eventHandle = CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);ThrowIfFailed(m_Fence->SetEventOnCompletion(m_CurrentFrameResource->Fence, eventHandle));WaitForSingleObject(eventHandle, INFINITE);CloseHandle(eventHandle);}float x = m_Radius * sinf(m_Phi) * cosf(m_Theta);float y = m_Radius * cosf(m_Phi);float z = m_Radius * sinf(m_Phi) * sinf(m_Theta);DirectX::XMVECTOR pos = DirectX::XMVectorSet(x, y, z, 1.0f);DirectX::XMVECTOR target = DirectX::XMVectorZero();DirectX::XMVECTOR up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 1.0f);DirectX::XMMATRIX view = DirectX::XMMatrixLookAtLH(pos, target, up);DirectX::XMStoreFloat4x4(&m_View, view);UpdateMainPassCB(gt);UpdateObjectCBs(gt);}
void ShapesApp::DrawRenderItems(ID3D12GraphicsCommandList* cmdList, const std::vector<RenderItem*>& ritems)
{for (UINT i = 0; i < ritems.size(); i++){auto ri = ritems[i];D3D12_VERTEX_BUFFER_VIEW vertexBufferView = ri->Geo->VertexBufferView();D3D12_INDEX_BUFFER_VIEW indexBufferView = ri->Geo->IndexBufferView();cmdList->IASetVertexBuffers(0, 1, &vertexBufferView);cmdList->IASetIndexBuffer(&indexBufferView);cmdList->IASetPrimitiveTopology(ri->PrimitiveType);UINT objCbvIndex = m_CurrentFrameResourceIndex * (UINT)mOpaqueRitems.size() + ri->ObjCBIndex;CD3DX12_GPU_DESCRIPTOR_HANDLE handle(m_CbvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());handle.Offset(objCbvIndex, m_CbvSrvUavDescriptorSize);cmdList->SetGraphicsRootDescriptorTable(1, handle);cmdList->DrawIndexedInstanced(ri->IndexCount, 1, ri->StartIndexLocation, ri->BaseVertexLocation, 0);}
}void ShapesApp::Draw(const GameTimer& gt)
{auto currentCmdAlloc = m_CurrentFrameResource->CmdListAlloc;ThrowIfFailed(currentCmdAlloc->Reset());ThrowIfFailed(m_CommandList->Reset(currentCmdAlloc.Get(), m_PSOs["ShapeApp"].Get()));m_CommandList->RSSetViewports(1, &m_ViewPort);m_CommandList->RSSetScissorRects(1, &m_ScissorRect);ID3D12Resource* frameTar = CurrentBackBuffer();D3D12_CPU_DESCRIPTOR_HANDLE renderTarHandle = CurrentBackBufferHandle();D3D12_CPU_DESCRIPTOR_HANDLE depthTarHandle = DepthStencilView();CD3DX12_RESOURCE_BARRIER renderTarBarrier = CD3DX12_RESOURCE_BARRIER::Transition(frameTar, D3D12_RESOURCE_STATE_PRESENT,D3D12_RESOURCE_STATE_RENDER_TARGET);m_CommandList->ResourceBarrier(1, &renderTarBarrier);m_CommandList->OMSetRenderTargets(1, &renderTarHandle, true, &depthTarHandle);m_CommandList->ClearRenderTargetView(renderTarHandle, DirectX::Colors::LightSteelBlue, 0, nullptr);m_CommandList->ClearDepthStencilView(depthTarHandle, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);//绑定到渲染流水线的descriptorheapID3D12DescriptorHeap* descritptorHeaps[] = { m_CbvDescriptorHeap.Get()};m_CommandList->SetDescriptorHeaps(_countof(descritptorHeaps), descritptorHeaps);m_CommandList->SetGraphicsRootSignature(m_RootSignature.Get());int passCbvIndex = m_PassCbvOffset + m_CurrentFrameResourceIndex;CD3DX12_GPU_DESCRIPTOR_HANDLE handle(m_CbvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());handle.Offset(passCbvIndex, m_CbvSrvUavDescriptorSize);m_CommandList->SetGraphicsRootDescriptorTable(0, handle);DrawRenderItems(m_CommandList.Get(), mOpaqueRitems);renderTarBarrier = CD3DX12_RESOURCE_BARRIER::Transition(frameTar, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT);m_CommandList->ResourceBarrier(1, &renderTarBarrier);ThrowIfFailed(m_CommandList->Close());ID3D12CommandList* cmdLists[] = { m_CommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists);ThrowIfFailed(m_SwapChain->Present(0, 0));m_CurrentBackBufferIndex = (m_CurrentBackBufferIndex + 1) % SwapChainBufferCount;m_CurrentFrameResource->Fence = ++m_CurrentFence;m_CommandQueue->Signal(m_Fence.Get(), m_CurrentFence);
}

改为D3D_PRIMITIVE_TOPOLOGY_LINESTRIP

加入mouse的操作

void ShapesApp::OnMouseDown(WPARAM btnState, int x, int y)
{m_MousePos = {x, y};SetCapture(mhMainWnd);
}void ShapesApp::OnMouseMove(WPARAM btnState, int x, int y)
{if ((btnState & MK_LBUTTON) !=0 ){float dx = DirectX::XMConvertToRadians(0.25f * static_cast<float>(x - m_MousePos.x));float dy = DirectX::XMConvertToRadians(0.25f * static_cast<float>(y - m_MousePos.y));m_Phi += dy;m_Theta += dx;m_Phi = MathHelper::Clamp(m_Phi, 0.1f, MathHelper::Pi - 0.1f);}else if ((btnState & MK_RBUTTON) != 0){float dx = 0.005f * static_cast<float>(x - m_MousePos.x);float dy = 0.005f * static_cast<float>(y - m_MousePos.y);m_Radius += (dx - dy);m_Radius = MathHelper::Clamp(m_Radius, 3.0f, 15.0f);}m_MousePos = {x, y};
}void ShapesApp::OnMouseUp(WPARAM btnState, int x, int y)
{ReleaseCapture();
}

细探根签名

根签名定义了:在绘制调用之前,需要绑定到渲染流水线的资源,以及这些资源应如何映射到着色器的输入寄存器中。选择绑定到流水线上的资源要根据着色器程序的具体需求。从PSO被创建之时起,根签名和着色器程序的组合就开始生效。

根参数

根签名是由一系列根参数定义而成。到目前为止,我们只创建过存有一个描述符表的根参数。然而根参数实际上有三个类型可以选择:

  1. 描述符表:描述符表引用的是描述符堆中的一块连续范围,用于确定要绑定的资源。
  2. 根描述符(又称为内联描述符):通过直接设置根描述符即可指示要绑定的资源,而且无需将它存于描述符堆中。但是,只有常量缓冲区CBV,这也就意味着SRV并不能作为根描述符来实现资源绑定。
  3. 根常量:借助根常量可以直接绑定一系列32位的常量值。

可放入一个根签名的数据以64DWORD为限。3中根参数类型占用的情况如下:

  1. 描述符表:每个描述符表占用1DWORD
  2. 根描述符:每个根描述符(64为的GPU虚拟地址)占用2DWORD。
  3. 根常量:每个常量32位,占用1DWORD。

typedef struct D3D12_ROOT_PARAMETER{D3D12_ROOT_PARAMETER_TYPE ParameterType;union {D3D12_ROOT_DESCRIPTOR_TABLE DescriptorTable;D3D12_ROOT_CONSTANTS Constants;D3D12_ROOT_DESCRIPTOR Descriptor;} 	;D3D12_SHADER_VISIBILITY ShaderVisibility;} 	D3D12_ROOT_PARAMETER;
  1. ParameterType
    1. 指示根参数的类型
enum D3D12_ROOT_PARAMETER_TYPE{D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE	= 0,D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS	= ( D3D12_ROOT_PARAMETER_TYPE_DESCRIPTOR_TABLE + 1 ) ,D3D12_ROOT_PARAMETER_TYPE_CBV	= ( D3D12_ROOT_PARAMETER_TYPE_32BIT_CONSTANTS + 1 ) ,D3D12_ROOT_PARAMETER_TYPE_SRV	= ( D3D12_ROOT_PARAMETER_TYPE_CBV + 1 ) ,D3D12_ROOT_PARAMETER_TYPE_UAV	= ( D3D12_ROOT_PARAMETER_TYPE_SRV + 1 ) } 	D3D12_ROOT_PARAMETER_TYPE;
  1. DescriptorTable/Constants/Descriptor:描述根参数的结构体
  2. ShaderVisibility:指定此参数在着色器程序的可见性。此书一般会采用D3D12_SHADER_VISIBILITY_ALL枚举项。举个例子:如果知道某种资源只会在像素着色器中使用,我们就可把此资源的这一项指定为D3D12_SHADER_VISIBILITY_PIXEL。限制根参数的可见性可能使程序的性能得到优化
enum D3D12_SHADER_VISIBILITY{D3D12_SHADER_VISIBILITY_ALL	= 0,D3D12_SHADER_VISIBILITY_VERTEX	= 1,D3D12_SHADER_VISIBILITY_HULL	= 2,D3D12_SHADER_VISIBILITY_DOMAIN	= 3,D3D12_SHADER_VISIBILITY_GEOMETRY	= 4,D3D12_SHADER_VISIBILITY_PIXEL	= 5,D3D12_SHADER_VISIBILITY_AMPLIFICATION	= 6,D3D12_SHADER_VISIBILITY_MESH	= 7} 	D3D12_SHADER_VISIBILITY;

描述符表

通过填写D3D12_ROOT_PARAMETER结构体的成员DescriptorTable,即可进一步将根参数的类型定义为描述符表(descriptor table)

typedef struct D3D12_ROOT_DESCRIPTOR_TABLE
{
UINT NumDescriptorRanges;
_Field_size_full_(NumDescriptorRanges)  const D3D12_DESCRIPTOR_RANGE *pDescriptorRanges;
} 	D3D12_ROOT_DESCRIPTOR_TABLE;

上述表明,该结构体指定了一个D3D12_DESCRIPTOR_RANGE类型数组,以及该数组中元素的数量。

typedef struct D3D12_DESCRIPTOR_RANGE
{
D3D12_DESCRIPTOR_RANGE_TYPE RangeType;
UINT NumDescriptors;
UINT BaseShaderRegister;
UINT RegisterSpace;
UINT OffsetInDescriptorsFromTableStart;
} 	D3D12_DESCRIPTOR_RANGE;
  1. RangeType:下列枚举类型的成员之一,指示此范围中的描述符类型:
enum D3D12_DESCRIPTOR_RANGE_TYPE{D3D12_DESCRIPTOR_RANGE_TYPE_SRV	= 0,D3D12_DESCRIPTOR_RANGE_TYPE_UAV	= ( D3D12_DESCRIPTOR_RANGE_TYPE_SRV + 1 ) ,D3D12_DESCRIPTOR_RANGE_TYPE_CBV	= ( D3D12_DESCRIPTOR_RANGE_TYPE_UAV + 1 ) ,D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER	= ( D3D12_DESCRIPTOR_RANGE_TYPE_CBV + 1 ) } 	D3D12_DESCRIPTOR_RANGE_TYPE;
  1. NumDescriptors:范围内描述符的数量
  2. BaseShaderRegister:此描述符范围将要绑定到的基准着色器寄存器(base shader register)。比方说,如果你将NumDescriptors 设为3,把BaseShaderRegister 置为1,同时描述符范围的类型为CBV,那么就是按照以下方式与HLSL寄存器相绑定。

  1. RegisterSpace: 此属性将使你能够在不同的寄存器空间中指定着色器寄存器。例如,下列代码中的两种资源看起来似乎重复使用了寄存器槽t0,但实际上却并非如此,因为它们各自存在于不同的空间中:

如果着色器中没有显示指定空间寄存器,默认为space0,但是对于资源数组来说,使用多重寄存器空间会更加方便,尤其是在数组大小未知的情况下

  1. OffsetInDescriptorsFromTableStart: 此描述符范围距离描述符表起始地址的偏移量。由于我们可能将各种类型的描述符混合放置在一个描述符表中,所以会把寄存器槽参数初始化为:一个存有一系列D3D12_DESCRIPTOR_RANGE实例的描述符表。假设我们以“2个CBV,3个SRV和1个UAV”这3个描述符范围的顺序指定了由6个描述符构成的表,则此表的定义为:
CD3DX12_DESCRIPTOR_RANGE descRange[3];
descRange[0].Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 2, 0, 0,0 // 到此描述符表起始地址的偏移量);
descRange[1].Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 3, 0, 2);
descRange[2].Init(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 1, 0, 0, 5);
rootParameters[0].InitAsDescriptorTable(3, descRange, D3D12_SHADER_VISIBILITY_ALL);

上面配置的描述符表共存有6个描述符,而应用程序期望绑定描述符堆中一块连续的描述符范围,其中依次包含2个CBV,3个SRV和1个UAV。我们可以看到所有类型的描述符表都是以寄存器0作为基准寄存器,但是却并没有发生寄存器重叠冲突,这是因为CBV,SRV和UAV都分别被绑定在不同类型的寄存器上,并始于各自的寄存器0.

我们能通过指定D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND,来计算偏移量,可以看到下面的辅助方法就是用了这个

inline void Init(
D3D12_DESCRIPTOR_RANGE_TYPE rangeType,
UINT numDescriptors,
UINT baseShaderRegister,
UINT registerSpace = 0,
UINT offsetInDescriptorsFromTableStart =
D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) noexcept
{Init(*this, rangeType, numDescriptors, baseShaderRegister, registerSpace, offsetInDescriptorsFromTableStart);
}static inline void Init(
_Out_ D3D12_DESCRIPTOR_RANGE &range,
D3D12_DESCRIPTOR_RANGE_TYPE rangeType,
UINT numDescriptors,
UINT baseShaderRegister,
UINT registerSpace = 0,
UINT offsetInDescriptorsFromTableStart =
D3D12_DESCRIPTOR_RANGE_OFFSET_APPEND) noexcept
{range.RangeType = rangeType;range.NumDescriptors = numDescriptors;range.BaseShaderRegister = baseShaderRegister;range.RegisterSpace = registerSpace;range.OffsetInDescriptorsFromTableStart = offsetInDescriptorsFromTableStart;
}

根描述符

通过填写结构体D3D12_ROOT_DESCRIPTOR Descriptor,即可将根参数的类型进一步定义为根描述符(root descriptor)。根描述符使用较多,可以使用辅助方法InitAsConstantBufferView来创建根CBV

typedef struct D3D12_ROOT_DESCRIPTOR
{
UINT ShaderRegister;
UINT RegisterSpace;
} 	D3D12_ROOT_DESCRIPTOR;
  1. ShaderRegister:此描述符将要绑定的着色器寄存器。例如,如果将它指定为2,而且此参数是一个CBV,则此根参数将被映射到register(b2)中的常量缓冲区
  1. RegisterSpace: 和D3D12_DESCRIPTOR_RANGE一样。

他就是通过commandlist去直接的绑定对应的resource.也就是他不用去创建对应的descriptor heap直接的通过这种形式去绑定对应的资源

rootParameters[0].InitAsConstantBufferView(0);
inline void InitAsConstantBufferView(
UINT shaderRegister,
UINT registerSpace = 0,
D3D12_SHADER_VISIBILITY visibility = D3D12_SHADER_VISIBILITY_ALL) noexcept
{InitAsConstantBufferView(*this, shaderRegister, registerSpace, visibility);
}

根常量

设置根常量仍要将数据映射到着色器视角中的常量缓冲区 – 不需要描述符堆

通过填写结构体D3D12_ROOT_CONSTANTS的成员Constants,即可进一步将根参数的类型定义为根常量

typedef struct D3D12_ROOT_CONSTANTS
{
UINT ShaderRegister;
UINT RegisterSpace;
UINT Num32BitValues;
} 	D3D12_ROOT_CONSTANTS;

shaderRegister,RegisterSpace和上面说的一样

Num32BitValues:这个设置需要的32位常量的个数。1

// 根常量使用示例:
// 根签名的定义
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
slotRootParameter[0].InitAsConstants(12, 0); // 12个根常量// 根签名是一系列根参数
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, slotRootParameter, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT
);// 应用程序代码部分:
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
);// HLSL代码部分:
// 我们无法获取常量缓冲区中映射有根常量数据的数组元素,所以只能将每个元素分别单独列出
cbuffer cbSettings : register(b0){int gBlurRadius;float w0;float w1;// ... 11个w值 -- 对应weights数组float w10;
}/*
SetGraphicsRoot32BitConstants参数:1.根参数的索引,对应寄存器的槽号2.常量数据个数3.数据指针4.偏移量
*/
virtual void STDMETHODCALLTYPE SetGraphicsRoot32BitConstants( 
_In_  UINT RootParameterIndex,
_In_  UINT Num32BitValuesToSet,
_In_reads_(Num32BitValuesToSet*sizeof(UINT))  const void *pSrcData,
_In_  UINT DestOffsetIn32BitValues) = 0;
  1. RootParameterIndex:我们所设置的根参数的索引,即将根常量绑定到此槽号的寄存器
  2. Num32BitValuesToSet:本次设置的32位常量数据的个数
  3. pSrcData:指向将要设置的32位常量数据数组的指针
  4. DestOffsetIn32BitValues:本次设置的第一个常量数据在常量缓冲区中的偏移量(这个是以index为单位)

也就是:

  1. 我们设置根参数的时候指定了多少个4字节的数据(如上面12个4字节)
  2. 利用cmdList SetGraphicsRoot32BitConstants去指定对应的数据

更复杂的根签名示例

SDK文档建议:根签名中的根参数应当按照变更频率,由高到低排列

Direct3D 12文档建议尽可能避免频繁切换根签名 – 因此好的方法就是令您创建的多个PSO共享同一个根签名。

而且资源最后肯定是通过cmd来绑定的

比如下面的shader情况:

Texture2D gDiffuseMap : register(t0);
cbuffer cb1 : register(b0)
{float4x4 gWorld;float4x4 gTexTransform;
};
cbuffer cb2 : register(b1)
{float4x4 View;float4x4 InvView;float4x4 Proj;float4x4 InvProj;float4x4 ViewProj;float4x4 InvViewProj;float3 EyePosW;float cbPerObjectPad1;float2 RenderTargetSize;float2 InvRenderTargetSize;float NearZ;float FarZ;float TotalTime;float DeltaTime;
};
cbuffer cbMaterial : register(b2)
{float4 gDiffuseAlbedo;float3 gFresnelR0;
};

得到对应的根签名

CD3DX12_ROOT_PARAMETER rootP[4];
CD3DX12_DESCRIPTOR_RANGE texTable;
texTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0);
rootP[0].InitAsDescriptorTable(1, &texTable, D3D12_SHADER_VISIBILITY_ALL);rootP[1].InitAsConstantBufferView(0);
rootP[2].InitAsConstantBufferView(1);
rootP[3].InitAsConstantBufferView(2);CD3DX12_ROOT_SIGNATURE_DESC rootSignature(4, rootP, 0, nullptr
, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

根参数的版本控制

根实参,即我们向根参数传递的实际数值。

陆地与波浪演示程序

该实例会构建一个三角形栅格,并通过将其中的顶点偏移到不同的高度来创建地形。另外,还要使用另一个三角形栅格来表现水,动态的改变其顶点高度来创建波浪。应该就是类似通过两个vertexFactory来维护。

此例程还将针对不同的常量缓冲区而切换所用的根描述符,这使我们能够摆脱设置繁琐的CBV描述符堆。

水面的高度可以用y = f(xz)

来表示,也就是通过xz平面定义高度

我们需要去生成这个四边形,假设宽度为w,深度为d。那么针对m \times n

个顶点所构成的栅格。

  1. 四边形的个数为(m - 1) \times (n - 1)。三角形的个数为2 \times (m-1) \times (n-1)
  2. dx = w / (n-1),dz = d / (m - 1)
  3. 这样我们就可以得到每个顶点的位置(注意这个栅格定义在其局部坐标中,实际上我们也可以通过model去控制其scale)v_{ij} = [-0.5w + j * dx, 0.0, 0.5d - i * dz]

  1. 我们还可以得到针对一个四边形,其对应的两个三角形为:  T1 = (i + 1) * n + j, i * n + j, i * n + j + 1T2 = (i + 1) * n + j, i * n + j + 1, (i + 1) * n + j + 1
GeometryGenerator::MeshData GeometryGenerator::CreateGrid(float width, float depth, uint32 m, uint32 n)
{MeshData meshData;uint32 vertexCount = m * n;float dWidth = width / (float) (n - 1);float dDepth = depth / (float) (m - 1);float du = 1.0f / (float) (n - 1);float dv = 1.0f / (float) (m - 1);for (uint32 i = 0; i < m; i++){for (uint32 j = 0; j < n; j++){Vertex vertex;float x = -0.5f * width + dWidth * (float) j;float z = 0.5f * depth  - dDepth * (float) i;vertex.Position = DirectX::XMFLOAT3(x, 0.0f, z);vertex.TexC = DirectX::XMFLOAT2(du * j, dv * i);vertex.Normal = DirectX::XMFLOAT3(0.0f, 1.0f, 0.0f);vertex.TangentU = DirectX::XMFLOAT3(1.0f, 0.0f, 0.0f);meshData.Vertices.push_back(vertex);}}for (uint32 i = 0; i < m - 1; i++){for (uint32 j = 0; j < n - 1; j++){meshData.Indices32.push_back((i + 1) * n + j);meshData.Indices32.push_back(i * n + j);meshData.Indices32.push_back(i * n + j + 1);meshData.Indices32.push_back((i + 1) * n + j);meshData.Indices32.push_back(i * n + j + 1);meshData.Indices32.push_back((i + 1) * n + j + 1);}}return meshData;
}

我们可以稍微验证一下,针对资源程序,我们只需要修改vertexFactory还有渲染项就可以了

void ShapesApp::BuildShapeGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData box = geoGen.CreateBox(1.5f, 0.5f, 1.5f);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);UINT boxVertexOffset = 0;UINT gridVertexOffset = (UINT)box.Vertices.size();UINT sphereVertexOffset = (UINT)grid.Vertices.size() + gridVertexOffset;UINT cylinderVertexOffset = (UINT)sphere.Vertices.size() + sphereVertexOffset;UINT boxIndexOffset = 0;UINT gridIndexOffset = (UINT)box.Indices32.size();UINT sphereIndexOffset = gridIndexOffset + grid.Indices32.size();UINT cylinderIndexOffset = (UINT)sphere.Indices32.size() + sphereIndexOffset;d3dUtil::SubmeshGeometry boxSubmesh;boxSubmesh.IndexCount = (UINT)box.Indices32.size();boxSubmesh.StartIndexLocation = boxIndexOffset;boxSubmesh.BaseVertexLocation = boxVertexOffset;d3dUtil::SubmeshGeometry gridSubMesh;gridSubMesh.IndexCount = (UINT)box.Indices32.size();gridSubMesh.StartIndexLocation = gridIndexOffset;gridSubMesh.BaseVertexLocation = gridVertexOffset;d3dUtil::SubmeshGeometry sphereSubmesh;sphereSubmesh.IndexCount = (UINT)sphere.Indices32.size();sphereSubmesh.StartIndexLocation = sphereIndexOffset;sphereSubmesh.BaseVertexLocation = sphereVertexOffset;d3dUtil::SubmeshGeometry cylinderSubmesh;cylinderSubmesh.IndexCount = (UINT)cylinder.Indices32.size();cylinderSubmesh.StartIndexLocation = cylinderIndexOffset;cylinderSubmesh.BaseVertexLocation = cylinderVertexOffset;//提取出所需的顶点元素,再将所有网络的顶点装进一个顶点缓冲区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 = DirectX::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 = DirectX::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 = DirectX::XMFLOAT4(DirectX::Colors::ForestGreen);}for (size_t i = 0; i < cylinder.Vertices.size(); i++, k++){vertices[k].Pos = cylinder.Vertices[i].Position;vertices[k].Color = DirectX::XMFLOAT4(DirectX::Colors::Crimson);}std::vector<std::uint16_t> indices;indices.insert(indices.end(), box.GetIndices16().begin(), box.GetIndices16().end());indices.insert(indices.end(), grid.GetIndices16().begin(), grid.GetIndices16().end());indices.insert(indices.end(), sphere.GetIndices16().begin(), sphere.GetIndices16().end());indices.insert(indices.end(), cylinder.GetIndices16().begin(), cylinder.GetIndices16().end());const UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);const UINT ibByteSize = (UINT)indices.size() * sizeof(std::uint16_t);auto geo = std::make_unique<d3dUtil::MeshGeometry>();geo->Name = "shapeGeo";ThrowIfFailed(D3DCreateBlob(vbByteSize, geo->VertexBufferCPU.GetAddressOf()));CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);ThrowIfFailed(D3DCreateBlob(ibByteSize, geo->IndexBufferCPU.GetAddressOf()));CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), vertices.data(), vbByteSize, geo->VertexBufferUploader);geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), indices.data(), ibByteSize, geo->IndexBufferUploader);geo->VertexByteStride = sizeof(Vertex);geo->VertexByteSize = vbByteSize;geo->IndexBufferByteSize = ibByteSize;geo->IndexFormat = DXGI_FORMAT_R16_UINT;geo->DrawArgs["box"] = boxSubmesh;geo->DrawArgs["grid"] = gridSubMesh;geo->DrawArgs["sphere"] = sphereSubmesh;geo->DrawArgs["cylinder"] = cylinderSubmesh;m_Geometries[geo->Name] = std::move(geo);
}...
std::unique_ptr<RenderItem> gridRitem = std::make_unique<RenderItem>();
gridRitem->Geo = m_Geometries["shapeGeo"].get();
gridRitem->World = MathHelper::Identity4x4();
gridRitem->ObjCBIndex = 1;
gridRitem->Geo = m_Geometries["shapeGeo"].get();
gridRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_LINESTRIP;
gridRitem->BaseVertexLocation = gridRitem->Geo->DrawArgs["grid"].BaseVertexLocation;
gridRitem->StartIndexLocation = gridRitem->Geo->DrawArgs["grid"].StartIndexLocation;
gridRitem->IndexCount = gridRitem->Geo->DrawArgs["grid"].IndexCount;
mAllRitems.push_back(std::move(gridRitem));UINT objCBIndex = 2;
...

看起来是对了

应用计算高度的函数

待栅格创建完成后,我们就能从MeshData栅格中获取所需的顶点元素,根据顶点的高度(y坐标)将平坦的栅格变为用于表现山峰起伏的曲面,并为它生成对应的颜色。

void ShapesApp::BuildLandGeometry()
{GeometryGenerator geoGen;GeometryGenerator::MeshData grid = geoGen.CreateGrid(160.0f, 160.0f, 50, 50);std::vector<Vertex> vertices(grid.Vertices.size());for (size_t i = 0; i < grid.Vertices.size(); i++){auto& p = grid.Vertices[i].Position;vertices[i].Pos = p;vertices[i].Pos.y = GetHillHeight(p.x, p.z);//基于顶点高度为它上色if (vertices[i].Pos.y < -10.0f){//沙滩黄色vertices[i].Color = DirectX::XMFLOAT4(1.0f, 0.96f, 0.62f, 1.0f);}else if (vertices[i].Pos.y < 5.0f){//浅黄绿色vertices[i].Color = DirectX::XMFLOAT4(0.48f, 0.77f, 0.46f, 1.0f);}else if (vertices[i].Pos.y < 12.0f){//深黄绿色vertices[i].Color = DirectX::XMFLOAT4(0.1f, 0.48f, 0.19f, 1.0f);}else if (vertices[i].Pos.y < 20.0f){//深棕色vertices[i].Color = DirectX::XMFLOAT4(0.45f, 0.39f, 0.34f, 1.0f);}else{//白雪皑皑vertices[i].Color = DirectX::XMFLOAT4(1.0f, 1.f, 1.f ,1.f);}}auto geo = std::make_unique<d3dUtil::MeshGeometry>();geo->Name = "lanGeo";geo->VertexByteSize = (UINT) sizeof(Vertex) * vertices.size();geo->IndexBufferByteSize = (UINT) sizeof(uint16_t) * grid.GetIndices16().size();ThrowIfFailed(D3DCreateBlob(geo->VertexByteSize, geo->VertexBufferCPU.GetAddressOf()));ThrowIfFailed(D3DCreateBlob(geo->IndexBufferByteSize, geo->IndexBufferCPU.GetAddressOf()));CopyMemory(geo->VertexBufferCPU->GetBufferPointer(), vertices.data(), geo->VertexByteSize);CopyMemory(geo->IndexBufferCPU->GetBufferPointer(), grid.GetIndices16().data(), sizeof(uint16_t) * grid.GetIndices16().size());geo->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), vertices.data(), geo->VertexByteSize, geo->VertexBufferUploader);geo->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(), grid.GetIndices16().data(), geo->IndexBufferByteSize, geo->IndexBufferUploader);geo->VertexByteStride = sizeof(Vertex);d3dUtil::SubmeshGeometry subMesh;subMesh.IndexCount = (UINT)grid.GetIndices16().size();subMesh.BaseVertexLocation = 0;subMesh.StartIndexLocation = 0;geo->DrawArgs["grid"] = subMesh;m_Geometries[geo->Name] = std::move(geo);
}float ShapesApp::GetHillHeight(float x, float z) const
{return 0.3f * (z * sinf(0.1f * x) + x * cosf(0.1f * z));
}void ShapesApp::BuildLandRenderItems()
{std::unique_ptr<RenderItem> landRitem = std::make_unique<RenderItem>();landRitem->World = MathHelper::Identity4x4();landRitem->ObjCBIndex = 0;landRitem->Geo = m_Geometries["lanGeo"].get();landRitem->IndexCount = landRitem->Geo->DrawArgs["grid"].IndexCount;landRitem->StartIndexLocation = landRitem->Geo->DrawArgs["grid"].StartIndexLocation;landRitem->BaseVertexLocation = landRitem->Geo->DrawArgs["grid"].BaseVertexLocation;landRitem->PrimitiveType = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;mAllRitems.push_back(std::move(landRitem));for (size_t i = 0; i < mAllRitems.size(); i++){mOpaqueRitems.push_back(mAllRitems[i].get());}
}

根常量缓冲区视图

这里就是我们不想使用描述符堆去维护了。打算直接的通过根描述符去定义,也就是那个2WORD的方式,它只能用于绑定cbv

CD3DX12_ROOT_PARAMETER rootParameters[2];
rootParameters[0].InitAsConstantBufferView(0);
rootParameters[1].InitAsConstantBufferView(1);CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc(2, rootParameters, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

我们通过InitAsConstantBufferView创建cbv并指定了该根参数将要绑定的shader register。

然后就是通过cmd去绑定资源。可以看到原本复杂的堆操作就变成了简单的gpu地址偏移

/*//绑定到渲染流水线的descriptorheapID3D12DescriptorHeap* descritptorHeaps[] = { m_CbvDescriptorHeap.Get()};m_CommandList->SetDescriptorHeaps(_countof(descritptorHeaps), descritptorHeaps);*/
m_CommandList->SetGraphicsRootSignature(m_RootSignature.Get());
//int passCbvIndex = m_PassCbvOffset + m_CurrentFrameResourceIndex;
/*CD3DX12_GPU_DESCRIPTOR_HANDLE handle(m_CbvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());handle.Offset(passCbvIndex, m_CbvSrvUavDescriptorSize);*/
/*m_CommandList->SetGraphicsRootDescriptorTable(0, handle);*/
ID3D12Resource* frameCBAddress = (ID3D12Resource*)(*m_CurrentFrameResource->PassCB);// drawRenderItem
//UINT objCbvIndex = m_CurrentFrameResourceIndex * (UINT)mOpaqueRitems.size() + ri->ObjCBIndex;
/*CD3DX12_GPU_DESCRIPTOR_HANDLE handle(m_CbvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());handle.Offset(objCbvIndex, m_CbvSrvUavDescriptorSize);cmdList->SetGraphicsRootDescriptorTable(1, handle);*/
ID3D12Resource* cbBuffer= (ID3D12Resource*)(*m_CurrentFrameResource->ObjectCB);
D3D12_GPU_VIRTUAL_ADDRESS gpuAddress = cbBuffer->GetGPUVirtualAddress();
UINT objCBByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConsts));
gpuAddress += objCBByteSize * ri->ObjCBIndex;
cmdList->SetGraphicsRootConstantBufferView(1, gpuAddress);

动态顶点缓冲区

目前为止,我们始终将顶点存在默认的缓冲区资源中,这是静态的,也就是说,我们不能动态的改变此资源中所存的几何体——只能一次性设置好数据,然后GPU读取其中的数据并进行绘制。

此时一种名为动态顶点缓冲区的资源应运而生,它允许用户频繁地更改其中的顶点数据。

我们使用高度函数求高度,如果用这个来绘制波浪,我们应使用之前的grid,并且实时计算

f(x, z, t)。我们需要在短时间内(比如说i每1/30秒)多次更新高度,从而获得平滑的动画效果。

贴一个伪代码吧,实际上就是gen对应的vertexbuffergpu 更改为上传堆缓冲区。

std::unique_ptr<UploadBuffer<Vertex>> WavesVB = nullptr;WavesVB = std::make_unique<UploadBuffer<Vertex>>(device, waveVertCount, false);void LandAndWavesApp::UpdateWaves(const GameTimer& gt)
{// 每隔1/4秒生成一个随机波浪static float t_base = 0.0f;if((mTimer.TotalTime() - t_base) >= 0.25f){t_base += 0.25f;int i = MathHelper::Rand(4, mWaves->RowCount() - 5);int j = MathHelper::Rand(4, mWaves->ColumnCount() - 5);float r = MathHelper::RandF(0.2f, 0.5f);mWaves->Disturb(i, j, r);}// 更新模拟的波浪mWaves->Update(gt.DeltaTime());// 更新波浪顶点缓冲区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();
}

在使用动态缓冲区时会不可避免的产生一些开销,因为必须将新数据从cpu端回传至GPU端显存。

Direct3D在最近的版本中已经引入了新的特性,以减少对动态缓冲区的需求。例如:

  1. 简单的动画可以在顶点着色器中实现。
  2. 可以使用渲染到纹理(render to texture),或者计算着色器 (computer shader) 与顶点纹理拾取 (vertex texture fetch) 等技术实现上述波浪模拟,而且全程皆是在GPU中进行。
  3. 几何着色器为gpu创建或销毁图元提供了支持。
  4. 曲面细分阶段可以通过gpu来对几何体进行镶嵌化处理。
http://www.xdnf.cn/news/18509.html

相关文章:

  • flink常见问题之非法配置异常
  • Hive Metastore和Hiveserver2启停脚本
  • jetson ubuntu 打不开 firefox和chromium浏览器
  • Python 实战:内网渗透中的信息收集自动化脚本(2)
  • 嵌入式LINUX——————网络TCP
  • Mysql InnoDB 底层架构设计、功能、原理、源码系列合集【六、架构全景图与最佳实践】
  • ArcGIS Pro 安装路径避坑指南:从崩溃根源到规范实操(附问题修复方案)
  • 在 CentOS 7 上搭建 OpenTenBase 集群:从源码到生产环境的全流程指南
  • SpringMVC相关自动配置
  • 第四十三天(JavaEE应用ORM框架SQL预编译JDBCMyBatisHibernateMaven)
  • 算法训练营day60 图论⑩ Bellman_ford 队列优化算法、判断负权回路、单源有限最短路
  • Vue 3 useModel vs defineModel:选择正确的双向绑定方案
  • [特殊字符] 在 Windows 新电脑上配置 GitHub SSH 的完整记录(含坑点与解决方案)
  • 简单留插槽的方法
  • 生成一个竖直放置的div,宽度是350px,上面是标题固定高度50px,下面是自适应高度的div,且有滚动条
  • 航空复杂壳体零件深孔检测方法 - 激光频率梳 3D 轮廓检测
  • FFMPEG相关解密,打水印,合并,推流,
  • 鸿蒙中Snapshot分析
  • Vue3+ElementPlus倒计时示例
  • 应用服务器和数据库服务器的区别
  • 机器学习案例——预测矿物类型(数据处理部分)
  • [CISCN2019 华北赛区 Day1 Web5]CyberPunk
  • `sudo apt update` 总是失败
  • Linux问答题:调优系统性能
  • 李宏毅NLP-12-语音分类
  • 基于Labview的旋转机械AI智能诊断系统
  • 2015-2018年咸海流域1km归一化植被指数8天合成数据集
  • html-docx-js 导出word
  • Linux问答题:归档和传输文件
  • 探秘北斗卫星导航系统(BDS):架构、应用与未来蓝图,展现中国力量