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

Direct12第六章

第五章是概念,第六章就开始实际的操作

顶点与输入布局

首先我们得定义vertexbuffer的数据格式

下面是两种类型的顶点格式

struct Vertex1
{
XMFLOAT3 Pos;
XMFLOAT4 Color;
};
struct Vertex2
{
XMFLOAT3 pos;
XMFLOAT3 Normal;
XMFLOAT2 Tex0;
XMFLOAT2 Tex1;
};

定义了顶点结构体之后,我们还需要向Direct3D提供该顶点结构体的描述,使它了解应增怎样来处理结构体中的每个成员。用户提供给Direct3D的这种描述被称为输入布局描述,用结构体D3D12_INPUT_LAYOUT_DESC来表示

typedef struct D3D12_INPUT_LAYOUT_DESC
{
_Field_size_full_(NumElements)  const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
UINT NumElements;
} 	D3D12_INPUT_LAYOUT_DESC;

D3D12_INPUT_ELEMENT_DESC数组中的元素对应着的是顶点结构体中的属性,比如上面1有两个,2有4个

typedef struct D3D12_INPUT_ELEMENT_DESC
{
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D12_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} 	D3D12_INPUT_ELEMENT_DESC;

下面给出了和顶点着色器一一对应的关系

 

  1. SemanticName:语义,与元素相关联的特定字符串。通过语义即可将顶点结构体中的元素与顶点着色器输入签名中的元素一一映射。
  2. SemanticIndex:附加到语义上的索引。此成员的设计动机可从图6.1中看出。例如,顶点结构体中的纹理坐标可能不止一组,而仅在语义名尾加一个索引就可以区分,POSITION和POSITION0等价
  3. Format:在Direct3D中,要通过枚举类型DXGI_FORMAT中的成员来指定顶点元素的格式(即数据类型)。

  4. InputSlot:指定传递元素所用的输入槽索引。Direct3D共支持16个输入槽,上面的定义代表都来自0输入槽。
  5. AlignedByteoffset:在特定的输入槽中中从起始地址开始的偏移量,以字节为单位。
  6. InputSlotClass:还可以指定为instance的渲染方式
  7. InstanceDataStepRate:如果需要采用实例化这种技术,则此参数设为1

 

顶点缓冲区

为了使GPU可以访问顶点数组,就需要把顶点的数据放置在缓冲区的GPU资源里。我们把存储顶点的缓冲区叫做顶点缓冲区。在ue里面就是vertex factory。这个buffer资源就像是之前创建的swapChainBuffer,depthbuffer一样,我们需要给他创建buffer资源,还有引用这个资源的view(也就是描述符)

对于创建资源,按照我们之前的做法是创建一个D3D12_RESOURCE_DESC然后通过device的commitresource来创建一个buffer的资源。

Direct3D 12提供了一个方法

struct CD3DX12_RESOURCE_DESC : public D3D12_RESOURCE_DESC

它派生自 D3D12_RESOURCE_DESC。它有许多的静态方法可以使用,比如下面的Buffer的方法。

下面这个就可以给我们来创建顶点缓冲区,我们只需要输入顶点缓冲的字节数即可。

static inline CD3DX12_RESOURCE_DESC Buffer(UINT64 width,D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE,UINT64 alignment = 0 ) noexcept{return CD3DX12_RESOURCE_DESC( D3D12_RESOURCE_DIMENSION_BUFFER, alignment, width, 1, 1, 1,DXGI_FORMAT_UNKNOWN, 1, 0, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, flags );}

对于静态几何体,每一帧都不会改变,我们一般把他放到默认堆D3D12_HEAP_TYPE_DEFAULT。默认堆就是GPU访问,cpu访问不到。但是我们需要传递数据到这个buffer中,这里就需要使用我们的上传缓冲区。

我们只需要将顶点的数据复制到上传缓冲区,然后复制到真正的顶点缓冲区即可。

这个一个重复性的工作,因为不管是顶点还是其它的什么数据,都是可以通过这个方式创建一个缓冲区,然后将资源通过上传缓冲区来提交到默认的缓冲区中供gpu使用。

因此这里文章的做法是将它放入到d3dUtil中(这里我把.h当预编译的文件)。这里注意,一定不要在cmd执行之前释放上传堆,不然会传不上去,所以这里使用的是引入一个上传堆,保持上传堆的生命周期。

Microsoft::WRL::ComPtr<ID3D12Resource> CreateDefaultBuffer(ID3D12Device* device,ID3D12GraphicsCommandList* cmdList,const void* initData,UINT64 byteSize,Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer
)
{using Microsoft::WRL::ComPtr;ComPtr<ID3D12Resource> defaultBuffer;ThrowIfFailed(device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),D3D12_HEAP_FLAG_NONE,&CD3DX12_RESOURCE_DESC::Buffer(byteSize),D3D12_RESOURCE_STATE_COMMON,nullptr,IID_PPV_ARGS(defaultBuffer.GetAddressOf())));ThrowIfFailed(device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),D3D12_HEAP_FLAG_NONE,&CD3DX12_RESOURCE_DESC::Buffer(byteSize),D3D12_RESOURCE_STATE_COMMON,nullptr,IID_PPV_ARGS(uploadBuffer.GetAddressOf())));D3D12_SUBRESOURCE_DATA subResourceData;subResourceData.pData = initData;subResourceData.RowPitch = byteSize;subResourceData.SlicePitch = byteSize;cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), D3D12_RESOURCE_STATE_COMMON,D3D12_RESOURCE_STATE_COPY_DEST));UpdateSubresources(cmdList, defaultBuffer.Get(), uploadBuffer.Get(), 0, 0, 1, &subResourceData);cmdList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(defaultBuffer.Get(), D3D12_RESOURCE_STATE_COPY_DEST,D3D12_RESOURCE_STATE_COMMON));return defaultBuffer;
}

d3d12_subresource_date结构体的定义为:

typedef struct D3D12_SUBRESOURCE_DATA{const void *pData;LONG_PTR RowPitch;LONG_PTR SlicePitch;} 	D3D12_SUBRESOURCE_DATA;
  1. pData指向数据的地址
  2. RowPitch:对于缓冲区而言,此参数为欲复制数据的字节数。
  3. SlicePitch:对于缓冲区而言,此参数亦为欲复制数据的字节数
Vertex1 vertices[] = {
{XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::White)},
{XMFLOAT3(-1.0f, 1.0f, -1.0f), XMFLOAT4(Colors::Black)},
{XMFLOAT3(1.0f, 1.0f, -1.0f), XMFLOAT4(Colors::Red)},
{XMFLOAT3(+1.0f, -1.0f, -1.0f), XMFLOAT4(Colors::Green)},
};
const UINT64 vbByteSize = 4 * sizeof(Vertex1);
ComPtr<ID3D12Resource> VertexBuffer;
ComPtr<ID3D12Resource> VertexBufferUpload;
VertexBuffer = d3dUtil::CreateDefaultBuffer(device, cmdList, vertices, vbByteSize, VertexBufferUpload);

创建描述符(视图)

接着我们需要给顶点缓冲区创建视图,但是和RTV这种不同,我们无需创建一个堆。而且顶点缓冲区视图是由D3D12_VERTEX_BUFFER_VIEW结构体来表示的。

typedef struct D3D12_VERTEX_BUFFER_VIEW{D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;UINT SizeInBytes;UINT StrideInBytes;} 	D3D12_VERTEX_BUFFER_VIEW;
  1. BufferLocation:待创建视图的顶点缓冲区资源虚拟地址。我们可以通过VertexBuffer->GetGPUVirtualAddress()的方式来得到这个地址
  2. SizeInBytes: 待创建视图的顶点缓冲区大小(用字节表示)
  3. StrideInBytes: 每个顶点元素所占用的字节数

最后我们就可以通过视图来引用这个vertexBuffer的数据,这里我们就可以通过下面的方式来用commitlist来set这个buffer

virtual void STDMETHODCALLTYPE IASetVertexBuffers( 
_In_  UINT StartSlot,
_In_  UINT NumViews,
_In_reads_opt_(NumViews)  const D3D12_VERTEX_BUFFER_VIEW *pViews) = 0;
  1. StartSlot:在绑定多个顶点缓冲区时,所用的起始输入槽(若仅有一个顶点缓冲区,则将其绑定至此槽)。输入槽共有16个,索引为0~15
  2. NumViews:将要与输入槽绑定的顶点缓冲区数量(即视图数组pViews中视图的数量)。如果起始输入槽StartSlot的索引值为k,且我们要绑定n个顶点缓冲区,那么这些缓冲区依次与相绑定。
  3. pViews:指向顶点缓冲区视图数组中第一个元素的指针。
