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

  • Post author:
  • Post category:其他



(注:


【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来设置多重采样,其定义如下:

typedef struct DXGI_SAMPLE_DESC {
  UINT Count;
  UINT Quality;
} DXGI_SAMPLE_DESC, *LPDXGI_SAMPLE_DESC;

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

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


8. 特征等级:Feature Level



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

typedef enum D3D_FEATURE_LEVEL {
  D3D_FEATURE_LEVEL_9_1    = 0x9100,
  D3D_FEATURE_LEVEL_9_2    = 0x9200,
  D3D_FEATURE_LEVEL_9_3    = 0x9300,
  D3D_FEATURE_LEVEL_10_0   = 0xa000,
  D3D_FEATURE_LEVEL_10_1   = 0xa100,
  D3D_FEATURE_LEVEL_11_0   = 0xb000 
} D3D_FEATURE_LEVEL;

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

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

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


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


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


3. 创建交换链


4. 创建RenderTargetView


5. 创建DepthStencilView


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


7. 设置Viewport



下面从第一步开始:

1. 创建设备及上下文:

函数原型如下:

 HRESULT  D3D11CreateDevice(
  __in   IDXGIAdapter *pAdapter,
  __in   D3D_DRIVER_TYPE DriverType,
  __in   HMODULE Software,
  __in   UINT Flags,
  __in   const D3D_FEATURE_LEVEL *pFeatureLevels,
  __in   UINT FeatureLevels,
  __in   UINT SDKVersion,
  __out  ID3D11Device **ppDevice,
  __out  D3D_FEATURE_LEVEL *pFeatureLevel,
  __out  ID3D11DeviceContext **ppImmediateContext
);

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为设备上下文指针的地址,要求同设备指针。

创建设备代码如下:

ID3D11Device		*d3dDevice(NULL);
ID3D11DeviceContext	*deviceContext(NULL);
D3D_FEATURE_LEVEL featureLevels[6] = {
	D3D_FEATURE_LEVEL_11_0,
	D3D_FEATURE_LEVEL_10_1,
	D3D_FEATURE_LEVEL_10_0,
	D3D_FEATURE_LEVEL_9_3,
	D3D_FEATURE_LEVEL_9_2,
	D3D_FEATURE_LEVEL_9_1};
D3D_FEATURE_LEVEL	curLevel;
D3D11CreateDevice(
	0,  				//默认适配器
	D3D_DEVICE_TYPE_HARDWARE,	//硬件加速设备类型
	0, 
	0, 
	featureLevels, 6,
	D3D11_SDK_VERSION,
	&d3dDevice,
	&curLevel,
	&deviceContext);

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

m_d3dDevice->CheckMultisampleQualityLevels(DXGI_FORMAT_R8G8B8A8_UNORM,4,&x4MsaaQuality);

2. 创建交换链

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

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指定后缓冲区有关特性;

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

BufferUsage,对于交换链,为DXGI_USAGE_RENDER_TARGET_OUTPUT;

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

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

Windowed:是否全屏;

DXGI_SWAP_EFFECT:通常为DXGI_SWAP_EFFECT_DISCARD;

Flags:可选,我们设为0;

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, *LPDXGI_MODE_DESC;

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

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

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

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

	DXGI_SWAP_CHAIN_DESC scDesc = {0};		//填充结构,设置交换链相当属性
	scDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;		//缓冲区数据格式
	scDesc.BufferDesc.Width = 640;			//缓冲区大小
	scDesc.BufferDesc.Height = 480;
	scDesc.BufferDesc.RefreshRate.Numerator = 60;			//刷新率,一般这样设定即可
	scDesc.BufferDesc.RefreshRate.Denominator = 1;
	scDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;			//固定参数
	scDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;	//固定参数
	scDesc.BufferCount = 1;					//缓冲区个数
	scDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;		//Usage为Render Target Output
	scDesc.Flags = 0;
	scDesc.OutputWindow = m_hWnd;			//主窗口句柄
	scDesc.SampleDesc.Count = 4;			//4个采样点
	scDesc.SampleDesc.Quality = x4MsaaQuality-1;	//4重采样支持等级
	scDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;	//常用参数
	scDesc.Windowed = true;				//窗口模式

	//通过如下三步获得接口IDXGIFactory,来创建交换链
	IDXGIDevice *pDxgiDevice(NULL);
	m_d3dDevice->QueryInterface(__uuidof(IDXGIDevice),reinterpret_cast<void**>(&pDxgiDevice));
	IDXGIAdapter *pDxgiAdapter(NULL);
	pDxgiDevice->GetParent(__uuidof(IDXGIAdapter),reinterpret_cast<void**>(&pDxgiAdapter));
	IDXGIFactory *pDxgiFactory(NULL);
	pDxgiAdapter->GetParent(__uuidof(IDXGIFactory),reinterpret_cast<void**>(&pDxgiFactory));
	pDxgiFactory->CreateSwapChain(d3dDevice,&scDesc,&swapChain);
	
	//释放接口
	SafeRelease(pDxgiFactory);
	SafeRelease(pDxgiAdapter);
	SafeRelease(pDxgiDevice);

3. 创建RenderTargetView

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

	ID3D11Texture2D *backBuffer(NULL);
	//获取后缓冲区地址
	swapChain->GetBuffer(0,__uuidof(ID3D11Texture2D),reinterpret_cast<void**>(&backBuffer));
	//创建视图
	d3dDevice->CreateRenderTargetView(backBuffer,0,&m_renderTargetView);
	//释放后缓冲区引用
	backBuffer->Release();

CreateRenderTargetView函数原型如下:

	HRESULT CreateRenderTargetView(
  	 ID3D11Resource *pResource,				//视图对应资源
  	 const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,		//视图描述
  	 ID3D11RenderTargetView **ppRTView			//要创建的视图(指针的地址)
);

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

4. 创建深度、模板缓冲区及对应视图

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

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

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

	D3D11_TEXTURE2D_DESC dsDesc;
	dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
	dsDesc.Width = 640;
	dsDesc.Height = 480;
	dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
	dsDesc.MipLevels = 1;
	dsDesc.ArraySize = 1;
	dsDesc.CPUAccessFlags = 0;
	dsDesc.SampleDesc.Count = 4;
	dsDesc.SampleDesc.Quality = x4MsaaQuality-1;
	dsDesc.MiscFlags = 0;
	dsDesc.Usage = D3D11_USAGE_DEFAULT;
	d3dDevice->CreateTexture2D(&dsDesc,0,&depthStencilBuffer);
	d3dDevice->CreateDepthStencilView(depthStencilBuffer,0,&depthStencilView);

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

	deviceContext->OMSetRenderTargets(1,&renderTargetView,depthStencilView);

该函数原型如下:

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

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

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

设置视口代码:

	D3D11_VIEWPORT viewPort;
	viewPort.Width = static_cast<FLOAT>(clientWidth);
	viewPort.Height = static_cast<FLOAT>(clientHeight);
	viewPort.MaxDepth = 1.f;
	viewPort.MinDepth = 0.f;
	viewPort.TopLeftX = 0.f;
	viewPort.TopLeftY = 0.f;
	deviceContext->RSSetViewports(1,&viewPort);

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

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

	XMVECTORF32 color = {0.f, 1.f, 0.f, 1.6f};
	g_deviceContext->ClearRenderTargetView(g_renderTargetView,reinterpret_cast<float*>(&color));
	g_deviceContext->ClearDepthStencilView(g_depthStencilView,D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,1.f,0);

	g_swapChain->Present(0,0);

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

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

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


D3D11完整初始化代码