利用Direct3D绘制几何体

基础知识:如下所示:
1.除了空间位置信息,Direct3D中的顶点还可以存储其他类型的属性数据(法线,纹理等)。
2.在Direct3D 12中,所有的资源均用ID3D12Resource接口表示。可以通过ID3D12Device::CreateCommittedResource函数来获取该接口对象。
3.资源类型由D3D12_RESOURCE_DIMENSION字段加以区分。如:D3D12_RESOURCE_DIMENSION_BUFFER表示缓冲区;D3D12_RESOURCE_DIMENSION_TEXTURE2D表示2D纹理。
4.CD3D12_RESOURCE_DESC是D3D12_RESOURCE_DESC的派生类型,它内部提供了许多便于使用的构造函数和辅助函数。
5.静态几何体是指每一帧都不会发生改变的几何体。
6.由于默认堆的性能要高于其他类型的堆,所以最好让CPU将数据写入到默认堆中。当CPU不能向默认堆写入数据时,此时可以先让CPU将数据写入到上传堆,然后再将数据从上传堆复制到默认堆中。
7.输入槽共有16个,索引为0 ~ 15。
8.物体的第一个顶点相对于全局顶点缓冲区的位置叫做它的基准顶点地址。
9.物体的新索引是通过原始索引加上它的基准顶点地址来获取。
10.系统值语义指的是以SV开头的语义。它会对参数做出一些特殊处理,如:SV_POSITION获取齐次裁剪空间中的顶点位置信息;SV_Target将输出值存储于渲染目标之中。
11.着色器模型定义了HLSL的编写规范,确定了其内置函数,着色器属性等一切语言元素。
12.Direct3D 12不仅保证了ID3D12Resource接口中Map与Unmap函数在多线程中调用的安全性,还令Map函数可嵌套调用。第一次调用Map函数时,Direct3D会在CPU端分配一块虚拟内存地址范围,用来映射GPU中的资源。而最后一次调用Unmap函数时,则会释放这块CPU虚拟地址空间范围。Map函数会在必要时对CPU缓存执行invalidate操作,以此使CPU端可以读取GPU端这段地址内容所做的修改;相反的,Unmap函数则会在必要时对CPU缓存进行flush操作,以令GPU端可以读取CPU端这段地址内容所做的修改。
13.以D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV类型所创建的描述符堆里面可以混合存储常量缓冲区描述符,着色器资源描述符和无序访问描述符。
14.寄存器槽就是向着色器传递资源的手段,register(*#)中*表示寄存器传递的资源类型,可以是t(表示着色器资源视图)、s(采样器)、u(无序访问视图)以及b(常量缓冲区视图),#则为所用的寄存器编号。
15.ID3DBlob类型描述的其实就是一段普通的内存块。内部的GetBufferPointer函数用来返回数据的地址;GetBufferSize函数用来返回数据的大小。
16.CD3DX12_DEFAULT只是一个哑类型,主要用来将CD3D12X_RASTERIZER_DESC、CD3DX12_BLEND_DESC、CD3DX12_DEPTH_STENCIL_DESC等工具类中需要被初始化的成员重载为默认值。
17.旋转摄像机系统指的是将摄像机视为针对目标物体的可控侦查卫星,用鼠标调整摄像机与物体间的距离(也就是球面半径)以及观察角度,再将其转换为笛卡尔坐标。
18.渲染项指的是单次绘制调用过程中,需要向渲染流水线提交的数据集。

输入布局描述:向Direct3D提供顶点结构体中每个成员的细节。具有以下特性:
1.输入布局描述定义的是:数据进入输入寄存器之前的类型。
2.输入布局描述为渲染流水线状态描述中的一个字段,它会随着渲染流水线状态对象与渲染流水线的输入装配阶段相绑定。
3.输入布局描述与顶点着色器输入签名进行格式的比对验证。

顶点缓冲区:存储顶点的缓冲区。具有以下特性:
1.可以通过顶点缓冲区资源来创建一个对应的顶点缓冲区视图。该视图的定义如下所示:

typedef struct D3D12_VERTEX_BUFFER_VIEW
{
    
    
    D3D12_GPU_VIRTUAL_ADDRESS BufferLocation; // 顶点缓冲区的地址,可以通过ID3D12Resource::GetGPUVirtualAddress函数来获得此地址。
    UINT SizeInBytes; // 顶点缓冲区的总字节数
    UINT StrideInBytes; // 顶点缓冲区中每个顶点的字节数
} D3D12_VERTEX_BUFFER_VIEW;

2.通过ID3D12GraphicsCommandList::IASetVertexBuffers函数来将顶点缓冲区视图与渲染流水线上的一个输入槽相绑定。这样一来,我们就能向渲染流水线的输入装配阶段传递顶点数据了。
3.通过ID3D12GraphicsCommandList::DrawInstanced函数来将顶点数据绘制成指定图元拓扑状态对应的几何体。

索引缓冲区:存储索引的缓冲区。具有以下特性:
1.可以通过索引缓冲区资源来创建一个对应的索引缓冲区视图。该视图的定义如下所示:

typedef struct D3D12_INDEX_BUFFER_VIEW
{
    
    
    D3D12_GPU_VIRTUAL_ADDRESS BufferLocation; // 索引缓冲区的地址,可以通过ID3D12Resource::GetGPUVirtualAddress函数来获得此地址。
    UINT SizeInBytes; // 索引缓冲区的总字节数
    DXGI_FORMAT Format; // 索引的格式必须为表示16位索引的DXGI_FORMAT_R16_UINT类型,或表示32位索引的DXGI_FORMAT_R32_UINT类型。
} D3D12_INDEX_BUFFER_VIEW;

2.通过ID3D12GraphicsCommandList::IASetIndexBuffer函数来将索引缓冲区视图绑定到渲染流水线的输入装配阶段。
3.通过ID3D12GraphicsCommandList::DrawIndexedInstanced函数来将顶点数据按照索引顺序绘制成几何体。

常量缓冲区:具有以下特性:
1.常量缓冲区的大小必须为硬件最小分配空间(256字节)的整数倍。为了免去系统将常量缓冲区元素隐式凑整为256字节整数倍的这项处理环节,我们可以手动地填充所有的常量缓冲区结构体,使之皆为256字节的整数倍。
2.常量缓冲区创建在一个上传堆而不是默认堆中,从而可以使用ID3D12Resource::Map和Unmap函数来使CPU更新常量缓冲区中的数据内容。
3.常量缓冲区的数据内容可以被着色器程序所引用。参考代码如下所示:

// 将常量缓冲区的数据元素定义在一个结构体中
struct ObjectConstants
{
    
    
	float4x4 gWorldViewProj;
	uint matIndex;
};
// 使用结构体创建常量缓冲区对象
ConstantBuffer<ObjectConstants> gObjConstants : register(b0);
// 在着色器里访问常量缓冲区的各个字段
uint index = gObjConstants.matIndex; 

4.通过常量缓冲区视图对象将资源绑定到渲染流水线的流程如下所示:
1>.创建D3D12_DESCRIPTOR_HEAP_DESC描述符堆对象。该对象的类型属性设置成 D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV。
2>.创建D3D12_CONSTANT_BUFFER_VIEW_DESC对象。该对象描述的是绑定到HLSL常量缓冲区结构体的常量缓冲区资源子集。
3>.将1>和2>两步骤得到的对象以参数的形式来调用ID3D12Device::CreateConstBufferView函数,从而创建常量缓冲区视图对象。
5.在对一个常量缓冲区进行更新的时候,其中所有的变量都会随之更新,正所谓牵一发而动全身。因此,应该根据更新频率将数据有效的组织为不同的常量缓冲区,以此来避免无谓的冗余更新,从而提高效率。 但是,不要在着色器内使用过多的常量缓冲区,出于性能的考虑,常量缓冲区的数量应该少于5个。
6.物体常量缓冲区用来存储与该物体自身相关的常量数据(如:世界矩阵)。
7.渲染过程常量缓冲区用来存储不同渲染过程中通用的常量数据(如:观察位置、观察矩阵、投影矩阵、渲染目标等)。

描述符表:指定的是描述符堆中存有描述符的一块连续区域。具有以下特性:
1.通常使用CD3D12_DESCRIPTOR_RANGE辅助结构体来创建以及初始化描述符表对象。
2.通过调用ID3D12GraphicsCommandList::SetGraphicsRootDescriptorTable函数令描述符表与渲染流水线相绑定。

根参数:描述的是绘制调用过程中着色器所需资源。具有以下特性:
1.根参数可以是根常量、根描述符或者描述符表。
2.通常使用CD3DX12_ROOT_PARAMETER辅助结构体来创建以及初始化根参数对象。

根签名:由ID3D12RootSignature接口来表示,并以一组根参数定义而成。具有以下特性:
1.在执行绘制命令之前,应用程序会将绑定到渲染流水线上的资源映射到着色器的对应输入寄存器上。
2.如果把着色器程序当做一个函数,输入资源当做参数,那么根签名则定义了函数签名。通过绑定不同的资源作为参数,着色器的输出也将有所差别。
3.根签名一定要为着色器提供其执行期间需要绑定到渲染流水线的所有资源。
4.Direct3D 12规定,必须使用D3D12SerializeVersionRootSignature函数(Windows10 14393版本以后才有该函数)或者D3D12SerializeRootSignature函数先将根签名的描述布局进行序列化处理,待其转换为以ID3DBlob接口表示的序列化数据格式后,才可将它传入ID3D12Device::CreateRootSignature函数,正式创建根签名。
5.根签名只定义了应用程序要绑定到渲染流水线的资源,却没有真正地执行任何资源绑定操作。只有通过调用命令列表(ID3D12GraphicsCommandList)接口中提供的辅助函数才能将资源绑定到渲染流水线上。
6.每当在绘制调用或者调度调用之间有根签名的内容发生改变时,通过D3D12的驱动程序便会将与应用程序相绑定的根签名内容自动更新为最新的数据,然后按照新的根签名定义重新将所有的对应资源绑定到渲染流水线上。
7.出于性能的原因,我们应当使根签名的规模尽可能的小。除此之外,还要试着尽量减少每帧渲染过程中根签名的修改次数。

光栅器状态:具有以下特性:
1.配置渲染流水线中光栅化阶段的光栅器状态组由结构体D3D12_RASTERIZER_DESC来表示。参考代码如下所示:

typedef struct D3D12_RASTERIZER_DESC {
    
    
	// 填充模式。
	// D3D12_FILL_MODE_WIREFRAME是采用线框模式进行渲染。
	// D3D12_FILL_MODE_SOLID(默认)是采用实体模式进行渲染。
    D3D12_FILL_MODE FillMode;
    // 剔除操作。
    // D3D12_FILL_MODE_NONE是禁用剔除操作。
    // D3D12_FILL_MODE_FRONT是剔除正面朝向的三角形。
    // D3D12_FILL_MODE_BACK(默认)是剔除背面朝向的三角形。
    D3D12_CULL_MODE CullMode;
    // 如果指定为false(默认值),则根据摄像机的观察视角,将顶点顺序为顺时针方向的三角形看作正面朝向,而把逆时针绕序的三角形当做背面朝向。
    // 如果指定为true,则根据摄像机的观察视角,将顶点顺序为逆时针方向的三角形看作正面朝向,而把顺时针绕序的三角形当做背面朝向。
    BOOL FrontCounterClockwise;
    INT DepthBias;
    FLOAT DepthBiasClamp;
    FLOAT SlopeScaledDepthBias;
    BOOL DepthClipEnable;
    BOOL MultisampleEnable;
    BOOL AntialiasedLineEnable;
    UINT ForcedSampleCount;
    D3D12_CONSERVATIVE_RASTERIZATION_MODE ConservativeRaster;
} D3D12_RASTERIZER_DESC;

2.CD3DX12_RASTERIZER_DESC是在扩展自D3D12_RASTERIZER_DESC结构体的基础上,又添加了一些辅助构造函数的工具类。

渲染流水线状态对象:由ID3D12PipelineState接口来表示。具有以下特性:
1.D3D12_GRAPHICS_PIPELINE_STATE_DESC结构体用来描述渲染流水线状态对象的具体实现细节。它会将大多数控制图形流水线的状态对象集总起来,再统一对渲染流水线进行设置。这样一来,Direct3D就能验证所有的状态是否彼此兼容,而驱动程序也将可以提前生成硬件本地指令及其状态。
2.ID3D12Device::CreateGraphicsPipelineState函数用来创建渲染流水线状态对象。
3.由于渲染流水线状态对象的验证和创建操作过于耗时,所以应该在初始化期间就生成渲染流水线状态对象。创建好的渲染流水线状态对象可以存储在散列表中,以便在后续使用时快速获取。
4.考虑到程序的性能问题,我们应当尽可能减少改变渲染流水线状态对象的次数。为此,若能以一个渲染流水线状态对象绘制出所有的物体,就绝不用第二个渲染流水线状态对象。切记,不要在每次绘制调用时都修改渲染流水线状态对象。

帧资源:每帧都需CPU来修改的资源。具有以下特性:
1.在每帧中等待GPU处理完命令队列中所有命令的做法效率极低,因为这种策略在某些时刻会导致CPU或者GPU处于空闲状态。
2.环形数组指的是由帧资源作为基本元素所构成的数组。
3.环形数组可以让CPU无需等待GPU结束当前的任务,即可继续处理下一帧的相关工作。该策略的思路是:在处理第n帧的时候,CPU将周而复始地从环形数组中获取下一个没被GPU使用的帧资源。趁着GPU还在处理n-1帧之时,CPU将为第n帧更新资源,并构建和提交对应的命令列表。随后,CPU会继续针对第n+1帧执行同样的工作流程,并不断重复下去。
4.使用环形数组时,如果CPU处理帧的速度总是快于GPU,则CPU必须在某些时刻等待GPU追赶上来(CPU需要更新的帧资源正是GPU正在处理的帧资源),但是此场景正是我们所期盼的:不仅GPU的处理能力将得到充分地发挥,同时多出来的CPU资源又总是可以被游戏的其他部分,如AI、物理模拟、游戏逻辑所利用。

编译着色器:具有以下特性:
1.在Direct3D中,着色器程序必须先被编译为一种可移植的字节码(.cso文件)。接下来,图形驱动程序将获取这些字节码,并将其重新编译为针对当前系统GPU所优化的本地指令。
2.在运行期间可以使用D3DCompileFromFile函数对着色器进行编译。
3.采用离线编译着色器的原因如下:
1>.对于Windows 8应用商店中的应用而言,必须采用离线编译这种方式。
2>.以便在早于运行时的构建处理期间提前发现编译异常。
3>.对于复杂的着色器来说,其编译过程可能耗时太长。因此借助离线编译可以缩短应用程序的加载时间。
4.为了以离线的方式编译着色器,我们将使用DirectX自带的FXC命令行编译工具。该工具常见的命令参数如下表所示:

参数 描述
/Od 禁用优化(对于调试十分有用)
/Zi 开启调试信息
/T <string> 着色器类型和着色器模型的版本
/E <string> 着色器入口点
/Fo <string> 经过编译的着色器对象字节码(.cso文件)
/Fc <string> 输出一个着色器的汇编文件清单(.asm文件。该文件对于调试、检验指令数量、查阅生成的代码细节都是很有帮助的)

5.Visual Studio内部集成了FXC命令行编译工具,但是它只允许每个HLSL文件中仅有一个着色器程序,并且只输出一个.cso文件。

顶点着色器:针对每一个顶点而运行的函数。具有以下特性:
1.输入签名定义的是:在执行顶点着色器程序的过程中,将数据从输入寄存器中读取时所视作的类型。
2.输入参数语义用于将顶点结构体中的成员映射到顶点着色器的相应输入参数。
3.输出参数语义用于将顶点着色器中的输出参数映射到下个处理阶段中相应的输入参数。
4.因为硬件期望在执行完顶点着色器之后获取顶点位于齐次裁剪空间之中的坐标,所以可以在几何着色器中输出顶点在齐次裁剪空间中坐标;也可以在顶点着色器中用SV_POSITION语义来输出顶点在齐次裁剪空间中的坐标。
5.在顶点着色器中不能实现透视除法的,此阶段只能实现投影矩阵这一环节的运算。而透视除法将在后面交给硬件执行。

像素着色器:针对每一个像素片段而运行的函数。具有以下特性:
1.我们会在光栅化处理期间对顶点着色器(或几何着色器)输出的顶点属性进行插值。然后将这些插值数据传至像素着色器中作为它的输入。
2.只要为像素着色器指定了输入数据,它就会为像素片段计算出一个对应的颜色。
3.由于像素片段可能会在像素着色器中被裁减掉,所以在确定后台缓冲区某一像素的过程中,可能会存在多个候选的像素片段。
4.早期深度剔除技术指的是在执行像素着色器之前,先执行深度测试。如果已经确定某像素片段被剔除,那么像素着色器将不再对它进行处理。

猜你喜欢

转载自blog.csdn.net/zjz520yy/article/details/116158238
今日推荐