D3D12_VERTEX_BUFFER_VIEW vbv;
vbv.BufferLocation = VertexBuffer->GetGPUVirtualAddress();
vbv.SizeInBytes = sizeof(4 * sizeof(Vertex1));
vbv.StrideInBytes = sizeof(Vertex1);D3D12_VERTEX_BUFFER_VIEW vertexBuffers[1] = {vbv};
cmdList->IASetVertexBuffers(0, _countof(vertexBuffers), vertexBuffers);

本书一般的例子是一个槽去作业。

有了vertex factory以后,我们就需要创建对应的Element去绘制,也就是index,uniform那些(ue的sorry)

接下来我们需要创建drawcall的命令

virtual void STDMETHODCALLTYPE DrawInstanced( 
_In_  UINT VertexCountPerInstance,
_In_  UINT InstanceCount,
_In_  UINT StartVertexLocation,
_In_  UINT StartInstanceLocation) = 0;
  1. VertexCountPerInstance:每个实例要绘制的顶点的数量
  2. InstanceCount用于实现实例化的,因为实例化要求mesh相同,就可以复用这个mesh,如果只有一个实例就设置为1.
  3. StartVertexLocation:指定顶点缓冲区内第一个被绘制顶点的索引(该索引以0为基准)
  4. StartInstanceLocation:用于实现实例化的方式。

 

我们还需要指定绘制什么图元

cmdList->IASetVertexBuffers(0, _countof(vertexBuffers), vertexBuffers);
cmdList->IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
cmdList->DrawInstanced(4, 1, 0, 0);

索引和索引缓冲区

与顶点相似,为了使GPU可以访问索引数组,就需要将它们放置于GPU的缓冲区资源内。我们称存储索引的缓冲区为索引缓冲区。一般vertex factory是共享的,而索引缓冲区就是用来区分绘制的不同的mesh的。同样的索引缓冲区也是一种资源,我们需要给他添加视图。索引缓冲区用D3D12_INDEX_BUFFER_VIEW来表示。

typedef struct D3D12_INDEX_BUFFER_VIEW{D3D12_GPU_VIRTUAL_ADDRESS BufferLocation;UINT SizeInBytes;DXGI_FORMAT Format;} 	D3D12_INDEX_BUFFER_VIEW;
  1. BufferLocation:待创建的索引缓冲区资源虚拟地址,一样的可以通过上面的顶点缓冲区的方式来获得。
  2. SizeInBytes:待创建视图的索引缓冲区大小
  3. Format:索引的格式必须表示为16为索引的DXGI_FORMAT_R16_UINT,或者32位的UINT类型,16位的可以减少内存和带宽的占用,但如果索引值范围超过了16位数据的表达范围,则也只能采用32位索引了。
  4. 我们需要在绘制前绑定到渲染流水线上。
virtual void STDMETHODCALLTYPE IASetIndexBuffer( _In_opt_  const D3D12_INDEX_BUFFER_VIEW *pView) = 0;
std::uint16_t indices[] = {
0, 1, 2,
0, 2, 3,4, 5, 6,
4, 7, 6,4, 5, 1,
4, 1, 0,3, 2, 6,
3, 6, 7,1, 5, 6,
1, 6, 2,4, 0, 3,
4, 3, 7
};
const UINT idByteSize = 36 * sizeof(std::uint16_t);
ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;
ComPtr<ID3D12Resource> IndexBufferUpload = nullptr;
IndexBufferGPU = d3dUtil::CreateDefaultBuffer(device, cmdList, indices, idByteSize, IndexBufferUpload);D3D12_INDEX_BUFFER_VIEW ibv;
ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();
ibv.SizeInBytes = idByteSize;
ibv.Format = DXGI_FORMAT_R16_UINT;cmdList->IASetIndexBuffer(&ibv);

我们还需要使用cmdList->DrawIndexedInstanced来代替DrawInstanced的方法进行绘制。

virtual void STDMETHODCALLTYPE DrawIndexedInstanced( _In_  UINT IndexCountPerInstance,_In_  UINT InstanceCount,_In_  UINT StartIndexLocation,_In_  INT BaseVertexLocation,_In_  UINT StartInstanceLocation) = 0;
  1. IndexCountPerInstance:每个实例将要绘制的索引数量
  2. InstanceCount:用于实现一种被称为实例化的高级技术。就目前而言,我们只绘制一个实例,因而将此值设置为1
  3. StartIndexLocation:指向索引缓冲区的某个元素,将其标记为欲读取的起始索引
  4. BaseVertexLocation:在本次绘制调用读取顶点之前,要为每个索引都加上此整数
  5. StartInstanceLocation:实例化的操作,目前设置为0

理解这些参数,我们思考一个场景:假设有3个欲绘制的物体,一个球体一个立方体以及一个圆柱体。首先,每个物体都有自己的顶点缓冲区以及索引缓冲区。而每个局部的索引缓冲区中的索引,又都引用的是各自局部的顶点缓冲区。

但是这样非常的麻烦,比如ue中就是有一个vertex factory这样,如果有一些顶点是复用的,我们就只需要一份顶点缓冲区。这里我们就可以创建一个全局的索引缓冲区和顶点缓冲区。

 

但是我们在创建的时候,是根据当前物体的局部缓冲区创建的,所以就需要根据顶点数进行索引缓冲区的偏移。比如原先立方体的第一个顶点索引是0,后续就要offset一个球体的顶点。我们在绘制的时候就需要根据索引缓冲区的offset来定位需要绘制的索引。

 

顶点着色器示例

cbuffer cbPerObject : register(b0)
{float4x4 gWorldViewProj;
};void VS(float3 iPosL : POSITION,float4 iColor : COLOR,out float4 oPosH : SV_POSITION,out float4 oColor : COLOR)
{oPosH = mul(float4(iPosL, 1.0f), gWorldViewProj);oColor = iColor;        
}

顶点着色器就是上例中VS函数,值得注意的是,我们可以给顶点着色器起任何名字。其中四个参数,前两个为输入参数,后两个是输出参数(通过out表示)HLSL没有引用和指针的概念,所以需要借助结构体或多个输出参数才能从函数中返回多个值。在HLSL中所有的函数都是inline.

前两个参数分别对应于绘制立方体所自定义顶点结构体的两个数据成员,也构成了顶点着色器的输入签名。参数语义:POISTION和:COLOR用于将顶点结构体中的元素映射到顶点着色器的相应输入参数。通过顶点描述desc中

 

输出参数也附带各自的语义。并以此为纽带将顶点着色器的输出参数映射到下个处理阶段(几何着色器或像素着色器)中所对应的输入参数。注意,SV_POSITION语义比较特殊(SV代表系统值,即System value),他所修饰的顶点着色器输出的元素存有齐次裁剪空间中的顶点位置信息。因此,我们必须为输出位置信息的参数附上SV_POSITION语义,使GPU可以在进行例如裁剪、深度测试和光栅化处理的时候,借此实现其它属性所无法介入的有关运算。对于任何不具有系统值的输出参数而言,我们都可以根据需求以合法的语义名修饰它

 

我们可以把函数的返回类型和输入签名替换为结构体(从而取代过长的参数列表)

cbuffer cbPerObject : register(b0)
{float4x4 gWorldViewProj;
};struct VertexIn
{float3 PosL : POSITION;float4 Color : COLOR;
};struct VertexOut
{float4 PosH : SV_POSITION;float4 Color : COLOR:
};void VS(VertexIn vin)
{VertexOut vout;vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);vout.Color = vin.Color;return vout;       
}

连接输出布局描述符与输入签名

通过上面的图示,输送到渲染流水线的顶点属性与输入布局描述的定义相关联。如果我们传入的顶点数据与顶点着色器所期望的输入不相符,便会导致错误。例如下列顶点着色器的输入签名与顶点数据就是不匹配的:

在创建ID3D12PipelineState对象的时候,必须指定输入布局描述和顶点着色器,而Direct3D则会验证二者是否匹配。

 

事实上,顶点数据与输入签名不需要完全匹配,前提是顶点着色器需要的数据,我们的输出签名一定要提供上。也就是我们可以多数据,但是不能少数据,也不能错数据,下面就是一种不匹配但是正确的情况。

 

 

下面还有一种情况,当顶点结构体(着色器)和输入签名有着匹配的顶点元素,唯独两者的颜色属性不同。这也是正确的Direct3D允许用户对输入寄存器中的数据类型重新加以解释。但是会给警告

 

 

像素着色器

为了计算出三角形中每个像素的属性,我们会在光栅化处理期间对顶点着色器(或几何着色器)输出顶点属性进行插值。随后,再将这些插值数据传至像素着色器中作为它的输入。现假设我们的程序未使用几何着色器,下图展示的即为当前顶点数据所流经的路径。

 

