在先前的解决方案中新建一个新的Win32项目FirstD3D11Demo。在写代码之前,我们必须先添加dx11所需要的库。为了链接dx库,右键项目选择属性->vc++目录,在包含目录中添加你所安装的SDK根目录\Include,在库目录中添加 根目录\lib\x86(或x64),在链接器->输入的附加依赖项中添加d3d11.lib、d3dx11.lib、dxerr.lib。
第一次使用d3d,首先应该从初始化开始。
初始化d3d11的步骤主要有以下几个:
1、定义我们要检查的设备类型和特征级别
2、创建d3d设备,渲染环境和交换链
3、创建渲染对象
4、设置视口观察区(ViewPort)
下面将对一些概念和用到的d3d对象和函数作具体说明。
数据格式
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位数组成,每个成员为有符号整型;
特征级别(Feature Level)
特征级别定义了一系列支持不同d3d功能的相应等级,如果一个用户的硬件不支持某一特征等级,程序可以选择较低的等级来运行。
下面是d3d定义的几个不同级别代表不同的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_11_1 = 0xb100, D3D_FEATURE_LEVEL_12_0 = 0xc000, D3D_FEATURE_LEVEL_12_1 = 0xc100 } D3D_FEATURE_LEVEL;
在初始化过程中,我们可以提供一组不同的特征等级,程序会从第一个开始逐个检测,碰到第一个合适的来创建设备。因此我们在数组中从高到低放置特征等级提供给初始化程序。
交换链(SwapChain)
为了实现平滑的动画,至少需要两个缓冲区,一个前缓冲区用于显示,一个后缓冲区用于下一帧的绘制,每次绘制完一帧后通过交换前、后缓冲区对应的指针来显示新一帧,并在之前的前缓冲区(当前的后缓冲区)上开始继续绘制下一帧。交换链可以有3个或者更多缓冲区,但一般情况下两个够用了。通常在游戏中,我们有两种颜色缓存,一个主缓存,一个辅助缓存,这就是所谓的前向和后向缓存。主缓存是显示在屏幕上的,辅助缓存则是用于下一帧的绘制。在d3d11中交换链对应的接口为IDXGISwapChain。
深度/模板缓冲区:Depth/Stencil Buffer
深度缓冲区是与交换链缓冲区大小完全一样的一块显存区域,即每个像素在深度缓冲区中对应相应的位置。在渲染管线的最终的混合阶段(Output Merger Stage),每个片(Fragment)都有一个深度值z,与深度缓冲区对应位置上的深度相比较,如果该片段z更小,则绘制该片段,并覆盖当前的尝试值,否则抛弃该片段。该缓冲区主要用于实现投影在屏幕上同一位置、远近不同的物体之间相同的遮挡效果。此外,灵活配置尝试缓冲区,可以实现很多种高级特效。
多重采样抗锯齿: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;
D3D11_CREATE_DEVICE_FLAG 枚举类型
typedef enum D3D11_CREATE_DEVICE_FLAG { D3D11_CREATE_DEVICE_SINGLETHREADED = 0x1, D3D11_CREATE_DEVICE_DEBUG = 0x2, D3D11_CREATE_DEVICE_SWITCH_TO_REF = 0x4, D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS = 0x8, D3D11_CREATE_DEVICE_BGRA_SUPPORT = 0x20, D3D11_CREATE_DEVICE_DEBUGGABLE = 0x40, D3D11_CREATE_DEVICE_PREVENT_ALTERING_LAYER_SETTINGS_FROM_REGISTRY = 0x80, D3D11_CREATE_DEVICE_DISABLE_GPU_TIMEOUT = 0x100, D3D11_CREATE_DEVICE_VIDEO_SUPPORT = 0x800 } D3D11_CREATE_DEVICE_FLAG;
D3D11_CREATE_DEVICE_SINGLETHREADED
如果使用该常量,你的应用程序将只可以在一个线程中的调用 Dierct3D 11接口。在默认情况下ID3D11Device
对象是一个安全线程。使用这个标志,你可以增强性能。然而,如果你使用这个标志并且你的应用程序使用
多线程调用Dierct3D 11接口,可能导致不可预期的结果。
D3D11_CREATE_DEVICE_DEBUG
创建一个设备支持调用层。
D3D11_CREATE_DEVICE_SWITCH_TO_REF
注意 这个标志不支持Direct3D 11.
D3D11_CREATE_DEVICE_PREVENT_INTERNAL_THREADING_OPTIMIZATIONS
阻止被多线程创建。当使用WARP标志时,WARP和所有光栅不能够被线程调用。这个标志不建议使用。
D3D11_CREATE_DEVICE_BGRA_SUPPORT
Dierct2D需要和Direct3D资源交互。
交换链具体定义
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:可选
其中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;
其他参数一般为固定的。
创建交换链的具体过程见后面附的代码
D3D11CreateDeviceAndSwapChain函数原型:
HRESULT D3D11CreateDeviceAndSwapChain( _In_opt_ IDXGIAdapter *pAdapter, D3D_DRIVER_TYPE DriverType, HMODULE Software, UINT Flags, _In_opt_ 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 );
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为设备上下文指针的地址,要求同设备指针。
CreateRenderTargetView函数原型
HRESULT CreateRenderTargetView( [in] ID3D11Resource *pResource, [in, optional] const D3D11_RENDER_TARGET_VIEW_DESC *pDesc, [out, optional] ID3D11RenderTargetView **ppRTView );
pResource为视图对应资源
pDesc为视图描述
ppRTView要创建的视图,是一个指针的地址
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;
下面给出整个工程的代码:
1 #include <windows.h> 2 #include <d3d11.h> 3 #include <DxErr.h> 4 #include <D3DX11.h> 5 6 HINSTANCE g_hInstance = NULL; 7 HWND g_hWnd = NULL; 8 LPCWSTR g_name = L"FirstD3D11Demo"; 9 D3D_DRIVER_TYPE g_driverType = D3D_DRIVER_TYPE_NULL; //驱动类型 10 D3D_FEATURE_LEVEL g_featureLevel = D3D_FEATURE_LEVEL_11_0; //特征等级 11 ID3D11Device *g_pd3dDevice = NULL; //设备 12 ID3D11DeviceContext *g_pImmediateContext = NULL; //设备上下文 13 IDXGISwapChain *g_pSwapChain = NULL; //交换链 14 ID3D11RenderTargetView *g_pRenderTargetView = NULL; //要创建的视图 15 16 17 HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow); 18 HRESULT InitDevice(); 19 void CleanupDevice(); 20 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 21 void Render(); 22 23 int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShowCmd) 24 { 25 if (FAILED(InitWindow(hInstance, nShowCmd))) 26 return 0; 27 if (FAILED(InitDevice())) 28 { 29 CleanupDevice(); 30 return 0; 31 } 32 MSG msg; 33 ZeroMemory(&msg, sizeof(MSG)); 34 while (msg.message != WM_QUIT) 35 { 36 if (PeekMessage(&msg, NULL, 0, 0,PM_REMOVE)) 37 { 38 TranslateMessage(&msg); 39 DispatchMessage(&msg); 40 } 41 else//渲染 42 { 43 Render(); 44 } 45 } 46 CleanupDevice(); 47 return static_cast<int>(msg.wParam); 48 } 49 50 HRESULT InitWindow(HINSTANCE hInstance, int nCmdShow) 51 { 52 WNDCLASSEX wcex; 53 wcex.cbClsExtra = 0; 54 wcex.cbSize = sizeof(WNDCLASSEX); 55 wcex.cbWndExtra = 0; 56 wcex.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); 57 wcex.hCursor = LoadCursor(NULL,IDC_ARROW); 58 wcex.hIcon = LoadIcon(NULL, IDI_WINLOGO); 59 wcex.hIconSm = wcex.hIcon; 60 wcex.hInstance = hInstance; 61 wcex.lpfnWndProc = WndProc; 62 wcex.lpszClassName = g_name; 63 wcex.lpszMenuName = NULL; 64 wcex.style = CS_HREDRAW | CS_VREDRAW; 65 if (!RegisterClassEx(&wcex)) 66 return E_FAIL; 67 68 g_hInstance = hInstance; 69 RECT rc{0,0,640,480}; 70 AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, FALSE); 71 g_hWnd = CreateWindowEx(WS_EX_APPWINDOW, g_name, g_name, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 72 rc.right - rc.left, rc.bottom - rc.top, NULL, NULL, g_hInstance, NULL); 73 if (!g_hWnd) 74 return E_FAIL; 75 76 ShowWindow(g_hWnd, nCmdShow); 77 78 return S_OK; 79 } 80 81 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wPararm, LPARAM lParam) 82 { 83 switch (message) 84 { 85 case WM_DESTROY: 86 PostQuitMessage(0); 87 break; 88 default: 89 return DefWindowProc(hWnd, message, wPararm, lParam); 90 } 91 return 0; 92 } 93 94 //创建设备及交换链 95 HRESULT InitDevice() 96 { 97 HRESULT hResult = S_OK;//返回结果 98 99 RECT rc; 100 GetClientRect(g_hWnd, &rc);//获取窗口客户区大小 101 UINT width = rc.right - rc.left; 102 UINT height = rc.bottom - rc.top; 103 104 UINT createDeviceFlags = 0; 105 #ifdef _DEBUG 106 createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG; 107 #endif 108 109 //驱动类型数组 110 D3D_DRIVER_TYPE driverTypes[] = 111 { 112 D3D_DRIVER_TYPE_HARDWARE, 113 D3D_DRIVER_TYPE_WARP, 114 D3D_DRIVER_TYPE_REFERENCE 115 }; 116 UINT numDriverTypes = ARRAYSIZE(driverTypes); 117 118 //特征级别数组 119 D3D_FEATURE_LEVEL featureLevels[] = 120 { 121 D3D_FEATURE_LEVEL_11_0, 122 D3D_FEATURE_LEVEL_10_1, 123 D3D_FEATURE_LEVEL_10_0 124 }; 125 UINT numFeatureLevels = ARRAYSIZE(featureLevels); 126 127 //交换链 128 DXGI_SWAP_CHAIN_DESC sd; 129 ZeroMemory(&sd, sizeof(DXGI_SWAP_CHAIN_DESC));//填充 130 sd.BufferCount = 1; //我们只创建一个后缓冲(双缓冲)因此为1 131 sd.BufferDesc.Width = width; 132 sd.BufferDesc.Height = height; 133 sd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; 134 sd.BufferDesc.RefreshRate.Numerator = 60; 135 sd.BufferDesc.RefreshRate.Denominator = 1; 136 sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; 137 sd.OutputWindow = g_hWnd; 138 sd.SampleDesc.Count = 1; //1重采样 139 sd.SampleDesc.Quality = 0; //采样等级 140 sd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD; //常用参数 141 sd.Windowed = TRUE; //全屏 142 143 for (UINT driverTypeIndex = 0; driverTypeIndex < numDriverTypes; ++driverTypeIndex) 144 { 145 g_driverType = driverTypes[driverTypeIndex]; 146 hResult = D3D11CreateDeviceAndSwapChain( 147 NULL, //默认图形适配器 148 g_driverType, //驱动类型 149 NULL, //实现软件渲染设备的动态库句柄,如果使用的驱动设备类型是软件设备则不能为NULL 150 createDeviceFlags, //创建标志,0用于游戏发布,一般D3D11_CREATE_DEVICE_DEBUG允许我们创建可供调试的设备,在开发中比较有用 151 featureLevels, //特征等级 152 numFeatureLevels, //特征等级数量 153 D3D11_SDK_VERSION, //sdk版本号 154 &sd, 155 &g_pSwapChain, 156 &g_pd3dDevice, 157 &g_featureLevel, 158 &g_pImmediateContext 159 ); 160 if (SUCCEEDED(hResult)) 161 break; 162 } 163 if (FAILED(hResult)) 164 return hResult; 165 166 //创建渲染目标视图 167 ID3D11Texture2D *pBackBuffer = NULL; 168 //获取后缓冲区地址 169 hResult = g_pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer); 170 if (FAILED(hResult)) 171 return hResult; 172 173 //创建目标视图 174 hResult = g_pd3dDevice->CreateRenderTargetView(pBackBuffer, NULL, &g_pRenderTargetView); 175 //释放后缓冲 176 pBackBuffer->Release(); 177 if (FAILED(hResult)) 178 return hResult; 179 180 //绑定到渲染管线 181 g_pImmediateContext->OMSetRenderTargets(1, &g_pRenderTargetView, NULL); 182 183 //设置viewport 184 D3D11_VIEWPORT vp; 185 vp.Height = (FLOAT)height; 186 vp.Width = (FLOAT)width; 187 vp.MinDepth = 0.0f; 188 vp.MaxDepth = 1.0f; 189 vp.TopLeftX = 0; 190 vp.TopLeftY = 0; 191 g_pImmediateContext->RSSetViewports(1, &vp); 192 193 return S_OK; 194 } 195 196 void Render() 197 { 198 float ClearColor[4] = { 0.5f, 0.1f, 0.2f, 1.0f }; //red,green,blue,alpha 199 g_pImmediateContext->ClearRenderTargetView(g_pRenderTargetView, ClearColor); 200 g_pSwapChain->Present(0, 0); 201 } 202 203 void CleanupDevice() 204 { 205 if (g_pImmediateContext) 206 g_pImmediateContext->ClearState(); 207 if (g_pSwapChain) 208 g_pSwapChain->Release(); 209 if (g_pRenderTargetView) 210 g_pRenderTargetView->Release(); 211 if (g_pImmediateContext) 212 g_pImmediateContext->Release(); 213 if (g_pd3dDevice) 214 g_pd3dDevice->Release(); 215 }