《DirectX12 3D游戏开发实战》读书笔记3:D3D12初始化

初始化步骤

  1. 用D3D12CreateDevice函数创建ID3D12Device接口实例

  2. 创建一个ID3D12Fence对象,并查询描述符的大小

  3. 检测用户设备对4xMSAA质量级别的支持

  4. 依次创建命令队列、命令列表分配器和主命令列表

  5. 描述并创建交换链

  6. 创建应用程序所需要的描述符堆

  7. 调整后台缓冲区大小

  8. 创建深度/模板缓冲区及与之关联的深度/模板视图

  9. 设置视口(veiw port)和裁剪矩形(scissor rectangle)

创建设备

初始化D3D12必须先创建D3D12设备(ID3D12Device),此设备代表一个显示适配器。D3D设备既可以检测系统环境对功能的支持情况,又能创建所有其他的D3D接口对象(如视图、资源和命令列表)。

启用调试层:

#if defined(DEBUG)||defined(_DEBUG)
{
    ComPtr<ID3D112Debug> debugController;
    ThrowIfFailed(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController)));
    debugController->EnableDebugLayer();
}
#endif

调试层启用后,发生错误时将在输出窗口输出调试信息

创建D3D12设备:

HRESULT WINAPI D3D12CreateDevice(IUnknown* pAdapter,
                                D3D_FEATURE_LEVEL MinimumFeatureLevel,
                                REFIID riid,
                                void** ppDevice);
/*
第一个参数pAdapter:
指定在创建设备时使用的显示适配器。若将此参数设为nullptr则使用主显示适配器
第二个参数MinimumFeatureLevel:
应用程序需要硬件所支持的最低功能级别,若硬件不支持,则创建失败
第三个参数riid:
所建ID3D12Device接口的COM ID
第四个参数ppDevice:
返回所创建的D3D12设备
*/

当创建失败时:

if(FAILED(hardwareResult))//在调用创建设备的函数时,将返回值赋值给了hardwareResult
{
    ComPtr<IDXGIAdapter> pWarpAdapter;
    ThrowIfFailed(mdxgiFactory->EnumWrapAdapter(IID_PPV_ARGS(&pWarpAdapter)));
    
    ThrowIfFailed(D3D12CreateDevice(
    pWarpAdapter.Get(),
    D3D_FEATURE_LEVEL_11_0,
    IID_PPV_ARGS(&md3dDevice)));
}

这是回退至软件适配器WARP设备,Windows7及以下版本最高支持10.1,在Windows8中支持11.1,Windows10以上支持12.1

为了创建WARP设备,需要先创建一个IDXGIFactory4对象,并通过它枚举WARP适配器

ComPtr<IDXGIFactory4> mdxgiFactory;
CreateDXGIFactory1(IID_PPV_ARGS(&mdxgiFactory));
mdxgiFactory->EnumWarpAdapter(IID_PPV_ARGS(&pWarpAdapter));

作为DXGI的一部分,mdxgiFactory也可以用来创建交换链

创建围栏并获取描述符大小

设备创建完毕就可以为GPU与CPU的同步创建围栏了,不同的描述符在不同的GPU上的大小不同,因而需要查询相关信息,随后将描述符大小缓存起来,需要时可以进行直接引用:

ThrowIfFailed(md3dDevice->CreateFence(
	0,D3D12_FENCE_FLAG_NONE,IID_PPV_ARGS(&mFence)));