像素着色器与顶点着色器有相似:前者是针对每一个像素片段而运行的函数,后者针对每个顶点而运行的函数。注意这时候的像素片段可能不会传入或留存在后台缓冲区中。例如像素片段可能会在像素着色器中被裁剪掉,或者是在深度测试的时候没有比过另外一个像素片段。

下面是一个简单的像素着色器的代码:

cbuffer cbPerObject : register(b0)
{float4x4 gWorldViewProj;
};struct VertexIn
{float3 PosL : POSITION;float4 Color : COLOR;
};struct VertexOut
{float4 PosH : SV_POSITION;float4 Color : COLOR:
};void VS(VertexIn vin)
{VertexOut vout;vout.PosH = mul(float4(vin.PosL, 1.0f), gWorldViewProj);vout.Color = vin.Color;return vout;       
}float4 PS(VertexOut pin) : SV_Target
{return vout.Color;
}
// or (float4 PosH : SV_POSITION, float4 color : COLOR) : SV_Target

常量缓冲区

创建常量缓冲区

常量缓冲区也是一种gpu的资源,其数据内容可供着色器程序所引用。就像我们在本书中将会学到的纹理等其他类型的缓冲区资源一样,他们都可以被着色器程序所引用。

下面就是常量缓冲区

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

与顶点缓冲区和索引缓冲区不同的是,常量缓冲区通常由CPU每帧更新一次。举个例子,如果摄像机每帧都在不停的移动,那么常量缓冲区也需要在每一帧都随之以新的视图矩阵而更新。所以,我们会把常量缓冲区创建到一个上传堆而非默认堆中,这样做能使我们从CPU端更新常量。

常量缓冲区对硬件也有特别的要求,即常量缓冲区的大小必为最小分配空间256B的整数倍。

我们经常需要用到多个相同类型的常量缓冲区。例如,假设常量缓冲区cbPerObject内存储的是随不同物体而异的常量数据,比如我们需要绘制n个物体,当然每个物体都会有他们各自的proj。

下面是一个例子,创建一个缓冲区资源,并利用它来存储NumElements个常量缓冲区

UINT CalcConstBufferByteSize(UINT byteSize)
{return (byteSize + 255) & ~255;
}
struct ObjectConstants
{
XMFLOAT4X4 WorldViewProj;
};
UINT mElementByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConstants));
ComPtr<ID3D12Resource> mUploadBuffer;
ComPtr<ID3D12Device> pDevice;
pDevice->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),D3D12_HEAP_FLAG_NONE, &CD3DX12_RESOURCE_DESC::Buffer(NumElements * mElementByteSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(mUploadBuffer.GetAddressOf()));

尽管我们已经按照上述方式在程序中分配了256整数倍的字节大小,但是却无须为HLSL结构体中显示填充相应的常量数据,这是因为它会暗中完成这项工作。

 

随Direct3D 12一同推出的是着色模型。其中新引进了一条可用于定义常量缓冲区的HLSL语法。也就是通过ConstantBuffer<>的方式定义一个结构体为常量缓冲区

struct ObjectConsts
{float4x4 gWorldViewProj;uint matIndex;
};
ConstantBuffer<ObjectConsts> gObjectConstants : register(b0);

这样我们就可以通过下面的方式来进行访问了

uint index = gObjectConstants.matIndex;

更新常量缓冲区

由于常量缓冲区是用D3D12_HEAP_TYPE_UPLOAD这种堆类型来创建的,所以我们就能用过cpu为常量缓冲区来更新数据。为此,我们首先要获得指向欲更新资源数据的指针,可用Map方法来做到这一点。

BYTE* mMappedData = nullptr;
mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData));
memcpy(mMappedData, &data, dataSizeInBytes);

第一个参数是子资源的索引,指定了欲映射的子资源。对于缓冲区来说,它自身就是唯一的子资源,所以我们将此参数设置为0。第二个参数是可选项,是个指向D3D12_RANGE结构体的指针,此结构体描述了内存映射范围,若该参数指定是一个空指针,则对整个资源进行映射。第三个参数则借助双重指针,返回待映射资源数据的目标内存块。我们利用memcpy函数将数据从系统内存复制到常量缓冲区。

 

当缓冲区更新完后,我们应在释放映射内存之前对其进行unmap操作。

if (mUploadBuffer != nullptr)
{mUploadBuffer->Unmap(0, nullptr);
}
mUploadBuffer = nullptr;

第一个参数是子资源的索引,指定了将要被取消映射的子资源。若取消映射的是缓冲区,则将其置为0,第二个参数是可选的,指向一个D3D12_RANGE结构体的指针,用于描述取消映射的内存范围,若将它指定为空指针,则取消整个资源的映射。

上传缓冲区辅助函数

这里定义了一个类,令上传缓冲区的相关处理工作更加轻松。它替我们实现了上传缓冲区资源的构造函数、处理资源的映射和取消映射操作,还提供了CopyData方法来更新缓冲区的特定元素。在需要通过CPU修改上传缓冲区中数据的时候,就可以使用这个函数。这个类可用于任何类型的缓冲区,并非针对常量缓冲区。任何的上传缓冲区都可以通过这个类去做。

我们还需要有一个Bool来确定是不是常量缓冲区。如果是常量缓冲区,我们就需要为256的整数倍。

这个类取代了一般的上传堆,也就是它可以拷贝数据到这里面,通过这个类内部维护的数据数组来存储数据。自然它只能有一种数据类型。而且它的数据大小也是确定的,比如维护3个proj数组。

这里有个点很重要,为什么不批量传入,因为这个上传堆是一个泛化的,也就是它可以是别的上传堆,也可以是constant的上传堆。如果是constant的我们在上传的时候需要把他转为256byte的大小。但是这里拷贝数据注意,拷贝的是原数据的大小

#pragma oncetemplate<typename T>
class UploadBuffer
{
public:UploadBuffer(ID3D12Device* device, UINT elementCount, bool isContantBuffer): mIsConstantBuffer(isContantBuffer){mElementByteSize = sizeof(T); // per Element sizeByteif (mIsConstantBuffer){mElementByteSize = d3dUtil::CalcConstBufferByteSize(mElementByteSize);}ThrowIfFailed(device->CreateCommittedResource(&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),D3D12_HEAP_FLAG_NONE,&CD3DX12_RESOURCE_DESC::Buffer(mElementByteSize * elementCount),D3D12_RESOURCE_STATE_GENERIC_READ,nullptr,IID_PPV_ARGS(mUploadBuffer.GetAddressOf())));//只要还会修改当前资源,我们就无须取消映射ThrowIfFailed(mUploadBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));}UploadBuffer(const UploadBuffer&&)=delete;UploadBuffer(const UploadBuffer&)=delete;UploadBuffer& operator=(const UploadBuffer&) = delete;~UploadBuffer(){if (mUploadBuffer != nullptr)mUploadBuffer->Unmap(0, nullptr);mUploadBuffer = nullptr;}operator Microsoft::WRL::ComPtr<ID3D12Resource>(){ return mUploadBuffer; }void CopyData(int elementIndex, const T& data){memcpy(&mMappedData[elementIndex * mElementByteSize], &data, sizeof(T));}
private:Microsoft::WRL::ComPtr<ID3D12Resource> mUploadBuffer;BYTE* mMappedData = nullptr;UINT mElementByteSize = 0;bool mIsConstantBuffer = false;
};

一般来讲,物体的世界矩阵将伴随其旋转移动缩放而改变,观察矩阵则会根据虚拟相机的移动旋转而改变。下面的演示程序中,我们通过鼠标来移动和旋转相机,变换观察角度。因此我们每一帧都要用Update函数。

void ReviewApp::OnMouseMove(WPARAM btnState, int x, int y)
{if ((btnState & MK_LBUTTON) != 0){float dx = DirectX::XMConvertToRadians(0.25f * static_cast<float>( x - mLastMousePos.x ));float dy = DirectX::XMConvertToRadians(0.25f * static_cast<float>( y - mLastMousePos.y ));mTheta += dx;mPhi += dy;mPhi = MathHelper::Clamp(mPhi, 0.1f, MathHelper::Pi - 0.1f);}else if ((btnState & MK_RBUTTON) != 0){float dx = 0.005f * static_cast<float>(x - mLastMousePos.x);float dy = 0.005f * static_cast<float>(y - mLastMousePos.y);mRadius += dx - dy;mRadius = MathHelper::Clamp(mRadius, 3.0f, 15.0f);}
}
void ReviewApp::Update(const GameTimer& gt)
{float x = mRadius * sin(mPhi) * cos(mTheta);float y = mRadius * cos(mPhi);float z = mRadius * sin(mPhi) * sin(mTheta);DirectX::XMVECTOR pos = DirectX::XMVectorSet(x, y, z, 1.0f);DirectX::XMVECTOR tar = DirectX::XMVectorZero();DirectX::XMVECTOR up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);DirectX::XMMATRIX view = DirectX::XMMatrixLookAtLH(pos, tar, up);DirectX::XMStoreFloat4x4(&m_View, view);DirectX::XMMATRIX world = DirectX::XMLoadFloat4x4(&m_World);DirectX::XMMATRIX proj = DirectX::XMLoadFloat4x4(&m_Proj);DirectX::XMMATRIX worldViewProj = world * view * proj;struct ObjectConsts{DirectX::XMFLOAT4X4 worldProjView;}objectConst;DirectX::XMStoreFloat4x4(&objectConst.worldProjView, worldViewProj);UploadBuffer<ObjectConsts> mObjectCB(m_Device.Get(), 1, true);mObjectCB.CopyData(0, objectConst);
}

