(转)【D3D11游戏编程】学习笔记五:D3D11初始化

(注:【D3D11游戏编程】学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:http://blog.csdn.net/BonChoix,谢谢~)

       初次使用D3D11,先从它的初始化开始。不过在使用D3D之前,需要了解几个重要的概念:

       1. 硬件能力:Hardware Capacity

       熟悉D3D9的会很清楚,在初始化d3d9的一开始需要做的就是检测硬件的能力,以了解该用户机器支持哪些d3d特性,哪些不支持,以在运行期合理的调用API。除非使用d3d自带的“软件渲染引擎”,否则试图使用硬件不支持的特性是会出错的。但是在d3d11中,检测硬件能力这一步不再有必要。这对程序员来说省了一点功夫,但是额外的要求是,该硬件必须支持d3d11的全部特性!因此实际上,d3d11应用程序对硬件的要求更苛刻了。

       2. 数据格式

       D3D应用程序中,无论是纹理图片,还是创建的缓冲区,都有着特定的数据格式。D3D11支持有限的数据格式,以枚举变量形式存在,如下几种:

       DXGI_FORMAT_R32G32B32_FLOAT: 3个32位单精度符点数组成,比如用于代表三维空间坐标,以及24位颜色;

       DXGI_FORMAT_R16G16B16A16_UNORM: 4个16位数组成,每个成员位于[0,1.0f]之间,UNORM意指:unsigned normalized,即无符号,且归一化的;

       DXGI_FORMAT_R32G32_UINT:2个32位数组成,每个成员为无符号整型(unsigned int);

       DXGI_FORMAT_R8G8B8A8_UNORM:4个8位数组成,每个成员为[0,1.f]之间;

       DXGI_FORMAT_R8G8B8A8_SNORM:4个8位数组成,每个成员为[-1.0f, 1.0f]之间,SNORM意指:signed normalized;

       DXGI_FORMAT_R8G8B8A8_SINT:4个8位数组成,每个成员为有符号整型;

       此外还有很多其他类型的数据格式,熟悉各部分的意义后对任何类型可以很快从名字中得知其意思,也很容易写出指定意义的数据格式对应的枚举变量。数据格式在程序中使用相当频繁,因此很有必要提前熟悉该枚举类型变量的特点。

       3. DXGI

       DXGI,即DirectX Graphics Infrastructure,是从d3d10开始出现的,封装了一些d3d底层、基础的机制,包括数据格式定义、交换链(SwapChain),枚举设备类型及检测设备信息等。由于这些机制适合于任何一代的D3D接口,而跟特定的d3d特性无关,更新远不及其他d3d特性频繁,因此微软把这些从direct3d中单独分离了出来。这部分对应的枚举变量及函数等都以DXGI开头。

       4. 交换链:SwapChain

       交换链不是d3d11特有的性质,而是任何2D、3D计算机动画最基本的机制之一。为了实现平滑的动画,至少需要两个缓冲区,一个前缓冲区用于显示,一个后缓冲区用于下一帧的绘制,每次绘制完一帧后通过交换前、后缓冲区对应的指针来显示新一帧,并在之前的前缓冲区(当前的后缓冲区)上开始继续绘制下一帧。交换链可以有3个或者更多缓冲欧,但绝大多数情况下,2个已经足够了。在d3d11中交换链对应的接口为IDXGISwapChain。

       5. 深度/模板缓冲区:Depth/Stencil Buffer

       深度缓冲区是与交换链缓冲区大小完全一样的一块显存区域,即每个像素在深度缓冲区中对应相应的位置。在渲染管线的最终的混合阶段(Output Merger Stage),每个片段(Fragment)都有一个深度值z,与深度缓冲区对应位置上的深度相比较,如果该片段z更小,则绘制该片段,并覆盖当前的尝试值,否则抛弃该片段。该缓冲区主要用于实现投影在屏幕上同一位置、远近不同的物体之间相同的遮挡效果。此外,灵活配置尝试缓冲区,可以实现很多种高级特效。

       模板缓冲区与深度缓冲区共享同一块区域,每个深度值处对应一个模板值,模板值主要用于实现一些高级的特效,比如平面反射、阴影等。利用深度、模板缓冲区的不同的configuration实现各种特效在后面会有详细介绍的。

       6. 视图(View)

       视图是D3D11中的最重要的概念之一。以纹理为例,一个纹理可以被绑定到3D渲染管线的不同阶段以实现不同的用途。比如常见的纹理图片(d3d11中叫Shader Resource),另一方面也可用于渲染对象(Render Target),充当交换链中的缓冲区,用于绘制场景。同一个纹理可以同时被绑定到多个管线阶段(Dynamic Cube Mapping就是一个例子,后面会专门介绍),但在创建该纹理资源时需要指定其绑定的类型(Bind Flags),比如上述例子,绑定类型要指定为:D3D11_BIND_RENDER_TARGET | D3D11_SHADER_RESOURCE。

       实际上,在d3d11中,绑定到管线某阶段的并不是纹理资源本身,而是对应的“视图”。对于使用该纹理的每一种方式,我们分别创建相应的视图,然后把视图绑定到管线特定的阶段。D3D11中针对每种视图相应的接口为:ID3D11ShaderResourceView和ID3D11RenderTargetView、ID3D11DepthStencilView。使用视图的具体细节在后面的过程中会慢慢体会到。

       7. 多重采样抗锯齿:Multisampling Atialiasing

       针对光栅化显示器抗锯齿的方法有多种,在d3d中采用的多重采样方法。即在每个像素点内部,设置多个采样点,绘制多边形边缘时,针对每个采样点判断是否被多边形覆盖,最终的颜色值从采样点中取均值,以对多边形的边缘进行“模糊化",从而减轻锯齿效果。如下图所示,这是一个4重采样的例子,该像素最终的颜色值是多边形本身颜色值的3/4:

支持d3d11的硬件全部支持4重采样,因此我们在后面的程序中将普遍使用4个采样点。在d3d11中通过结构DXGI_SAMPLE_DESC来设置多重采样,其定义如下:

[cpp] view plain copy
  1. typedef struct DXGI_SAMPLE_DESC {  
  2.   UINT Count;  
  3.   UINT Quality;  
  4. } DXGI_SAMPLE_DESC, *LPDXGI_SAMPLE_DESC;  

Count为我们设置的采样的个数,Quality为机器支持的不同的等级,初始化过程中我们会对Quality进行检测。

注意Multisampling区别于super sampling的关键点:不同采样点是分别计算颜色值(super sampling)还是共享同一颜色值(Multisampling)。详细情况有很多参考书介绍。

       8. 特征等级:Feature Level

       特征等级定义了一系列支持不同d3d功能的相应的等级,用意即如果一个用户的硬件不支持某一特征等级,程序可以选择较低的等级,以确保正确地运行。d3d11中定义了如下几个等级以代表不同的d3d版本:

[cpp] view plain copy
  1. typedef enum D3D_FEATURE_LEVEL {  
  2.   D3D_FEATURE_LEVEL_9_1    = 0x9100,  
  3.   D3D_FEATURE_LEVEL_9_2    = 0x9200,  
  4.   D3D_FEATURE_LEVEL_9_3    = 0x9300,  
  5.   D3D_FEATURE_LEVEL_10_0   = 0xa000,  
  6.   D3D_FEATURE_LEVEL_10_1   = 0xa100,  
  7.   D3D_FEATURE_LEVEL_11_0   = 0xb000   
  8. } D3D_FEATURE_LEVEL;  

在初始化过程中,我们可以提供一组不同的特征等级,程序会从第一个开始逐个检测,碰到第一个合适的来创建设备。因此我们在数组中从高到低放置特征等级提供给初始化程序。

      

       OK,重要的概念就是这些,现在开始初始化D3D~

       D3D11的初始化主要有以下几个步骤:

       1. 创建设备ID3D11Device和设备上下文ID3D11DeviceContext;

       2. 检测多重采样支持的等级:CheckMultisampleQualityLevels

       3. 创建交换链

       4. 创建RenderTargetView

       5. 创建DepthStencilView

       6. 把上述两个视图绑定到渲染管线相应的阶段

       7. 设置Viewport

       下面从第一步开始:

       1. 创建设备及上下文:

       函数原型如下:

[cpp] view plain copy
  1. HRESULT  D3D11CreateDevice(  
  2.  __in   IDXGIAdapter *pAdapter,  
  3.  __in   D3D_DRIVER_TYPE DriverType,  
  4.  __in   HMODULE Software,  
  5.  __in   UINT Flags,  
  6.  __in   const D3D_FEATURE_LEVEL *pFeatureLevels,  
  7.  __in   UINT FeatureLevels,  
  8.  __in   UINT SDKVersion,  
  9.  __out  ID3D11Device **ppDevice,  
  10.  __out  D3D_FEATURE_LEVEL *pFeatureLevel,  
  11.  __out  ID3D11DeviceContext **ppImmediateContext  
  12. ;  

       pAdapter来选择相应的图形适配器,设为NULL以选择默认的适配器;

       DriverType设置驱动类型,一般毫无疑问选择硬件加速,即D3D_DRIVER_TYPE_HARDWARE,此时下一个参数就是NULL;

       Flags为可选参数,一般为NULL,可以设为D3D11_CREATE_DEVICE_DEBUG、D3D11_CREATE_DEVICE_SINGLETHREADED,或两者一起,前者让要用于调试时收集信息,后者在确定程序只在单线程下运行时设置为它,可以提高性能;

       pFeatureLevels为我们提供给程序的特征等级的一个数组,下一个参数为数组中元素个数;

       SDKVersion恒定为D3D11_SDK_VERSION;

       ppDevice为设备指针的地址,注意设备是指针类型,这里传递的是指针的地址(二维指针,d3d程序中所有的接口都声明为指针类型!);

       pFeatureLevel为最后程序选中的特征等级,我们定义相应的变量,传递它的地址进来;

       ppImmediateContext为设备上下文指针的地址,要求同设备指针。

       创建设备代码如下:

[cpp] view plain copy
  1. ID3D11Device        *d3dDevice(NULL);  
  2. ID3D11DeviceContext *deviceContext(NULL);  
  3. D3D_FEATURE_LEVEL featureLevels[6] = {  
  4.     D3D_FEATURE_LEVEL_11_0,  
  5.     D3D_FEATURE_LEVEL_10_1,  
  6.     D3D_FEATURE_LEVEL_10_0,  
  7.     D3D_FEATURE_LEVEL_9_3,  
  8.     D3D_FEATURE_LEVEL_9_2,  
  9.     D3D_FEATURE_LEVEL_9_1};  
  10. D3D_FEATURE_LEVEL   curLevel;  
  11. D3D11CreateDevice(  
  12.     0,                  //默认适配器  
  13.     D3D_DEVICE_TYPE_HARDWARE,   //硬件加速设备类型  
  14.     0,   
  15.     0,   
  16.     featureLevels, 6,  
  17.     D3D11_SDK_VERSION,  
  18.     &d3dDevice,  
  19.     &curLevel,  
  20.     &deviceContext);  

      创建完即可检测多重采样(我们只用4重采样)的等级:

[cpp] view plain copy
  1. m_d3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM,4,&x4MsaaQuality);  

        2. 创建交换链

       交换链性质通过如下结构指定:

[cpp] view plain copy
  1. typedef struct DXGI_SWAP_CHAIN_DESC {  
  2.   DXGI_MODE_DESC   BufferDesc;  
  3.   DXGI_SAMPLE_DESC SampleDesc;  
  4.   DXGI_USAGE       BufferUsage;  
  5.   UINT             BufferCount;  
  6.   HWND             OutputWindow;  
  7.   BOOL             Windowed;  
  8.   DXGI_SWAP_EFFECT SwapEffect;  
  9.   UINT             Flags;  
  10. } DXGI_SWAP_CHAIN_DESC;  

       BufferDesc指定后缓冲区有关特性;

       SampleDesc指定多重采样,前面说过;

       BufferUsage,对于交换链,为DXGI_USAGE_RENDER_TARGET_OUTPUT;

       BufferCount:我们只创建一个后缓冲区(双缓冲),因此为1;

       OutputWindow:指定窗口句柄,Win32程序初始化完创建的主窗口;

       Windowed:是否全屏;

       DXGI_SWAP_EFFECT:通常为DXGI_SWAP_EFFECT_DISCARD;

       Flags:可选,我们设为0;

DXGI_MODE_DESC结构定义如下:

[cpp] view plain copy
  1. typedef struct DXGI_MODE_DESC {  
  2.   UINT                     Width;  
  3.   UINT                     Height;  
  4.   DXGI_RATIONAL            RefreshRate;  
  5.   DXGI_FORMAT              Format;  
  6.   DXGI_MODE_SCANLINE_ORDER ScanlineOrdering;  
  7.   DXGI_MODE_SCALING        Scaling;  
  8. } DXGI_MODE_DESC, *LPDXGI_MODE_DESC;  

       Width、Height为缓冲区大小,一般设为主窗口大小;

       Format为缓冲区类型,一般作为渲染对象缓冲区类型为DXGI_FORMAT_R8G8B8A8_UNORM;

       其他参数一般为固定的,参见代码。

       此外,创建交换链需要获得接口IDXGIFactory,过程是固定的,创建交换链代码如下:

[cpp] view plain copy
  1. DXGI_SWAP_CHAIN_DESC scDesc = {0};      //填充结构,设置交换链相当属性  
  2. scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;      //缓冲区数据格式  
  3. scDesc.BufferDesc.Width = 640;          //缓冲区大小  
  4. scDesc.BufferDesc.Height = 480;  
  5. scDesc.BufferDesc.RefreshRate.Numerator = 60;           //刷新率,一般这样设定即可  
  6. scDesc.BufferDesc.RefreshRate.Denominator = 1;  
  7. scDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;          //固定参数  
  8. scDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;  //固定参数  
  9. scDesc.BufferCount = 1;                 //缓冲区个数  
  10. scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;       //Usage为Render Target Output  
  11. scDesc.Flags = 0;  
  12. scDesc.OutputWindow = m_hWnd;           //主窗口句柄  
  13. scDesc.SampleDesc.Count = 4;            //4个采样点  
  14. scDesc.SampleDesc.Quality = x4MsaaQuality-1;    //4重采样支持等级  
  15. scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;   //常用参数  
  16. scDesc.Windowed = true;             //窗口模式  
  17.   
  18. //通过如下三步获得接口IDXGIFactory,来创建交换链  
  19. IDXGIDevice *pDxgiDevice(NULL);  
  20. m_d3dDevice->QueryInterface(__uuidof(IDXGIDevice),reinterpret_cast<void**>(&pDxgiDevice));  
  21. IDXGIAdapter *pDxgiAdapter(NULL);  
  22. pDxgiDevice->GetParent(__uuidof(IDXGIAdapter),reinterpret_cast<void**>(&pDxgiAdapter));  
  23. IDXGIFactory *pDxgiFactory(NULL);  
  24. pDxgiAdapter->GetParent(__uuidof(IDXGIFactory),reinterpret_cast<void**>(&pDxgiFactory));  
  25. pDxgiFactory->CreateSwapChain(d3dDevice,&scDesc,&swapChain);  
  26.   
  27. //释放接口  
  28. SafeRelease(pDxgiFactory);  
  29. SafeRelease(pDxgiAdapter);  
  30. SafeRelease(pDxgiDevice);  

       3. 创建RenderTargetView

       D3D11中创建视图需要对应的资源,这里先获取后缓冲区地址,作为参数创建相应的视图:

[cpp] view plain copy
  1. ID3D11Texture2D *backBuffer(NULL);  
  2. //获取后缓冲区地址  
  3. swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&backBuffer));  
  4. //创建视图  
  5. d3dDevice->CreateRenderTargetView(backBuffer,0,&m_renderTargetView);  
  6. //释放后缓冲区引用  
  7. backBuffer->Release();  

       CreateRenderTargetView函数原型如下:

[cpp] view plain copy
  1.     HRESULT CreateRenderTargetView(  
  2.      ID3D11Resource *pResource,             //视图对应资源  
  3.      const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,        //视图描述  
  4.      ID3D11RenderTargetView **ppRTView          //要创建的视图(指针的地址)  
  5. );  

       d3d11中创建视图时需要用到视图的描述(第二个参数),但如果视图对应的资源类型已知,则一般不再需要描述,设为NULL。这里后缓冲区在创建时类型已给出(DXGI_FORMAT_R8G8B8A8_UNORM),因此这里不需要视图描述。资源创建时也可以指定为无类型(TYPELESS),在不同阶段以不同的类型进行解释,这时创建视图时就需要视图描述明确说明类型。
       4. 创建深度、模板缓冲区及对应视图

       创建缓冲区要即创建一个2维纹理,ID3D11Texture2D,创建它需要先给出描述D3D11_TEXTURE2D_DESC。定义如下:

[cpp] view plain copy
  1. typedef struct D3D11_TEXTURE2D_DESC {  
  2.   UINT             Width;  
  3.   UINT             Height;  
  4.   UINT             MipLevels;       //这里不需要mipmap,设为1  
  5.   UINT             ArraySize;       //纹理数组才用,这里为1  
  6.   DXGI_FORMAT      Format;      //数据格式,一般为DXGI_FORMAT_D24_UNORM_S8_UINT,24位用于深度,8位用于模板  
  7.   DXGI_SAMPLE_DESC SampleDesc;      //多重采样,如前,前后务必保持一致!  
  8.   D3D11_USAGE      Usage;       //Usage,对于只让GPU读、写,应为D3D11_USAGE_DEFAULT  
  9.   UINT             BindFlags;       //绑定类型,为D3D11_BIND_DEPTH_STENCIL  
  10.   UINT             CPUAccessFlags;  //CPU不可访问,设为0  
  11.   UINT             MiscFlags;       //设为0  
  12. } D3D11_TEXTURE2D_DESC;  

       创建视图类型DepthStencilView,代码如下:

[cpp] view plain copy
  1. D3D11_TEXTURE2D_DESC dsDesc;  
  2. dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;  
  3. dsDesc.Width = 640;  
  4. dsDesc.Height = 480;  
  5. dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;  
  6. dsDesc.MipLevels = 1;  
  7. dsDesc.ArraySize = 1;  
  8. dsDesc.CPUAccessFlags = 0;  
  9. dsDesc.SampleDesc.Count = 4;  
  10. dsDesc.SampleDesc.Quality = x4MsaaQuality-1;  
  11. dsDesc.MiscFlags = 0;  
  12. dsDesc.Usage = D3D11_USAGE_DEFAULT;  
  13. d3dDevice->CreateTexture2D(&dsDesc,0,&depthStencilBuffer);  
  14. d3dDevice->CreateDepthStencilView(depthStencilBuffer,0,&depthStencilView);  

创建完两个视图后当然就要绑定到渲染管线上去啦:

[cpp] view plain copy
  1. deviceContext->OMSetRenderTargets(1,&renderTargetView,depthStencilView);  

该函数原型如下:

[html] view plain copy
  1. void OMSetRenderTargets(  
  2.   [in]  UINT NumViews,                          //RenderTarget个数,我们一般只用一个  
  3.   [in]  ID3D10RenderTargetView *const *ppRenderTargetViews,     //RenderTarget数组,只一个,所以直接传其地址即可  
  4.   [in]  ID3D10DepthStencilView *pDepthStencilView           //DepthStencil view  
  5. );  

       最后设置viewport,很简单,D3D11_VIEWPORT定义如下:

[cpp] view plain copy
  1. typedef struct D3D11_VIEWPORT {  
  2.   FLOAT TopLeftX;       //视口左上角在屏幕上x坐标,一般视口占满屏幕的,所以为0  
  3.   FLOAT TopLeftY;       //y坐标,同上  
  4.   FLOAT Width;          //视口宽度,一般与后缓冲区一致,以保持图像不变形  
  5.   FLOAT Height;         //高度,同上  
  6.   FLOAT MinDepth;       //最小深度值:0.0f  
  7.   FLOAT MaxDepth;       //最大深度值:1.0f  
  8. } D3D11_VIEWPORT;  

       设置视口代码:

[cpp] view plain copy
  1. D3D11_VIEWPORT viewPort;  
  2. viewPort.Width = static_cast<FLOAT>(clientWidth);  
  3. viewPort.Height = static_cast<FLOAT>(clientHeight);  
  4. viewPort.MaxDepth = 1.f;  
  5. viewPort.MinDepth = 0.f;  
  6. viewPort.TopLeftX = 0.f;  
  7. viewPort.TopLeftY = 0.f;  
  8. deviceContext->RSSetViewports(1,&viewPort);  

       Voila,初始化过程全部完成!

       下面就可以进入主循环渲染函数绘制场景了,这次我们暂时什么也不画,只是简单地把屏幕清空为随意指定的颜色:

[cpp] view plain copy
  1. XMVECTORF32 color = {0.f, 1.f, 0.f, 1.6f};  
  2. g_deviceContext->ClearRenderTargetView(g_renderTargetView,reinterpret_cast<float*>(&color));  
  3. g_deviceContext->ClearDepthStencilView(g_depthStencilView,D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,1.f,0);  
  4.   
  5. g_swapChain->Present(0,0);  

       每帧开始渲染时,先清空RenderTargetView和深度缓冲区,然后就是全屏渲染内容了。绘制完整个场景后,最后调用让交换链Present来交换前后缓冲区,以显示新的一帧。清空RenderTarget很简单,把相应视图传进来,并给定任意背景颜色(4个float的数组,这里适合用XMVECTORF32)即可。对于清空深度、模板缓冲区,第一个参数一目了然,第二个用来指定清空的内容参数,一般我们要同时清空深度和模板,因此用两个枚举变量求并,最后两个参数分别指定清空深度和模板的默认值,深度清空为1.0f(最大),模板一般清空为0,就这样。
      

       整个过程就结束啦,配合上次中的Win32程序,现在已经是一个完整的、可工作的d3d程序了,显示的即为一个空白的指定颜色的窗口。初始化程序就是这样,一个最简单的d3d应用程序。以后慢慢开始渲染更加炫丽的场景~

       这次完整的初始化程序源代码如下:

       D3D11完整初始化代码

原文地址:https://www.cnblogs.com/wodehao0808/p/6603841.html