mRtvDescriptorSize=md3dDevice->GetDescriptorHandleIncrementSize(
	D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
mDsvDescriptorSize=md3dDevice->GetDescriptorHandleIncrementSize(
    D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
mCbvUavDescriptorSize=md3dDevice->GetDescriptorHandleIncrementSize(
    D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

检测对4xMSAA质量级别的支持

一切支持D3D11的设备上所有的渲染目标格式均支持4X MSAA。

但是为了保险起见,对质量级别的检测还是不可或缺,可以用以下代码实现

D3D12_FEATURE_DATA_MULTISAMPLE_QUALITY_LEVELS msQualityLevels;
msQualityLevels.Format=mBackBufferFormat;
msQualityLevels.SampleCount=4;
msQualityLevels.Flags=D3D12_MULTISAMPLE_QUALITY_LEVELS_FLAG_NONE;
msQualityLevels.NumQualityLevels=0;
ThrowIfFailed(md3dDevice->CheckFeatureSupport(
	D3D12_FEATURE_MULTISAMPLE_QUALITY_LEVELS,
	&msQualityLevels,
	sizeof(msQualityLevels)));

m4xMsaaQuality=msQualityLevles.NumQualityLevels;
assert(m4xMsaaQuality>0&&"Unexpected MSAA quality level.");

因为所使用的平台必然支持4X MSAA功能,其返回值应该也总是大于0,所以对此做出上述断言

创建命令队列与命令列表

ID3D12CommandQueue接口表示命令队列,ID3D12CommandAllocator接口表示命令分配器,ID3D12GraphicsCommandList接口表示命令列表。

这几种对象的创建流程:

ComPtr<ID3D12CommandQueue> mCommandQueue;
ComPtr<ID3D12CommandQueue> mDirectCmdListAlloc;
ComPtr<ID3D12CommandQueue> mCommandList;
void D3DApp::CreateCommandObjects()
{
	D3D12_COMMAND_QUEUE_DESC queueDesc={};
    queueDesc.Type=D3D12_COMMAND_LIST_TYPE_DIRECT;
    queueDesc.Flags=D3D12_COMMAND_QUEUE_FLAG_NONE;
    ThrowIfFaild(md3dDevice->CreateCommandQueue(
    	&queueDesc,IID_PPV_ARGS(&mCommandQueue)));
    ThrowIfFailed(md3dDevice->CreateCommandAllocator(
    	D3D12_COMMAND_LIST_TYPE_DIRECT,
    	IID_PPV_ARGS(mDirectCmdListAlloc.GetAddressOf())));
    
    ThrowIfFailed(md3dDevice->CreateCommandList(
    	0,
    	D3D12_COMMAND_LIST_TYPE_DIRECT,
    	mDirectCmdListAlloc.Get(),
    	nullptr,
    	IID_PPV_ARGS(mCommandList.GetAddressOf())));
    
    //首先将命令列表关闭,这是因为在使用前要先将其重置,而重置需要将其关闭
    mCommandList->Close();
}

由于这个示例程序不发出任何绘制命令,流水线状态对象(pipeline state object)被设置为一个空指针

描述并创建交换链

首先填写一个DXGI_SWAP_CHAIN_DESC结构体实例,用它来描述欲创建交换链的特性

typedef struct DXGI_SWAP_CHAIN_DESC
{
    DXGI_MODE_DESC BufferDesc;
    DXGI_SAMPLE_DESC SampleDesc;
    DXGI_USAGE BufferUsage;
    UINT BufferCount;
    HWND OutPutWindow;
    BOOL Windowed;
    DXGI_SWAP_EFFECT SwapEffect;
    UINT Flags;
}DXGI_SWAP_CHAIN_DESC;
/*
第一个参数BufferDesc:
这个结构体描述了待创建后台缓冲区的属性。这里仅关注它的长与宽,除此之外还有刷新率、显示格式、缩放模式、扫描方式,后续更新的DXGI_MODE_DESC1还有屏幕立体声
第二个参数SampleDesc:
多重采样的质量级别以及对每个像素的采样次数
第三个参数BufferUsage
1.DXGI_USAGE_BACK_BUFFER 图面或资源用作后台缓冲区。 创建交换链时,无需传递 DXGI_USAGE_BACK_BUFFER 。 但是,在调用 IDXGIResource::GetUsage 并获取DXGI_USAGE_BACK_BUFFER时,可以确定资源是否属于交换链。
2.DXGI_USAGE_DISCARD_ON_PRESENT 此标志仅供内部使用。
3.DXGI_USAGE_READ_ONLY 仅使用表面或资源进行读取
4.DXGI_USAGE_RENDER_TARGET_OUTPUT 使用图面或资源作为输出呈现目标
5.DXGI_USAGE_SHADER_INPUT 使用图面或资源作为着色器的输入
6.DXGI_USAGE_SHARED 共享图面或资源
7.DXGI_USAGE_UNORDERED_ACCESS 使用图面或资源进行无序访问
这里将BufferUsage设为DXGI_USAGE_RENDER_TARGET_OUTPUT将数据渲染至后台缓冲区
第四个参数BufferCount:
交换链中所用的缓冲区数量,这里指定为2
第五个参数OutputWindow:
渲染窗口的句柄
第六个参数Windowed:
若指定为true则程序将在窗口模式下运行,若指定为false则采用全屏模式
第七个参数SwapEffect:
这个参数的值里D3D11和D3D12有一些区别,可以在网上搜,这里使用DXGI_SWAP_EFFECT_FLIP_DISCARD
第八个参数Flags:
可选标志。若将其指定为DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH,那么当程序切换为全屏模式时,它将选择最适于当前应用窗口尺寸的显示模式。若没有指定这个标志,当程序切换为全屏模式时,将采用当前桌面的显示模式
*/   

DXGI_MODE_DESC:

typedef struct DXGI_MODE_DESC
{
    UINT Width;
    UINT Height;
    DXGI_RATIONAL RefreshRate;
    DXGI_FORMAT Format;
    DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;
    DXGI_MODE_SCALING Scaling;
}DXGI_MODE_DESC;

创建一个交换链:

HRESULT IDXGIFactory::CreateSwapChain(
	IUnknow *pDevice,				//指向ID3D12CommandQueue接口的指针
	DXGI_SWAP_CHAIN_DESC *pDesc,	//指向描述交换链的结构体的指针
	IDXGISwapChain **ppSwapChain);	//返回所创建的交换链接口

调用书中D3DApp的创建交换链的函数可以自动释放前一个交换链并重新创建一个交换链,由于D3D12不支持直接创建MSAA交换链,因此可以用这种方法在运行时实现修改MSAA配置的功能

DXGI_FORMAT mBackBufferFormat=DXGI_FORMAT_R8G8B8A8_UNORM;

void D3DApp::CreateSwapChain(){
    mSwapChain.Reset();//释放交换链
    
    DXGI_SWAP_CHAIN_DESC sd;
    sd.BufferDesc.Width=mClientWidth;
    sd.BufferDesc.Height=mClientHeight;
    sd.BufferDesc.RefreshRate.Numerator=60;
    sd.BufferDesc.RefreshRate.Denominator=1;
    sd.BufferDesc.Format=mBackBufferFormat;
    sd.BufferDesc.ScanlineOrdering=DXGI_MODE_SCANLINE_ORDER_UNSPECFIED;
    sd.BufferDesc.Scaling=DXGI_MODE_SCALING_UNSPECIFIED;
    sd.SampleDesc.Count=m4xMsaaState?4:1;
    sd.SampleDesc.Quality=m4xMsaaState?(m4xMsaaQuality-1):0;
    sd.BufferCount=SwapChainBufferCount;
    sd.OutputWindow=mhMainWnd;
    sd.Windowed=true;
    sd.SwapEffect=DXGI_SWAP_EFFECT_FLIP_DISCARD;
    sd.Flags=DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH;
    
    ThrowIfFailed(mdxgiFactory->CreateSwapChain(//使用命令队列刷新交换链
    	mCommandQueue.Get(),
    	&sd,
    	mSwapChain.GetAddressOf()));
}

创建描述符堆

创建描述符堆以储存程序中要用到的描述符/视图。D3D12以ID3D12DescriptorHeap接口表示描述符堆,并用ID3D12Device::CreateDescriptorHeap方法创建。以这个示例程序为例,需要为交换链中SwapChainBufferCount个用于渲染数据的缓冲区资源创建对应的渲染目标视图(RTV),并为用于深度测试的深度/模板缓冲区创建一个深度/模板视图(DSV)。

所以需要创建两个描述符堆,其一用于存储SwapChainBufferCount个RTV,另一个用于存储一个DSV:

ComPtr<ID3D12DescriptorHeap> mRtvHeap;
ComPtr<ID3D12DescriptorHeap> mDsvHeap;
void D3DApp:CreateRtvAndDescriptorHeaps()
{
    D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
    rtvHeapDesc.NumDescriptors=SwapChainBufferCount;
    rtvHeapDesc.Type=D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    rtvHeapDesc.Flags=D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    rtvHeapDesc.NodeMask=0;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
    	&rtvHeapDesc,IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
    
    D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
    dsvHeapDesc.NumDescriptors=1;
    dsvHeapDesc.Type=D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
    dsvHeapDesc.Flags=D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    dsvHeapDesc.NodeMask=0;
    ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
    	&dsvHeapDesc,IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
}

在此书的应用框架下有以下定义:

static const int SwapChainBufferCount=2;
int mCurrentBackBuffer=0;

其中mCurrentBackBuffer为记录当前后台缓冲区的索引

在创建描述符堆之后,还需要能够访问其中的所存的描述符。在程序中是使用句柄来引用描述符的,使用ID3D12DescriptorHeap::GetCPUDescriptorHandleForHeapStart方法来获得描述符堆中第一个描述符的句柄。使用以下方法来获得当前后台缓冲区的RTV与DSV:

ID3D12_CPU_DESCRIPTOR_HANDLE D3DApp::CurrentBackBufferVeiw()const
{
    //CD3DX12构造函数根据指定偏移量找到当前后台缓冲区的RTV
    return CD3DX12_CPU_DESCRIPTOR_HANDLE(
    	mRtvHeap->GetCPUDescriptorHandleForHeapStart(),//堆中首个句柄
    	mCurrentBackBuffer,//偏移至后台缓冲区描述符句柄的索引
    	mRtvDescriptorSize);//描述符所占的字节大小
}
D3D12_CPU_DESCRIPTOR_HEAP_HANDLE D3DApp::DepthStencilVeiw()const
{
    return mDsvHeap->GetCPUDescriptorHandleForHeapStart();
}

创建渲染目标视图

资源不可与渲染流水线中的阶段直接绑定,所以必须先为资源创建视图(描述符),比如说,为了将后台缓冲区绑定到流水线的输出合并阶段(output merger stage,这样D3D才能向其渲染),就需要为该后台缓冲区创建一个渲染目标视图。

第一个步骤就是获得存于交换链中的缓冲区资源

HRESULT IDXGISwapChain::GetBuffer(
	UINT Buffer,
	REFIID riid,
	void **ppSurface);
/*
第一个参数Buffer:
希望获得的特定后台缓冲区的索引
第二个参数riid:
希望获得的ID3D12Resources接口的COM ID,这个接口将物理内存与堆资源抽象组织为可处理的数据数组与多维数组,从而使CPU与GPU可以对这些资源进行读写
第三个参数:
返回一个指向接口的指针,这便是希望获得的后台缓冲区
*/

**调用IDXGISwapChain::GetBuffer方法会增加相关后台缓冲区的COM计数,所以在每次使用后一定要将其释放。**通过ComPtr可以实现这一点

使用ID3D12Device::CreateRenderTargetView方法为获取的后台缓冲区创建渲染目标视图

void ID3D12Device::CreateRenderTargetView(
	ID3D12Resource *pResource,
	const D3D12_RENDER_TARGET_VIEW_DESC *pDesc,
	D3D12_CPU_DESCRIPTOR_HANDLE DestDesscriptor);
/*
第一个参数pResource:
指定用作渲染目标的资源,在上面的例子是后台缓冲区(即为后台缓冲区创建了一个渲染目标视图)
第二个参数pDesc:
指向D3D12_RENDER_TARGET_VIEW_DESC数据结构实例的指针。该结构体描述了资源中元素的数据类型(格式)。若该资源在创建时已经指定了具体格式,那么就可以将这个指针设为空,表示采用该资源创建时的格式,为它的第一个mipmap层级(后台缓冲区只有一种mipmap层级,有关mipmap的内容暂时不展开)创建一个视图。由于已经指定了后台缓冲区的格式,因此就将这个参数设置为空指针
第三个参数DescDescriptor:
引用所创建渲染目标视图的描述符句柄
*/

为交换链中的每一个缓冲区创建一个RTV:

ComPtr<ID3D12Resource> mSwapChainBuffer[SwapChainBufferCount];
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHeapHanle(
	mRtvHeap->GetCPUDescriptorHanleForHeapStart());
for(UINT i=0;i<SwapChainBufferCount;i++)
{
	//获得交换链内的第i个缓冲区
	ThrowIfFailed(mSwapChain->GetBuffer(
		i,IID_PPV_ARGS(&mSwapChainBuffer[i])));
	
	//为此缓冲区创建一个RTV
	md3dDevice->CreateRenderTargetView(
		mSwapChainBuffer[i].Get(),nullptr,rtvHeapHandle);
	
	//偏移至描述符堆的下一个缓冲区
	rtvHeapHandle.Offset(1,mRtvDescriptorSize);
}

创建深度/模板缓冲区及其视图

**由于纹理是一种GPU资源,因此要通过填写D3D12_RESOURCE_DESC结构体来描述纹理资源,再用ID3D12Device::CreateCommittedResource方法来创建它。**D3D12_RESOURCE_DESC结构体的定义如下:

typedef struct D3D12_RESOURCE_DESC
{
	D3D12_RESOURCE_DIMENSION Dimension;
	UINT64 Alignment;
	UINT64 Width;
	UINT Height;
	UINT16 DepthOrArraySize;
	UINT16 MipLevels;
	DXGI_FORMAT Format;
	DXGI_SAMPLE_DESC SampleDesc;
	D3D12_TEXTURE_LAYOUT Layout;
	D3D12_RESOURCE_MTSC_FLAG Misc Flags;
}D3D12_RESOURCE_DESC;
/*
第一个参数Dimension:
资源的维度,该枚举类型:
enum D3D12_RESOURCE_DIMENSION
{
	D3D12_RESOURCE_DIMENSION_UNKNOWN=0,
	D3D12_RESOURCE_DIMENSION_BUFFER=1,
	D3D12_RESOURCE_DIMENSION_TEXTURE1D=2,
	D3D12_RESOURCE_DIMENSION_TEXTURE2D=3,
	D3D12_RESOURCE_DIMENSION_TEXTURE3D=4
}D3D12_RESOURCE_DIMENSION;
第二个参数Width:
以纹素为单位来表示的纹理宽度,对缓冲区来说,此项是缓冲区所占的字节数
第三个参数Height:
以纹素为单位来表示的纹理高度
第四个参数DepthOrArraySize:
以纹素为单位表示的纹理深度,对一维与二维纹理来说是纹理数组的大小,D3D12不存在三维纹理数组的概念
第五个参数MipLevels:
mipmap层级的数量,深度缓冲区只能有一个
第六个参数Format:
DXGI_FORMAT枚举类型的某个值,用于指定纹素的格式,深度缓冲可用的格式见之前的笔记
第七个参数SampleDesc:
多重采样的质量级别以及对每个像素的采样次数
第八个参数Layout:
D3D12_TEXTURE_LAYOUT枚举类型的一个值,用于指定纹理的布局,暂时不用考虑,设为D3D12_TEXTURE_LAYOUT_UNKNOWN
第九个参数Flags:
与资源有关的杂项标志,对于一个深度/模板缓冲区资源来说,要指定为D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL
*/

GPU资源都存储于堆中,本质是具有特定属性的显存块,使用ID3D12Device::CreateCommittedResource方法将根据给出的属性创建一个资源与一个堆,并将资源提交到堆中

HRESULT ID3D12Device::CreateCommittedResource(
	const D3D12_HEAP_PROPERTIES *pHeapProperties,
	D3D12_HEAP_FLAGS HeapFlags,
	const D3D12_RESOURCE_DESC *pDesc,
	D3D12_RESOURCE_STATES InitialReesourceState,
	const D3D12_CLEAR_VALUE *pOptimizedClearValue,
	REFIID riidResource,
	void **ppvResource);

typedef struct D3D_HEAP_PROPERTIES{
    D3D12_HEAP_TYPE Type;
    D3D12_CPU_PAGE_PROPERTY CPUPageProperty;
    D3D12_MEMORY_POOL MemoryPoolPreference;
    UINT CreationNodeMask;
    UINT VisibleNodeMask;
}D3D12_HEAP_PROPERTIES;
/*
第一个参数pHeapProperties:
堆所具有的属性,有些属性是为了高级用法而设,目前只需关心D3D12_HEAP_TYPE这一枚举类型,其中的主要成员:
1.D3D12_HEAP_TYPE_DEFAULT:默认堆,在这里存放的资源只有GPU可以访问,比如说GPU会读取深度/模板缓冲区但CPU从不访问它,所以将其放入默认堆
2.D3D12_HEAP_TYPE_UPLOAD:上传堆,这里提交的都是需要从CPU上传到GPU的资源
3.D3D12_HEAP_TYPE_READBACK:回读堆,这里存放的都是需要CPU读取的资源
4.D3D12_HEAP_TYPE_CUSTOM:用于高级场景,不展开描述
第二个参数HeapFlags:
与堆有关的额外选项标志,通常将其设为D3D12_HEAP_FLAG_NONE
第三个参数pDesc:
指向一个D3D12_RESOURCE_DESC实例的指针,用它描述待建的资源
第四个参数InitialResourceState:
不管何时,每个资源都处于一种特定的使用状态。在资源创建时,需要用此参数来设置它的初始状态,对于深度/模板缓冲区来说,通常将其初始状态设置为D3D12_RESOURCE_STATE_COMMON,再利用ResourceBarrier方法辅以D3D12_RESOURCE_STATE_STATE_DEPTH_WRITE状态,将其转换为可以绑定在渲染流水线上的深度/模板缓冲区
第五个参数pOptimizedClearValue:
指向一个D3D12_CLEAR_VALUE对象的指针,它描述了一个用于清除资源的优化值,可以提高清除操作的执行速度,若不希望指定优化清除值,可以将此参数设为nullptr
struct D3D12_CLEAR_VALUE
{
	DXGI_FORMAT Format;
	union
	{
		FLOAT Color[4];
		D3D12_DEPTH_STENCIL_VALUE DepthStencil;
	};
}D3D12_CLEAR_VALUE;
第六个参数riidResource:
希望获得的ID3D12Resource希望获得的ID3D12Resource接口的COM ID
第七个参数ppvResource:
返回一个指向ID3D12Resource的指针,即新建的资源
*/

在使用深度/模板缓冲区之前,还需要创建相关的深度/模板视图,并将它绑定到渲染流水线上,这个流程类似于创建渲染目标视图:

D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension=D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment=0;
depthStencilDesc.Width=mClientWidth;
depthStencilDesc.Height=mClientHeight;
depthStencilDesc.DepthOrArraySize=1;
depthStencilDesc.MipLevels=1;
depthStencilDesc.Format=mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count=m4xMsaaState?4:1;
depthStencilDesc.SampleDesc.Quality=m4xMsaaState?(m4xMsaaQuality-1):0;
depthStencilDesc.Layout=D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags=D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;

D3D12_CLEAR_VALUE optClear;
opClear.Format=mDepthStencilFormat;
opClear.DepthStencil.Depth=1.0f;
opClear.DepthStencil.Stencil=0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
	&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
	D3D12_HEAP_FLAG_NONE,
	&depthStencilDesc,
	D3D12_RESOURCE_STATE_COMMON,
	&optClear,
	IID_PPV_ARGS(mDepthStencilBuffer.GetAddressOf())));

//利用此资源的格式,为整个资源的第0mip层创建描述符
md3dDevice->CreateDepthStencilView(
	mDepthStencilBuffer.Get(),
	nullptr,
	DepthStencilView());

//将资源从初始状态转换为深度缓冲区
mCommandList->ResourceBarrier(
	1,
	&CD3DX12_RESOURCE_BARRIER::Transition(
		mDepthStencilBuffer.Get(),
		D3D12_RESOURCE_STATE_COMMON,
		D3D12_RESOURCE_STATE_DEPTH_WRITE));

刚才的CD3DX12_HEAP_PROPERTIES辅助构造函数用于创建堆的结构体,它的具体实现:

explicit CD3DX12_HEAP_PROPERTIES(
	D3D12_HEAP_TYPE Type,
	UINT creationNodeMask=1,
	UINT nodeMask=1)
{
	Type=type;
	CPUPageProperty=D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
	MemoryPoolPreference=D3D12_MEMORY_POOL_UNKNOWN;
	CreationNodeMask=creationNodeMask;
	VisibleNodeMask=1;
}

CreateDepthStencilView方法的第二个参数是指向D3D12_DEPTH_STENCIL_VIEW_DESC结构体的指针,这个结构体描述了资源中元素的数据类型,若资源在创建时已经指定了具体格式那么就可以将该参数设为空指针,表示以此资源创建时的格式为其第一个mipmap层级创建一个视图

设置视口

有时对3D场景进行显示时,希望它全屏显示,占用整个后台缓冲区,有时希望它仅占用后台缓冲区的某一确定的子矩形区域,后台缓冲区的这种子区域称为视口

通过下列结构体对其进行描述:

typedef struct D3D12_VIEWPORT{
	FLOAT TopLeftX;
    FLOAT TopLeftY;
    FLOAT Width;
    FLOAT Height;
    FLOAT MinDepth;
    FLOAT MaxDepth;
}D3D12_VIEWPORT;

前四个成员定义了视口矩形相对于后台缓冲区的绘制范围,x轴方向水平向右,y轴方向垂直向下,视口坐标最小值为D3D12_VIEWPORT_BOUNDS_MIN为-32768

其余两个成员负责将深度值从区间[0,1]转换到区间[MinDepth,MaxDepth],当这两个值均设为0时,使用这个视口绘制的物体会比场景中其他物体更加靠前,但大多情况下会将这两个值分别设为0和1

填写好这个结构体就可以使用ID3D12GraphicsCommandList::RSSetViewPorts方法设置D3D中的视口了。下面的示例是通过创建并设置一个视口,将场景绘制到整个后台缓冲区:

D3D12_VIEWPORT vp;
vp.TopLeftX=0.0f;
vp.TopLeftY=0.0f;
vp.Width=static_cast<float>(mClientWidth);
vp.Height=static_cast<float>(mClientHeight);
vp.MinDepth=0.0f;
vp.MaxDepth=1.0f;

mCommandList->RSSetViewports(1,&vp);

第一个参数是要绑定的视口数量(因为某些高级效果与功能需要用到多个视口),第二个参数是指向一个视口数组的指针

注意:

  • 不能为同一渲染目标指定多个视口,而多视口是一种用于对多个渲染目标同时进行渲染的高级技术
  • 命令列表一旦被重置,视口也需要重置

事实上,可以使用两个视口分别占用屏幕的左右半边来实现同一主机的双人游戏,一侧显示玩家1视角的画面另一侧显示玩家2视角的画面

设置裁剪矩形

可以相对于后台缓冲区定义一个裁剪矩形,在此矩形之外的所有像素都会被剔除(即指定区域之外的图像不会被光栅化到后台缓冲区),这样可以用于节省那些被UI覆盖的部分的资源

裁剪矩形由类型为RECT的D3D12_RECT结构体(typedef RECT D3D12_RECT;)定义而成:

typedef struct tagRECT
{
	LONG left;
	LONG top;
	LONG right;
	LONG bottom;
}RECT;

在D3D中要用ID3D12GraphicsCommandList::RSSetScissorRects方法来设置裁剪矩形。下面的示例将创建并设置一个覆盖后台缓冲区左上角四分之一区域的裁剪矩形:

mScissorRect={0,0,mClientWidth/2,mClientHeight/2};
mCommandList->RSSetScissorRects(1,&mScissorRect);

RSSetScissorRects方法的第一个参数是要绑定的裁剪矩形数量(为了实现一些高级效果可能会使用多个裁剪矩形),第二个参数是指向一个裁剪矩形数组的指针。

注意:

  • 不能为同一个渲染目标指定多个裁剪矩形,多裁剪矩形是一种用于同时对多个目标进行渲染的高级技术
  • 裁剪矩形需要随着命令列表的重置而重置

声明

大部分内容出自《DirectX12 3D游戏开发实战》,部分内容来源于网络,侵删

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值