常量缓冲区描述符

到目前为止,我们已经知道了深度/模板缓冲区、顶点缓冲区以及索引缓冲区这几种资源描述符。我们现在需要利用描述符将常量缓冲区绑定至渲染流水线上。而且常量缓冲区描述符都要存放在以D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV类型所建的描述符堆里。这种堆内可以混合存储常量缓冲区描述符、着色器资源描述符和无序访问描述符。为了存放这些新类型的描述符,我们需要为之创建以下类型的新式描述符堆

D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.NumDescriptors = 1;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;ComPtr<ID3D12Device> device;
device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(m_ConstBuffer.GetAddressOf()));

这里和深度/模板缓冲区的创建过程相似,唯一的不同在于Flags设置为D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE。在本章的示例程序中,我们并没有使用SRV,UAV,因为绘制一个简单物体不需要。

 

接下来是创建对应的描述符。这里演示的是一次性创建多个常量缓冲区,然后当绘制第i个物体的时候,移动到第i个物体的常量缓冲区资源

ComPtr<ID3D12DescriptorHeap> m_ConstBuffer;
D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc;
cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
cbvHeapDesc.NumDescriptors = 1;
cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
cbvHeapDesc.NodeMask = 0;ComPtr<ID3D12Device> device;
device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(m_ConstBuffer.GetAddressOf()));//此常量缓冲区存储了绘制n个物体所需的常量数据
std::unique_ptr<UploadBuffer<ObjectConstants>> mObjectCB = nullptr;
int n = 10;
mObjectCB = std::make_unique<UploadBuffer<ObjectConstants>>(device.Get(), n, true);
UINT objCBByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConstants));
//缓冲区的起始位置
D3D12_GPU_VIRTUAL_ADDRESS cbvAddress = static_cast<ID3D12Resource*>(*mObjectCB)->GetGPUVirtualAddress();//偏移到常量缓冲区绘制第i个物体所需的常量数据
int CBufferIndex = i;
cbvAddress += CBufferIndex * objCBByteSize;D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
cbvDesc.BufferLocation = cbvAddress;
cbvDesc.SizeInBytes = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConstants));
device->CreateConstantBufferView(&cbvDesc, m_ConstBuffer->GetCPUDescriptorHandleForHeapStart());

结构体D3D12_CONSTANT_BUFFER_VIEW_DESC描述的是绑定到HLSL常量缓冲区结构体的常量缓冲区资源子集。正如前面所提到的,如果常量缓冲区存储了一个内有n个物体常量数据的常量数组,那么我们就可以通过BufferLocation和SizeInBytes参数来获得第i个物体的常量数据。考虑到硬件的需求,成员SizeInBytes与BufferLocation必须为256B的整数倍。例如,若将上述两个成员的值都指定为64,那么我们将看到下面的调试错误

 

 

根签名和描述符表

在绘制调用开始执行前,我们应该将不同的着色器程序所需的各种类型的资源绑定到渲染流水线上。实际上,不同类型的资源会被绑定到特定的寄存器槽(registor slot)上,以供着色器程序访问。比如说,前文代码中的顶点着色器和像素着色器需要的是一个绑定到寄存器b0的常量缓冲区。在本书的后续内容中,我们会用到这两种着色器更高级的配置方法,以使多个常量缓冲区、纹理、采样器都能与各自的寄存器槽相绑定。


//将纹理资源绑定到纹理寄存器槽0
Texture2D gDiffuseMap : register(t0);//把下列采样器资源依次绑定到采样器寄存器槽0~5
SamplerState gsamPointWrap : register(s0);
SamplerState gsamPointClamp : register(s1);
SamplerState gsamLinearWrap : register(s2);
SamplerState gsamLinearClamp : register(s3);
SamplerState gsamAnisotropicWrap : register(s4);
SamplerState gsamAnisotropicClamp : register(s5);//将常量缓冲区资源cbuffer绑定到常量缓冲区寄存器槽0
cbuffer cbPerObject : register(b0)
{float4x4 gWorld;float4x4 gView;
};cbuffer cbMaterial : register(b1)
{float4 gDiffuseAlbedo;float3 gFresnelRo;
};

根签名

根签名定义的是:在执行绘制命令之前,那些应用程序将绑定到渲染流水线上的资源,它们会被映射到着色器对应输入寄存器。根签名一定要与使用它的着色器相兼容。这个过程就像是opengl中的setuniform那个函数,就是把资源绑定到对应的槽中。不同的绘制可能会用到一组不同的着色器程序,这也就意味着要用到不同的根签名。

在Direct3D中,根签名由ID3D12RootSignature接口表示,并以一组描述绘制调用过程中着色器所需资源的根参数定义而成。根参数可以是根常量(root const)根描述符 (root descriptor)或者描述表 (descriptor table)。下面只使用描述符表,描述符表指定的是描述符堆中存有描述符的一块连续的区域。

下面的代码创建了一个根签名,它的根参数为一个描述符表,其大小足以容下一个CBV(常量缓冲区视图)

  1. 首先是创建了根参数,其定义了对应的描述符资源应该绑定到那个寄存器。这里就是将一个含有CBV的描述符表绑定到常量缓冲区寄存器0,即HLSL代码中的register(b0)
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, //表中描述符的数量0); //将这段描述符区域绑定至此基准着色器寄存器slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);
  1. 然后我们需要根据这个根参数定义CD3DX12_ROOT_SIGNATURE_DESC
  2. 根据根参数序列化根签名
  3. 根据序列化的方式创建对应的根签名。
CD3DX12_ROOT_PARAMETER slotRootParameter[1];
CD3DX12_DESCRIPTOR_RANGE cbvTable;
cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, //表中描述符的数量0); //将这段描述符区域绑定至此基准着色器寄存器slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);
//根签名由根参数构成
CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, &slotRootParameter[0], 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);
//创建仅含一个槽位(该槽位指向一个仅由单个常量缓冲区组成的描述符区域)的根签名ComPtr<ID3DBlob> rootSigBlob;
ComPtr<ID3DBlob> errorBlob;
D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, rootSigBlob.GetAddressOf(), errorBlob.GetAddressOf());
ComPtr<ID3D12RootSignature> rootSignature;
ThrowIfFailed(device->CreateRootSignature(0,rootSigBlob->GetBufferPointer(),rootSigBlob->GetBufferSize(),IID_PPV_ARGS(rootSignature.GetAddressOf())));

现在我们就创建了一个根签名,绑定到其中一种类型的描述符。

但是从上面我们可以看到,我们只是定义了一个根签名,也就是最终我们用commlist告诉渲染流水线,我们绑定了一个什么描述符在流水线上。但是我们没有真正地执行资源的绑定,也就是声明了没有定义。所以我们需要通过commandlist的下面的方式将描述符表与渲染流水线相结合

virtual void STDMETHODCALLTYPE SetGraphicsRootDescriptorTable( _In_  UINT RootParameterIndex,_In_  D3D12_GPU_DESCRIPTOR_HANDLE BaseDescriptor) = 0;
  1. RootParameterIndex,将根参数按此索引(即欲绑定到寄存器槽号)进行设置
  2. BaseDescriptor:此参数指定的是将要向着色器绑定的描述符表中第一个描述符位于描述符堆中的句柄。比如说,如果根签名指明当前描述符表中共有5个描述符,则堆中的BaseDescriptor及后面的4个描述符将被设置到此描述符表中。

下面的代码先将根签名和CBV堆设置到命令列表上,并随后再通过设置描述符表来指定我们希望绑定到渲染流水线的资源:

也就是

  1. 设置根签名,根签名描述的是一系列的根参数,而根参数代表的就是不同类型的描述符。
  2. 将描述符堆绑定到命令队列中
  3. 根据设置的不同的堆去定义不同的描述符的起始位置。(这里的起始位置的原因是再一开始的描述符定义中可以定义描述符的数量,这里就是从当前开始的往后几个)
