创建Direct3D 11设备
首先创建一个窗口,当显示了一个窗口后,下面继续创建一个Direct3D 11设备,这个设备用于绘制3D场景。首先必须创建三个对象,包括设备,设备上下文,交换链。
当显示了一个窗口后,下面继续创建一个Direct3D 11设备,这个设备用于绘制3D场景。首先必须创建三个对象:一个设备、一个立即执行上下文(immediate context)(设备上下文的一种)和一个交换链(Swap Chain),立即执行上下文对象是Direct3D 11中新添加的。
设备:用于创建资源。
立即执行上下文:立即执行上下文用于将内容绘制到缓存。
交换链:交换链表示对缓冲(即设备绘制的和显示在屏幕上的内容)的操作。交换链包含两个或两个以上的缓冲,主要是前缓冲和后备缓冲,它们就是设备绘制形成的纹理,用于显示在屏幕上。前缓冲(front buffer)就是当前显示在屏幕上的内容,这个缓冲是只读的,无法修改。后备缓冲(back buffer)是设备将要绘制的渲染目标,一旦它完成了绘制操作,交换链就会通过交换前缓冲和后备缓冲,将后备缓冲的内容显示在屏幕上,此时后备缓冲就变成了前缓冲。
创建设备,设备上下文,交换链
要创建设备,设备上下文,交换链,可以使用D3D11CreateDeviceAndSwapChain()函数
/**
* 创建设备及上下文
* @param pAdapter 选择相应的图形适配器,设为NULL以选择默认的适配器
* @param DriverType 设置驱动类型,一般毫无疑问选择硬件加速,即D3D_DRIVER_TYPE_HARDWARE,此时下一个参数就是NULL
* @param Software DriverType选择硬件加速,当DriverType参数选择D3D_DRIVER_TYPE_HARDWARE时,该参数为NULL
* @param Flags 可选参数,一般为NULL,可以设为D3D11_CREATE_DEVICE_DEBUG、D3D11_CREATE_DEVICE_SINGLETHREADED,或两者一起,前者让要用于调试时收集信息,后者在确定程序只在单线程下运行时设置为它,可以提高性能
* @param pFeatureLevels 我们提供给程序的特征等级的一个数组
* @param FeatureLevels 数组中元素个数
* @param SDKVersion 恒定为D3D11_SDK_VERSION
* @param pSwapChainDesc 这个指针指向一个交换链接描述(DXGI_SWAP_CHAIN_DESC结构)
* @param ppSwapChain 返回一个IDXGISwapChain对象的指针地址,这个交换链接用于渲染。
* @param ppDevice 设备指针的地址,注意设备是指针类型,这里传递的是指针的地址(二维指针,d3d程序中所有的接口都声明为指针类型!)
* @param pFeatureLevel 最后程序选中的特征等级,我们定义相应的变量,传递它的地址进来
* @param ppImmediateContext 立即执行上下文指针的地址,要求同设备指针。
*/
HRESULT WINAPI D3D11CreateDeviceAndSwapChain(
_In_opt_ IDXGIAdapter* pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
_In_reads_opt_( FeatureLevels ) CONST D3D_FEATURE_LEVEL* pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
_In_opt_ CONST DXGI_SWAP_CHAIN_DESC* pSwapChainDesc,
_Out_opt_ IDXGISwapChain** ppSwapChain,
_Out_opt_ ID3D11Device** ppDevice,
_Out_opt_ D3D_FEATURE_LEVEL* pFeatureLevel,
_Out_opt_ ID3D11DeviceContext** ppImmediateContext
);
从函数中我们可以看出,我们创建的设备上下文是一个立即执行上下文
关于设备上下文
我们创建的立即执行上下文是设备上下文的一种,还有一种是延迟执行上下文(DeferredContext),使用 ID3D11Device::CreateDeferredContext函数创建。该上下文主要用于Direct3D 11支持的多线程程序。
1.在主线程中使用立即执行上下文。
2.在工作线程总使用延迟执行上下文。
(a)每个工作线程可以将图形指令记录在一个命令列表(ID3D11CommandList)中。
(b)随后,每个工作线程中的命令列表可以在主渲染线程中加以执行。
在多核系统中,可并行处理命令列表中的指令,这样可以缩短编译复杂图形所需的时间。
函数的第八个参数是一个指向DXGI_SWAPCHAIN_DESC结构体的指针,所以要创建交换链,我们需要设置一个DXGI_SWAPCHAIN_DESC结构体。
typedef struct DXGI_SWAP_CHAIN_DESC {
DXGI_MODE_DESC BufferDesc; //一个DXGI_MODE_DESC结构体,用来描述后台缓存显示模式。
DXGI_SAMPLE_DESC SampleDesc; //用来开启多重采样(multi-sampling)。SampleDesc的Count设置为1,Quality设置为0,这样就禁用了多重采样。
DXGI_USAGE BufferUsage; //使用后备缓冲区的方式。将BackBufferUsage设置为DXGI_USAGE_RENDER_TARGET_OUTPUT,表示将绘制到后备缓冲。(描述后台缓存的表面用法和CPU访问设置。后台缓存能使用渲染输入或渲染目标输出)
UINT BufferCount; //这个值用于描述交换链的缓存数量,包含前置缓存。(即若有一个前置缓存和一个后备缓存,则该值为2)
HWND OutputWindow; //表示交换链将使用的窗口,在这个窗口上我们显示图像。
BOOL Windowed; //如果值为true将会以窗口模式显示;全屏则为false。
DXGI_SWAP_EFFECT SwapEffect; //描述处理目前的缓存和目前的表面的设置。
UINT Flags; //描述交换链接行为设置。
} DXGI_SWAP_CHAIN_DESC;
其中第一个元素BufferDesc也是一个结构体,类型为DXGI_MODE_DESC,定义如下:
typedef struct DXGI_MODE_DESC
{
UINT Width; // 后台缓冲区宽度
UINT Height; // 后台缓冲区高度
DXGI_RATIONAL RefreshRate; // 显示刷新率
DXGI_FORMAT Format; // 后台缓冲区像素格式
DXGI_MODE_SCANLINE_ORDER ScanlineOrdering; // display scanline mode
DXGI_MODE_SCALING Scaling; // display scaling mode
} DXGI_MODE_DESC;
补充:可以使用IDXGIFactory::CreateSwapChain()函数创建交换链,也可以使用D3D11CreateDevice()函数创建设备和设备上下文。
检测多重采样质量
检测多重采样质量是为以后做抗锯齿做铺垫。我们使用ID3D11Device::CheckMultisampleQualityLevels函数检测多重采样质量。
/** ID3D11Device::CheckMultisampleQualityLevels
* 获取多重采样中可用质量等级
* @param Format 贴图的格式
* @param SampleCount 多重采样的样本数(比如用四重采样,则该参数填4)
* @param pNumQualityLevels 适配器支持的质量级别
*/
HRESULT CheckMultisampleQualityLevels(
DXGI_FORMAT Format,
UINT SampleCount,
UINT *pNumQualityLevels
);
创建渲染目标视图
下一步需要创建一个渲染目标视图(render target view)。资源不能被直接绑定到一个管线阶段,我们必须为资源创建资源视图(resource view),然后把资源视图绑定到不同的管线阶段。渲染目标视图是Direct3D 11中一种资源视图。
因为我们需要将交换链中的后备缓冲绑定为一个渲染目标,所以需要创建一个渲染目标视图,这样Direct3D 11就可以在其上进行绘制了。我们首先调用 GetBuffer() 方法获取后备缓冲对象。我们可以使用一个D3D11_RENDERTARGETVIEW_DESC结构体表示要创建的渲染目标视图,这个结构体通常是CreateRenderTargetView()方法的第二个参数。但是,在本教程中,默认的渲染目标视图就能满足需要,所以第二个参数为NULL表示使用默认的渲染目标视图。创建了渲染目标视图后,我们就可以调用 OMSetRenderTargets() 方法将它绑定到图形管线,这样管线的绘制输出被写到了后备缓冲中。创建并设置渲染目标视图的代码如下:
// Create a render target view
ID3D11Texture2D *pBackBuffer;
hr = g_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (LPVOID* )&pBackBuffer );
if( FAILED( hr ) )
return hr;
hr = g_pd3dDevice->CreateRenderTargetView( pBackBuffer, NULL, &g_pRenderTargetView );
pBackBuffer->Release();
if( FAILED( hr ) )
return hr;
g_pd3dDevice->OMSetRenderTargets( 1, &g_pRenderTargetView, NULL );
创建深度模版缓冲区及其视图
我们现在需要创建深度/模板缓冲区。深度缓冲区只是一个存储深度信息的2D纹理(如果使用模板,则模板信息也在该缓冲区中)。要创建纹理,我们必须填充一个D3D11_TEXTURE2D_DESC结构体来描述所要创建的纹理,然后再调用ID3D11Device::CreateTexture2D方法。该结构体的定义如下:
typedef struct D3D11_TEXTURE2D_DESC {
UINT Width; //纹理的宽度,单位为纹理元素(texel)
UINT Height; //纹理的高度,单位为纹理元素(texel)
UINT MipLevels; //多级渐近纹理层(mipmap level)的数量。对于深度/模板缓冲区来说,我们的纹理只需要一个多级渐近纹理层
UINT ArraySize; //在纹理数组中的纹理数量。对于深度/模板缓冲区来说,我们只需要一个纹理
DXGI_FORMAT Format; //一个DXGI_FORMAT枚举类型成员,它指定了纹理元素的格式。对于深度/模板缓冲区来说,它必须是4.1.5节列出的格式之一
DXGI_SAMPLE_DESC SampleDesc; //多重采样数量和质量级别
D3D10_USAGE Usage; //表示纹理用途的D3D11_USAGE枚举类型成员
UINT BindFlags; //指定该资源将会绑定到管线的哪个阶段。对于深度/模板缓冲区,该参数应设为D3D11_BIND_DEPTH_STENCIL
UINT CPUAccessFlags; //指定CPU对资源的访问权限,对于深度/模板缓冲区来说,该参数应设为0
UINT MiscFlags; //可选的标志值,与深度/模板缓冲区无关,所以设为0
} D3D11_TEXTURE2D_DESC;
第七个元素Usage有四个可选值,分别为:
- D3D11_USAGE_DEFAULT:表示GPU(graphics processing unit,图形处理器)会对资源执行读写操作。CPU不能读写这种资源。对于深度/模板缓冲区,我们使用D3D11_USAGE_DEFAULT标志值,因为GPU会执行所有读写深度/模板缓冲区的操作。
- D3D10_USAGE_IMMUTABLE:表示在创建资源后,资源中的内容不会改变。这样可以获得一些内部优化,因为GPU会以只读方式访问这种资源。除了在创建资源时CPU会写入初始化数据外,其他任何时候CPU都不会对这种资源执行任何读写操作。
- D3D10_USAGE_DYNAMIC:表示应用程序(CPU)会频繁更新资源中的数据内容(例如,每帧更新一次)。GPU可以从这种资源中读取数据,而CPU可以向这种资源中写入数据。
- D3D10_USAGE_STAGING:表示应用程序(CPU)会读取该资源的一个副本(即,该资源支持从显存到系统内存的数据复制操作)。
第八个元素BindFlags对于深度/模板缓冲区,该参数应设为D3D11_BIND_DEPTH_STENCIL。其他可用于纹理的绑定标志值还有:
- D3D11_BIND_RENDER_TARGET:将纹理作为一个渲染目标绑定到管线上。
- D3D11_BIND_SHADER_RESOURCE:将纹理作为一个着色器资源绑定到管线上。
第九个元素CPUAccessFlags:如果CPU需要向资源写入数据,则应指定D3D11_CPU_ACCESS_WRITE。具有写访问权限的资源的Usage参数应设为D3D11_USAGE_DYNAMIC或D3D11_USAGE_STAGING。如果CPU需要从资源读取数据,则应指定D3D11_CPU_ACCESS_READ。具有读访问权限的资源的Usage参数应设为D3D11_USAGE_STAGING。对于深度/模板缓冲区来说,只有GPU会执行读写操作;所以,我们将该参数设为0,因为CPU不会在深度/模板缓冲区上执行读写操作。
注意:推荐避免使用D3D11_USAGE_DYNAMIC和D3D11_USAGE_STAGING,因为有性能损失。要获得最佳性能,我们应创建所有的资源并将它们上传到GPU并保留其上,只有GPU在读取或写入这些资源。但是,在某些程序中必须有CPU的参与,因此这些标志无法避免,但你应该将这些标志的使用减到最小。
注意:在使用深度/模板缓冲区之前,我们必须为它创建一个绑定到管线上的深度/模板视图。过程与创建渲染目标视图的过程相似。下面的代码示范了如何创建深度/模板纹理以及与它对应的深度/模板视图。
D3D11_TEXTURE2D_DESC depthStencilDesc;
depthStencilDesc.Width = mClientWidth;
depthStencilDesc.Height = mClientHeight;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.ArraySize = 1;
depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
// 使用4X MSAA?——必须与交换链的MSAA的值匹配
if( mEnable4xMsaa)
{
depthStencilDesc.SampleDesc.Count = 4;
depthStencilDesc.SampleDesc.Quality = m4xMsaaQuality-1;
}
// 不使用MSAA
else
{
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
}
depthStencilDesc.Usage = D3D10_USAGE_DEFAULT;
depthStencilDesc.BindFlags = D3D10_BIND_DEPTH_STENCIL;
depthStencilDesc.CPUAccessFlags = 0;
depthStencilDesc.MiscFlags = 0;
ID3D10Texture2D* mDepthStencilBuffer;
ID3D10DepthStencilView* mDepthStencilView;
HR(md3dDevice->CreateTexture2D(
&depthStencilDesc, 0, &mDepthStencilBuffer));
HR(md3dDevice->CreateDepthStencilView(
mDepthStencilBuffer, 0, &mDepthStencilView));
CreateTexture2D的第二个参数是一个指向初始化数据的指针,这些初始化数据用来填充纹理。不过,由于个纹理被用作深度/模板缓冲区,所以我们不需要为它填充任何初始化数据。当执行深度缓存和模板操作时,Direct3D会自动向深度/模板缓冲区写入数据。所以,我们在这里将第二个参数指定为空值。
CreateDepthStencilView的第二个参数是一个指向D3D11_DEPTH_STENCIL_VIEW_DESC的指针。这个结构体描述了资源中这个元素数据类型(格式)。如果资源是一个有类型的格式(非typeless),这个参数可以为空值,表示创建一个资源的第一个mipmap等级的视图(深度/模板缓冲也只能使用一个 mipmap等级)。因为我们指定了深度/模板缓冲的格式,所以将这个参数设置为空值。
将视图绑定到输出合并器阶段
现在我们已经为后台缓冲区和深度缓冲区创建了视图,就可以将些视图绑定到管线的输出合并器阶段(output merger stage),使些资源成为管线的渲染目标和深度/模板缓冲区,我们使用OMSetRenderTargets函数
/** ID3D11DeviceContext::OMSetRenderTargets()
* 绑定一个或更多渲染目标和深度模板缓冲区到管线的输出合并器阶段
* @param NumViews 将要绑定的渲染目标的数量
* @param ppRenderTargetViews 将要绑定的渲染目标视图数组中的第一个元素的指针
* @param pDepthStencilView 将要绑定到管线的深度/模板视图
*/
void OMSetRenderTargets(
UINT NumViews,
ID3D11RenderTargetView * const *ppRenderTargetViews,
ID3D11DepthStencilView *pDepthStencilView
);
注意:我们可以设置一组渲染目标视图,但是只能设置一个深度/模板视图。使用多个渲染目标是一项高级技术。
设置视口
通常我们会把3D场景渲染到整个后台缓冲区上。不过,有时我们只希望把3D场景渲染到后台缓冲区的一个子矩形区域中,如图所示:
我们将后台缓冲区的子矩形区域称为视口(viewport),它由如下结构体描述:
typedef struct D3D11_VIEWPORT {
FLOAT TopLeftX; //定义了相对于窗口客户区的视口矩形范围
FLOAT TopLeftY; //定义了相对于窗口客户区的视口矩形范围
FLOAT Width; //定义了相对于窗口客户区的视口矩形范围
FLOAT Height; //定义了相对于窗口客户区的视口矩形范围
FLOAT MinDepth; //表示深度缓冲区的最小值
FLOAT MaxDepth; //表示深度缓冲区的最大值
} D3D11_VIEWPORT;
Direct3D使用的深度缓冲区取值范围是0到1,除非你想要得到一些特殊效果,否则应将MinDepth和MaxDepth分别设为0和1。在填充了D3D11_VIEWPORT结构体之后,我们可以使用ID3D11Device::RSSetViewports方法设置Direct3D的视口。:
/** ID3D11DeviceContext::RSSetViewports
* 设置Direct3D的视口
* @param NumViewports 绑定的视图的数量(可以使用超过1的数量用于高级的效果,如双人游戏中的分屏)
* @param pViewports 指向一个viewports的数组
*/
void RSSetViewports(
UINT NumViewports,
const D3D11_VIEWPORT *pViewports
);
关于第一个参数,可以用来实现双人游戏模式中的分屏效果。创建两个视口,各占屏幕的一半,一个居左,另一个居右。然后在左视口中以第一个玩家的视角渲染3D场景,在右视口中以第二个玩家的视角渲染3D场景。你也可以只绘制到屏幕的一个子矩形中,而在其他区域保留诸如按钮、列表框之类的UI控件。
总结:
D3D11的初始化主要有以下几个步骤:
1. 创建设备ID3D11Device和设备上下文ID3D11DeviceContext,交换链Swap Chain
2. 检测多重采样支持的等级:CheckMultisampleQualityLevels
4. 创建渲染目标视图RenderTargetView
5. 创建深度模板视图DepthStencilView
6. 把上述两个视图绑定到渲染管线相应的阶段
7. 设置Viewport