cmdList->SetGraphicsRootSignature(rootSignature.Get());
ID3D12DescriptorHeap* descriptorHeaps[] = { m_ConstBuffer.Get() };
cmdList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);
CD3DX12_GPU_DESCRIPTOR_HANDLE cbv(m_ConstBuffer->GetGPUDescriptorHandleForHeapStart());
//偏移到此次绘制调用所需的CBV处
cbv.Offset(cbvIndex, mCbvSrvUavDescriptorSize);
cmdList->SetGraphicsRootDescriptorTable(0, cbv);

编译着色器

在Direct3D中,着色器程序必须先被编译为一种可移植的字节码。接下来,图形驱动程序将获得这些字节码,并将其重新编译为针对当前系统GPU所优化的本地指令[ATI1]。我们可以在运行期间用下列函数对着色器进行编译。

HRESULT WINAPI
D3DCompileFromFile(_In_ LPCWSTR pFileName,
_In_reads_opt_(_Inexpressible_(pDefines->Name != NULL)) CONST D3D_SHADER_MACRO* pDefines,_In_opt_ ID3DInclude* pInclude,
_In_ LPCSTR pEntrypoint,_In_ LPCSTR pTarget,_In_ UINT Flags1,_In_ UINT Flags2,_Out_ ID3DBlob** ppCode,
_Always_(_Outptr_opt_result_maybenull_) ID3DBlob** ppErrorMsgs);
  1. pFileName:我们希望编译的以.hlsl作为扩展名的HLSL源代码文件
  2. pDefines:在本书中,我们并不使用这个高级的选项,因此总是将它指定为空指针。关于此参数的详细信息可参见SDK文档。
  3. pInclude:在本书中,我们并不使用这个高级选项,因而总是将它们指定为空指针。关于此参数的详细信息可详见SDK文档。
  4. pEntrypoint:着色器的入口点函数名。一个.hlsl文件可能存有多个着色器程序(例如,一个顶点着色器和一个像素着色器),所以我们需要为待编译的着色器指定入口点
  5. pTarget:指定所用着色器类型和版本的字符串。在本书中,我们采用的着色器模型版本是5.0和5.1。
  1. Flags1 :指示对着色器代码应当如何编译的标志。在SDK文档里,这些标志列出得不少,但是此书中我们仅用两种。
D3DCOMPILE_DEBUG //用调试模式来编译着色器
D3DCOMPILE_SKIP_OPTIMIZATION //指示编译器跳过优化阶段(对调试很有用处)
  1. Flags2:我们不会用到处理效果文件的高级编译选项,关于它的信息请参见SDK文档。
  2. ppCode:返回一个指向ID3DBlob数据结构的指针,它存储着编译好的着色器对象字节码
  3. ppErrorMsgs:返回一个指向ID3DBlob数据结构的指针。如果在编译过程中发生了错误,它便会存储报错的字符串。

为了能够输出错误信息,在d3dUtil.h/cpp文件实现了下列辅助函数在运行时编译的着色器

Microsoft::WRL::ComPtr<ID3DBlob> CompileShader(const std::wstring& filename, const D3D_SHADER_MACRO* defines, const std::string& entrypoint, const std::string& target)
{UINT compileFlags = 0;#if defined(DEBUG) || defined(_DEBUG)compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;#endifHRESULT hr = S_OK;Microsoft::WRL::ComPtr<ID3DBlob> byteCode = nullptr;Microsoft::WRL::ComPtr<ID3DBlob> errors;hr = D3DCompileFromFile(filename.c_str(), defines, nullptr, entrypoint.c_str(), target.c_str(), compileFlags, 0, byteCode.GetAddressOf(), errors.GetAddressOf());if (errors != nullptr){OutputDebugStringA((char*)errors->GetBufferPointer());}ThrowIfFailed(hr);return byteCode;
}

以下是一个调用此函数的示例

ComPtr<ID3DBlob> msvsByteCode = nullptr;
ComPtr<ID3DBlob> mpsByteCode = nullptr;
msvsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_0");
mpsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_0");

HLSL的错误和警告消息将通过ppErrorMsgs参数返回。比方说,如果不小心把mul函数拼写错误,那么我们便会从调试窗口得到类似于下列的错误输出。

离线编译

我们不仅可以在运行期间编译着着色器,还能够以单独的步骤(例如,将其作为构建整个工程过程中的一个独立环节,或是将其视为资源内容流水线流程的一部分)离线(offline)编译着色器。这样做有原因若干(ue中一开始就是在编译所有的着色器)

  1. 对于复杂的着色器来说,其编译过程可能耗时过长。因此,借助离线编译即可缩短应用程序的加载时间。
  2. 以便在早于运行时的构建处理期间提前发现编译错误。
  3. 对于Windows8应用商店中的应用而言,必须采用离线编译这种方式。

我们通常用.cso(即compiled shader object,已编译的着色器对象)作为编译着色器的扩展名。

为了以离线的方式编译着色器,我们将使用DirectX自带的FXC命令行编译工具。为了将color.hlsl文件中分别以VS和PS作为入口点的顶点着色器和像素着色器编译为调试版本的字节码,我们可以输入以下命令:

 

为了将color.hlsl文件中分别以VS和PS作为入口点的顶点着色器和像素着色器编译为发行版本的字节码,则可以输入以下命令:

 

 

参数

描述

/Od

禁用优化(对于调试十分有用)

/Zi

开启调试信息

/T

着色器类型和着色器模型的版本

<string>

 

/E

着色器入口点

<string>

 

/Fo

经过编译的着色器对象字节码

<string>

 

/Fc

输出一个着色器的汇编文件清单(对于调试、检验指令数量、查阅<string>生成的代码细节都是很有帮助的)

如果视图编译一个有语法错误的着色器,则FXC会将错误/警告信息输出到命令窗口。譬如,若是在color.hlsl效果文件中拼错了一个变量的名字

那么,我们会因为这一个失误而从调试输出窗口收到许多错误信息(关键在于改正最上面的错误):

 

可见,在编译期间及时获取错误信息要比在运行时才获得错误信息要便捷得多。

既然已经按离线的方式把顶点着色器和像素着色器编译到.cso文件里,也就不需要在运行时对其进行编译(即,无须再调用D3DCompileFromFile方法)。但是我们仍要将.cso文件中已编译好的着色器对象字节码加载到应用程序中,这可以由C++的标准文件输入机制来加以实现,如:

Microsoft::WRL::ComPtr<ID3DBlob> LoadBinary(const std::wstring& filename)
{std::ifstream fin(filename, std::ios::binary);fin.seekg(0, std::ios_base::end);std::fstream::pos_type fileSize = fin.tellg();fin.seekg(0, std::ios_base::beg);Microsoft::WRL::ComPtr<ID3DBlob> blob;ThrowIfFailed(D3DCreateBlob(fileSize, blob.GetAddressOf()));fin.read((char*)blob->GetBufferPointer(), fileSize);fin.close();return blob;
}

生成着色器汇编代码

FXC程序根据可选参数/Fc来生成可移植的着色器汇编代码。通过查阅着色器的汇编代码,即可核对着色器的指令数量,也能了解生成的代码细节——这是为了验证编译器所生成的代码与我们预想的是否一致。例如,如果我们在HLSL代码中写一个条件语句,那么可能会认为汇编代码中将存在一条与之对应的分支指令。在可编程GPU发展的初期阶段中,在着色器里使用分支指令的代价是比较高昂的。因此,编译器时常会通过对两个分支展开求值,再对求值结果进行插值来整理条件语句,以避免采用分支指令并计算出正确的结果。例如,下列两组代码是等价的:

 

上面代码的意思就是在两个结果之间进行插值,插值的条件就是判断语句的条件。

但是,在不查阅着色器汇编代码的情况下,我们无法知道此展开过程是否发生,甚至不能验证生成的分支指令是否正确。有时,查看着色器汇编代码的目的时为了弄清楚它到底做了什么。下面就是一个由color.hlsl文件中顶点着色器生成汇编代码示例:

 

 

利用Visual Studio离线编译着色器

Visual Studio 2015集成了一些对着色器程序进行编译工作的支持,我们可以向工程内添加.hlsl文件,而Visual Studio会识别它们并提供编译的选项。这些在UI中配置的选项就是FXC程序的参数。在向VS工程中添加HLSL文件后,他将称为构建流程的一部分,而着色器也会被FXC程序所编译。VS集成的HLSL工具只允许每个文件中仅有一个着色器程序。因此不允许像素和顶点着色器共存于一个文件里,同时如果我们希望以不同的预处理命令编译同一个着色器程序,从而获得不同的编译结果,VS的集成工具也不允许。

光栅器状态

当今渲染流水线大多阶段都是可编程的,但是有些特定环节却只能接受配置。例如,用于配置渲染流水线中光栅化阶段的光栅器状态组由结构体D3D12_RASTERIZER_DESC表示:

typedef struct D3D12_RASTERIZER_DESC
{
D3D12_FILL_MODE FillMode;
D3D12_CULL_MODE CullMode;
BOOL FrontCounterClockwise;
INT DepthBias;
FLOAT DepthBiasClamp;
FLOAT SlopeScaledDepthBias;
BOOL DepthClipEnable;
BOOL MultisampleEnable;
BOOL AntialiasedLineEnable;
UINT ForcedSampleCount;
D3D12_CONSERVATIVE_RASTERIZATION_MODE ConservativeRaster;
} 	D3D12_RASTERIZER_DESC;
  1. FillMode :
enum D3D12_FILL_MODE{D3D12_FILL_MODE_WIREFRAME	= 2, // 采用线框模式进行渲染D3D12_FILL_MODE_SOLID	= 3 // 使用实体模式进行渲染(默认设置)} 	D3D12_FILL_MODE;
  1. CullMode:
enum D3D12_CULL_MODE{D3D12_CULL_MODE_NONE	= 1,  //禁用剔除操作D3D12_CULL_MODE_FRONT	= 2, //剔除正面朝向的三角形D3D12_CULL_MODE_BACK	= 3 //剔除背面朝向的三角形} 	D3D12_CULL_MODE;
  1. FrontCounterClockwise:如果指定为false,则根据摄像机的观察视角,将顶点顺序为顺时针的三角形看作正面朝向,逆时针为背面。true就是相反(默认false)

CD3DX12_RASTERIZER_DESC时扩展自D3D12_RASTERIZER_DESC结构体的基础上,又添加了一些辅助构造函数的工具类。其中有一个以接收CD3DX12_DEFAULT作为参数来创建光栅化状态对象的构造函数,其实CD3DX12_DEFAULT只是一个哑类型(dummy)(也就是空的,主要是为了显示的创建一个默认的D3D12_RASTERIZER_DESC),而此函数的作用是将光栅化状态中需要被初始化的成员重载为默认值。

explicit CD3DX12_RASTERIZER_DESC( CD3DX12_DEFAULT ) noexcept{FillMode = D3D12_FILL_MODE_SOLID;CullMode = D3D12_CULL_MODE_BACK;FrontCounterClockwise = FALSE;DepthBias = D3D12_DEFAULT_DEPTH_BIAS;DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;DepthClipEnable = TRUE;MultisampleEnable = FALSE;AntialiasedLineEnable = FALSE;ForcedSampleCount = 0;ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;}
struct CD3DX12_DEFAULT {};
extern const DECLSPEC_SELECTANY CD3DX12_DEFAULT D3D12_DEFAULT;

流水线状态对象

我们已经配置了我们渲染pipline的一些可编程的环节了。接下来是将这些东西绑定到图形流水线上,用以实际绘制图形。大多数控制图形流水线状态的对象被称为流水线状态对象,用ID3D12PipelineState表示。

创建PSO我们还需要填写D3D12_GRAPHICS_PIPELINE_STATE_DESC

typedef struct D3D12_GRAPHICS_PIPELINE_STATE_DESC
{
ID3D12RootSignature *pRootSignature;
D3D12_SHADER_BYTECODE VS;
D3D12_SHADER_BYTECODE PS;
D3D12_SHADER_BYTECODE DS;
D3D12_SHADER_BYTECODE HS;
D3D12_SHADER_BYTECODE GS;
D3D12_STREAM_OUTPUT_DESC StreamOutput;
D3D12_BLEND_DESC BlendState;
UINT SampleMask;
D3D12_RASTERIZER_DESC RasterizerState;
D3D12_DEPTH_STENCIL_DESC DepthStencilState;
D3D12_INPUT_LAYOUT_DESC InputLayout;
D3D12_INDEX_BUFFER_STRIP_CUT_VALUE IBStripCutValue;
D3D12_PRIMITIVE_TOPOLOGY_TYPE PrimitiveTopologyType;
UINT NumRenderTargets;
DXGI_FORMAT RTVFormats[ 8 ];
DXGI_FORMAT DSVFormat;
DXGI_SAMPLE_DESC SampleDesc;
UINT NodeMask;
D3D12_CACHED_PIPELINE_STATE CachedPSO;
D3D12_PIPELINE_STATE_FLAGS Flags;
}   D3D12_GRAPHICS_PIPELINE_STATE_DESC;
  1. pRootSignature :指向一个与此PSO绑定的根签名的指针。该签名一定要与此PSO指定的着色器相兼容。
  2. VS:待绑定的顶点着色器。此成员由结构体D3D12_SHADER_BYTECODE表示,这个结构体存有指向已编译号的字节码数据的指针,以及该字节码数据所占的字节大小
typedef struct D3D12_SHADER_BYTECODE{_Field_size_bytes_full_(BytecodeLength)  const void *pShaderBytecode;SIZE_T BytecodeLength;}   D3D12_SHADER_BYTECODE;

 

  1. BlendState:指定混合操作所用的混合状态。
  2. SampleMask:多重采样最多可采集32个样本。借此参数的32位整数值,即可设置每个采样点的采样情况(采集或禁止采集)。例如,若禁用了第5位(将第5位设置为0),则将不会对第5个样本进行采样。当然,要禁止采集第5个样本的前提是,所用的多重采样至少要有5个样本。假如一个应用程序仅使用了单采样,那么只能针对该参数的第i位进行配置。一般来说,使用的都是默认值Oxffffffff,即表示对所有的采样点进行采样。
  3. RasterizerState指定用来配置光栅器的光栅化状态
  4. DepthStencilState:指定用于配置深度/模板测试的深度/模板状态。我们将在后续章节中对此状态进行讨论,目前是默认的。
  5. InputLayout:输入布局描述,此结构体中有两个成员:一个由D3D12_INPUT_ELEMENT_DESC元素构成的数组,以及一个表示此数组中元素数量的无符号整数
typedef struct D3D12_INPUT_LAYOUT_DESC
{
_Field_size_full_(NumElements)  const D3D12_INPUT_ELEMENT_DESC *pInputElementDescs;
UINT NumElements;
}   D3D12_INPUT_LAYOUT_DESC;
  1. PrimitiveTopologyType:指定图元的拓扑类型
typedef 
enum D3D12_PRIMITIVE_TOPOLOGY_TYPE
{
D3D12_PRIMITIVE_TOPOLOGY_TYPE_UNDEFINED = 0,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_POINT = 1,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_LINE  = 2,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE  = 3,
D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH = 4
}   D3D12_PRIMITIVE_TOPOLOGY_TYPE;
  1. NumRenderTargets:同时所用的渲染目标数量(即RTVFormats数组中渲染目标格式的数量)
  2. RTVFormats:渲染目标的格式。利用该数组实现向多渲染目标同时进行写操作。使用此PSO的渲染目标的格式设定应当与此参数相匹配。
  3. DSVFormat:深度/模板缓冲区格式。使用此PSO的深度/模板缓冲区的格式设定应当与此参数相匹配。
  4. SampleDesc:描述多重采样对每个像素采样的数量及其质量级别。此参数应当与渲染目标的对应设置相匹配。

 

 

 

不同的pipeline可以绘制出不同的效果。但是一个Pipeline使用的不同类型的着色器资源只有一个

几何图形辅助结构体

在本书中,我们通过创建一个同时存有顶点缓冲区和索引缓冲区的结构体来方便地定义多个几何体。另外,借此结构体即可将顶点和索引数据置于系统内存之中,以供CPU读取。例如,执行拾取和碰撞检测这样的工作就需要CPU来访问几何体数据。再者,该结构体还缓存了顶点缓冲区和索引缓冲区的一些重要属性(例如格式和每个顶点项所占用的字节数),并提供了返回缓冲区视图的方法。当需要定义多个几何体时,我们就使用下面的MeshGeometry结构体。

这个几何辅助结构体,需要有一些东西

  1. 顶点数据,索引数据
  2. 顶点资源,索引资源
  3. 可以返回顶点视图和索引视图
  4. 子数据,也就是可以有一个总的顶点缓冲区和索引缓冲区,但是我可以选择绘制其中的那些mesh
struct SubmeshGeometry
{
UINT IndexCount = 0;
UINT StartIndexLocation = 0;
UINT BaseVertexLocation = 0;//通过子网络定义当前SubmeshGeometry结构体中所存几何体的包围盒(bounding box)。我们将在本书的后续章节中使用这个数据
DirectX::BoundingBox Bounds;
};struct MeshGeometry
{
std::string Name;Microsoft::WRL::ComPtr<ID3DBlob> VertexBufferCPU = nullptr;
Microsoft::WRL::ComPtr<ID3DBlob> IndexBufferCPU = nullptr;Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferGPU = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferGPU = nullptr;Microsoft::WRL::ComPtr<ID3D12Resource> VertexBufferUploader = nullptr;
Microsoft::WRL::ComPtr<ID3D12Resource> IndexBufferUploader = nullptr;UINT VertexByteStride = 0;
UINT VertexByteSize = 0;
DXGI_FORMAT IndexFormat = DXGI_FORMAT_R16_UINT;
UINT IndexBufferByteSize = 0;//一个MeshGeometry结构体能够存储一组顶点/索引缓冲区中的多个几何体
//若利用下列容器来定义子网络几何体,我们就能单独地绘制出其中的子网络(单个几何体)
std::unordered_map<std::string, SubmeshGeometry> DrawArgs;D3D12_VERTEX_BUFFER_VIEW VertexBufferView() const
{D3D12_VERTEX_BUFFER_VIEW vbv;vbv.BufferLocation = VertexBufferGPU->GetGPUVirtualAddress();vbv.SizeInBytes = VertexByteSize;vbv.StrideInBytes = VertexByteStride;return vbv;
}D3D12_INDEX_BUFFER_VIEW IndexBufferView() const
{D3D12_INDEX_BUFFER_VIEW ibv;ibv.Format = IndexFormat;ibv.BufferLocation = IndexBufferGPU->GetGPUVirtualAddress();ibv.SizeInBytes = IndexBufferByteSize;return ibv;
}//数据上传到GPU以后,就需要释放这些内存
void DisposeUploaders()
{VertexBufferUploader = nullptr;IndexBufferUploader = nullptr;
}
};

立方体演示程序

HLSL中mul()函数的解释_hlsl mul-CSDN博客

mul函数是行主序乘列主序,也就是如果是矩阵会自动进行一次转置。hlsl默认是用行存储

这里就是根据前面的内容做一个立方体出来

#pragma once
#include "D3DUtil.h"#include "D3DApp.h"struct Vertex
{DirectX::XMFLOAT3 Pos;DirectX::XMFLOAT4 Color;
};struct ObjectConsts
{DirectX::XMFLOAT4X4 WorldViewProj;
};class BoxApp : public D3DApp
{
public:BoxApp(HINSTANCE hInstance):D3DApp(hInstance){}bool Initialize() override;private:void OnResize() override;void Update(const GameTimer& gt) override;void Draw(const GameTimer& gt) override;void OnMouseMove(WPARAM btnState, int x, int y) override;void OnMouseDown(WPARAM btnState, int x, int y) override;void OnMouseUp(WPARAM btnState, int x, int y) override;void BuildDescriptorHeaps();void BuildConstBuffer();void BuildRootSignature();void BuildShadersAndInputLayout();void BuildBoxGeometry();void BuildPSO();private:ComPtr<ID3D12RootSignature> m_RootSignature = nullptr;ComPtr<ID3D12DescriptorHeap> m_ConstBufferHeap = nullptr;std::unique_ptr<ObjectConsts> m_OjectCB = nullptr;std::unique_ptr<d3dUtil::MeshGeometry> m_BoxGeometry = nullptr;ComPtr<ID3DBlob> m_VSByteCode = nullptr;ComPtr<ID3DBlob> m_PSByteCode = nullptr;std::vector<D3D12_INPUT_ELEMENT_DESC> m_InputLayoutDesc;ComPtr<ID3D12PipelineState> m_PSO = nullptr;DirectX::XMFLOAT4X4 m_World = MathHelper::Identity4x4();DirectX::XMFLOAT4X4 m_View = MathHelper::Identity4x4();DirectX::XMFLOAT4X4 m_Proj = MathHelper::Identity4x4();float m_Theta = 1.5f * DirectX::XM_PI;float m_Phi = DirectX::XM_PIDIV4; // pi / 4float m_Radius = 5.0f;POINT m_LastMousePos;
};
#include "D3DUtil.h"
#include "BoxApp.h"#include <DirectXColors.h>
#include <array>
#include <sstream>
BoxApp::BoxApp(HINSTANCE hInstance):D3DApp(hInstance)
{}BoxApp::~BoxApp()
{
}void BoxApp::OnResize()
{D3DApp::OnResize();DirectX::XMMATRIX p = DirectX::XMMatrixPerspectiveFovLH(0.25f * MathHelper::Pi, AspectRatio(), 1.0f, 1000.0f);DirectX::XMStoreFloat4x4(&m_Proj, p);
}void BoxApp::OnMouseMove(WPARAM btnState, int x, int y)
{if ((btnState & MK_LBUTTON) != 0){float dx = DirectX::XMConvertToRadians(0.25f * static_cast<float>(x - m_LastMousePos.x));float dy = DirectX::XMConvertToRadians(0.25f * static_cast<float>(y - m_LastMousePos.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_LastMousePos.x);float dy = 0.005f * static_cast<float>(y - m_LastMousePos.y);m_Radius += (dx - dy);m_Radius = MathHelper::Clamp(m_Radius, 3.0f, 15.0f);}m_LastMousePos = { x, y };
}void BoxApp::OnMouseDown(WPARAM btnState, int x, int y)
{m_LastMousePos = { x, y };SetCapture(mhMainWnd);
}void BoxApp::OnMouseUp(WPARAM btnState, int x, int y)
{ReleaseCapture();
}void BoxApp::BuildDescriptorHeaps()
{D3D12_DESCRIPTOR_HEAP_DESC cbvHeapDesc{};cbvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;cbvHeapDesc.NumDescriptors = 1;cbvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;cbvHeapDesc.NodeMask = 0;ThrowIfFailed(m_Device->CreateDescriptorHeap(&cbvHeapDesc, IID_PPV_ARGS(m_ConstBufferHeap.GetAddressOf())));
}void BoxApp::BuildConstBuffer()
{m_ObjectCB = std::make_unique<UploadBuffer<ObjectConsts>>(m_Device.Get(), 1, true);UINT objCBByteSize = d3dUtil::CalcConstBufferByteSize(sizeof(ObjectConsts));D3D12_GPU_VIRTUAL_ADDRESS cbAdress = static_cast<ID3D12Resource*>(*m_ObjectCB)->GetGPUVirtualAddress();// 偏移到常量缓冲区中第i个物体所对应的常量数据//这里取i = 0int boxCBufferIndex = 0;cbAdress += boxCBufferIndex * objCBByteSize;D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc{};cbvDesc.BufferLocation = cbAdress;cbvDesc.SizeInBytes = objCBByteSize;m_Device->CreateConstantBufferView(&cbvDesc, m_ConstBufferHeap->GetCPUDescriptorHandleForHeapStart());
}void BoxApp::BuildRootSignature()
{CD3DX12_ROOT_PARAMETER slotRootParameter[1];CD3DX12_DESCRIPTOR_RANGE cbvTable;cbvTable.Init(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, 1, 0);slotRootParameter[0].InitAsDescriptorTable(1, &cbvTable);ComPtr<ID3DBlob> rootSignature;ComPtr<ID3DBlob> error;CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(1, &slotRootParameter[0], 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1, rootSignature.GetAddressOf(), error.GetAddressOf());if (error != nullptr){OutputDebugStringA((char*)error->GetBufferPointer());}ThrowIfFailed(hr);ThrowIfFailed(m_Device->CreateRootSignature(0,rootSignature->GetBufferPointer(),rootSignature->GetBufferSize(),IID_PPV_ARGS(m_RootSignature.GetAddressOf())));
}void BoxApp::BuildShadersAndInputLayout()
{HRESULT hr = S_OK;m_VSByteCode = d3dUtil::CompileShader(L"source/shader/color.usf", nullptr, "VS", "vs_5_0");m_PSByteCode = d3dUtil::CompileShader(L"source/shader/color.usf", nullptr, "PS", "ps_5_0");m_InputLayoutDesc = {{"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}};
}void BoxApp::BuildBoxGeometry()
{std::array<Vertex, 8> vertices = {Vertex({DirectX::XMFLOAT3(-1.0f, -1.0f, -1.0f), DirectX::XMFLOAT4(DirectX::Colors::White)}),Vertex({DirectX::XMFLOAT3(-1.0f, 1.0f,  -1.0f), DirectX::XMFLOAT4(DirectX::Colors::Black)}),Vertex({DirectX::XMFLOAT3(1.0f, 1.0f,   -1.0f), DirectX::XMFLOAT4(DirectX::Colors::Red)}),Vertex({DirectX::XMFLOAT3(1.0f, -1.0f,  -1.0f), DirectX::XMFLOAT4(DirectX::Colors::Green)}),Vertex({DirectX::XMFLOAT3(-1.0f, -1.0f, 1.0f), DirectX::XMFLOAT4(DirectX::Colors::Blue)}),Vertex({DirectX::XMFLOAT3(-1.0f, 1.0f,  1.0f), DirectX::XMFLOAT4(DirectX::Colors::Yellow)}),Vertex({DirectX::XMFLOAT3(1.0f, 1.0f,   1.0f), DirectX::XMFLOAT4(DirectX::Colors::Cyan)}),Vertex({DirectX::XMFLOAT3(1.0f, -1.0f,  1.0f), DirectX::XMFLOAT4(DirectX::Colors::Magenta)})};std::array<uint16_t, 36> indices ={0, 1, 2,0, 2, 3,4, 6, 5,4, 7, 6,4, 5, 1,4, 1, 0,3, 2, 6,3, 6, 7,1, 5, 6,1, 6, 2,4, 0, 3,4, 3, 7};UINT vbByteSize = (UINT)vertices.size() * sizeof(Vertex);UINT ibByteSize = (UINT)indices.size() * sizeof(uint16_t);m_BoxGeometry = std::make_unique<d3dUtil::MeshGeometry>();m_BoxGeometry->Name = "boxGeo";ThrowIfFailed(D3DCreateBlob(vbByteSize, m_BoxGeometry->VertexBufferCPU.GetAddressOf()));CopyMemory(m_BoxGeometry->VertexBufferCPU->GetBufferPointer(), vertices.data(), vbByteSize);ThrowIfFailed(D3DCreateBlob(ibByteSize, m_BoxGeometry->IndexBufferCPU.GetAddressOf()));CopyMemory(m_BoxGeometry->IndexBufferCPU->GetBufferPointer(), indices.data(), ibByteSize);m_BoxGeometry->VertexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(),m_BoxGeometry->VertexBufferCPU->GetBufferPointer(), vbByteSize, m_BoxGeometry->VertexBufferUploader);m_BoxGeometry->IndexBufferGPU = d3dUtil::CreateDefaultBuffer(m_Device.Get(), m_CommandList.Get(),m_BoxGeometry->IndexBufferCPU->GetBufferPointer(), ibByteSize, m_BoxGeometry->IndexBufferUploader);m_BoxGeometry->VertexByteStride = sizeof(Vertex);m_BoxGeometry->VertexByteSize = vbByteSize;m_BoxGeometry->IndexBufferByteSize = ibByteSize;m_BoxGeometry->IndexFormat = DXGI_FORMAT_R16_UINT;d3dUtil::SubmeshGeometry subMesh;subMesh.IndexCount = indices.size();subMesh.BaseVertexLocation = 0;subMesh.StartIndexLocation = 0;m_BoxGeometry->DrawArgs["box"] = subMesh;
}void BoxApp::BuildPSO()
{D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {};ZeroMemory(&psoDesc, sizeof(D3D12_GRAPHICS_PIPELINE_STATE_DESC));psoDesc.pRootSignature = m_RootSignature.Get();psoDesc.VS = CD3DX12_SHADER_BYTECODE(m_VSByteCode.Get());psoDesc.PS = CD3DX12_SHADER_BYTECODE(m_PSByteCode.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_InputLayoutDesc.data(), (UINT)m_InputLayoutDesc.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;ThrowIfFailed(m_Device->CreateGraphicsPipelineState(&psoDesc, IID_PPV_ARGS(m_PSO.GetAddressOf())));
}bool BoxApp::Initialize()
{if (!D3DApp::Initialize()){return false;}ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), m_PSO.Get()));BuildDescriptorHeaps();BuildConstBuffer();BuildRootSignature();BuildShadersAndInputLayout();BuildBoxGeometry();BuildPSO();ThrowIfFailed(m_CommandList->Close());ID3D12CommandList* cmdLists[] = { m_CommandList.Get() };m_CommandQueue->ExecuteCommandLists(_countof(cmdLists), cmdLists);FlushCommandQueue();return true;
}void BoxApp::Update(const GameTimer& gt)
{float x = m_Radius * sinf(m_Phi) * cosf(m_Theta);float y = m_Radius * cos(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, 0.0f);DirectX::XMMATRIX view = DirectX::XMMatrixLookAtLH(pos, target, up);DirectX::XMStoreFloat4x4(&m_View, view);DirectX::XMMATRIX world = DirectX::XMLoadFloat4x4(&m_World);DirectX::XMMATRIX proj = DirectX::XMLoadFloat4x4(&m_Proj);DirectX::XMMATRIX worldViewProj = world * view * proj;ObjectConsts objConstants;DirectX::XMStoreFloat4x4(&objConstants.WorldViewProj, DirectX::XMMatrixTranspose(worldViewProj));//objConstants.WorldViewProj = MathHelper::Identity4x4();m_ObjectCB->CopyData(0, objConstants);
}void BoxApp::Draw(const GameTimer& gt)
{ThrowIfFailed(m_CommandAllocator->Reset());ThrowIfFailed(m_CommandList->Reset(m_CommandAllocator.Get(), m_PSO.Get()));m_CommandList->RSSetViewports(1, &m_ViewPort);m_CommandList->RSSetScissorRects(1, &m_ScissorRect);D3D12_CPU_DESCRIPTOR_HANDLE CurrentRenderView = CurrentBackBufferHandle();D3D12_CPU_DESCRIPTOR_HANDLE CurrentDepthStencil = DepthStencilView();D3D12_RESOURCE_BARRIER p_rb = CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(), D3D12_RESOURCE_STATE_PRESENT,D3D12_RESOURCE_STATE_RENDER_TARGET);m_CommandList->ResourceBarrier(1, &p_rb);m_CommandList->ClearRenderTargetView(CurrentRenderView, DirectX::Colors::LightSteelBlue, 0, nullptr);m_CommandList->ClearDepthStencilView(CurrentDepthStencil, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);m_CommandList->OMSetRenderTargets(1, &CurrentRenderView, true, &CurrentDepthStencil);ID3D12DescriptorHeap* descriptorHeaps[] = { m_ConstBufferHeap.Get()};m_CommandList->SetDescriptorHeaps(_countof(descriptorHeaps), descriptorHeaps);m_CommandList->SetGraphicsRootSignature(m_RootSignature.Get());D3D12_VERTEX_BUFFER_VIEW vertexBufferView = m_BoxGeometry->VertexBufferView();D3D12_INDEX_BUFFER_VIEW indexBufferView = m_BoxGeometry->IndexBufferView();m_CommandList->IASetVertexBuffers(0, 1, &vertexBufferView);m_CommandList->IASetIndexBuffer(&indexBufferView);m_CommandList->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);m_CommandList->SetGraphicsRootDescriptorTable(0, m_ConstBufferHeap->GetGPUDescriptorHandleForHeapStart());m_CommandList->DrawIndexedInstanced(m_BoxGeometry->DrawArgs["box"].IndexCount, 1, 0, 0, 0);p_rb = CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(), D3D12_RESOURCE_STATE_RENDER_TARGET,D3D12_RESOURCE_STATE_PRESENT);m_CommandList->ResourceBarrier(1, &p_rb);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;FlushCommandQueue();
}

 

 

http://www.dtcms.com/a/316221.html

相关文章:

  • 【LeetCode 热题 100】347. 前 K 个高频元素——(解法一)排序截取
  • 防火墙的区域划分和流量控制
  • Qwen3技术之模型预训练
  • Redis Stream:高性能消息队列核心原理揭秘
  • 数据结构04 栈和队列
  • tensorRT配合triton部署模型
  • C语言的结构体与联合体
  • LOOP Finance:一场 Web3 共和国中的金融制度实验
  • Spring Boot 与 Ollama 集成部署私有LLM服务 的完整避坑指南,涵盖 环境配置、模型管理、性能优化 和 安全加固
  • 【数据结构入门】数组和链表的OJ题(2)
  • uv与conda环境冲突,无法使用uv环境,安装包之后出现ModuleNotFoundError: No module named ‘xxx‘等解决方法
  • SpringBoot中策略模式使用
  • tcp 确认应答和超时时间
  • mq_timedsend系统调用及示例
  • Lua语言程序设计1:基础知识、数值、字符串与表
  • DDOS攻击和CC攻击对服务器的伤害有哪些?
  • 蘑兔音乐:音乐创作的神奇钥匙​
  • AI产品经理手册(Ch9-11)AI Product Manager‘s Handbook学习笔记
  • Linux系统交叉编译:依赖、构建与实践
  • makefile的使用与双向链表
  • 使用YOLOv8-gpu训练自己的数据集并预测
  • 多传感器融合
  • 2025暑期作业
  • 企业如何用现代数仓架构挖掘新业务盈利点?AllData产品从目标、路径、结果给出答案
  • 分布式文件系统06-分布式中间件弹性扩容与rebalance冲平衡
  • 集成学习与随机森林:从原理到实践指南
  • 解决VScode无法打开本地文件夹及远程连接后无反应的问题
  • Maven和Gradle在构建项目上的区别
  • 范式集团与海博思创成立合资公司,杀入“AI+储能”赛道
  • 机器学习之KNN、贝叶斯与决策树算法