Lesson 7:初始化Direct3D
课程总览
现在你已经学习过Direct3D运作的概念,让我们通过构建一个简单的Direct3D程序深入实践。在这个程序中我们仅初始化Direc3D再关闭它,还不如“Hello World”复杂,但这是个好的开始。
COM
COM是啥?
COM代表组建对象模型。COM是一种创建高级对象的方法。就像乐高玩具一样。
正如你所知道的那样,乐高可以搭建在一起创造出更多高级的形态。它们之间相互兼容,你所做的就是把它们粘在一起以使它们有效工作。如果你要改变哪个部分只需要拔掉这部分并把新的装上去。
COM对象实际上是C++类或者你可以从中调用函数并达到某种目标的类组。没有类需要另外的类去管理,他们不需要真的一起工作以完成事情,但是你可以把它们插入或者拔出程序。
例如你有一个分布广泛的游戏并且你想要对它升级。你不需要追踪并发送一份新的拷贝给每个顾客,而是通知“更新!就在这里!”他们下载更新的COM对象,新的对象插件随即在你的程序里,没有那么麻烦。
我不想涉及太多COM的细节,因为它远比我们需要用到的要更复杂。所以你有较为轻松的时间。
为什么是COM?实际上DirectX是一系列COM对象,其中之一就是Direct3D。
Direct3D是一个含有其他COM对象的COM对象。最终,它包含了你需要使用软件、硬件等运作2D和3D图像的每一件事情。
因为Direct3D已经存储在类里面,所以不要看到Direct3D函数被如下调用而感到惊讶:
device->CreateRenderTargetView()
device->Release()
我们用间接成员访问操作符来访问来自Direct3D类的CreateRenderTargetView()函数和Release()函数。
尽管COM的工作是隐藏复杂性,你仍需要知道关于它的4件事情。
1.一个COM对象是受接口控制的一个类或者一套类。接口是一套函数,用于控制COM对象。在上面的例子中,“device”是一个COM对象,函数可以操控它。
2.每一类COM对象有一个独一无二的ID。例如Direct3D对象有它自己的ID,DirectSound对象有它自己的ID。有时候你需要在代码里使用这个ID。
3.当一个COM对象使用完毕之后,你必须调用Release()函数。这将告诉对象释放它的内存并关闭它的线程。
4.COM对象很容易识别,因为他们总是以‘I’开头,例如‘ID3D10Device’。
不要担心这四点的细节。我们立刻将会看到如何应用他们。
现在让我们来一点实际代码:
Direct3D 头文件
在我们搞实际的Direct3D代码之前,让我们讨论一下头文件,库文件和其它有趣的东西。在我们的示例程序中,我们会将这些事情置顶,以给我们对Direct3D的全局访问权限。
让我们看一下他们都是些什么。
// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")
// global declarations
IDXGISwapChain *swapchain; // the pointer to the swap chain interface
ID3D11Device *dev; // the pointer to our Direct3D device interface
ID3D11DeviceContext *devcon; // the pointer to our Direct3D device context
// function prototypes
void InitD3D(HWND hWnd); // sets up and initializes Direct3D
void CleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
#include <d3d11.h>
#include <d3dx11.h>
这些包含了Direct3D的头文件。这些文件包含有Direct3D 11库中实际方法的各种声明。
d3d11.h文件包含Direct3D的核心部分。d3dx11.h文件包含对Direct3D的扩展,它对图形库来说不是必须的,但是当写游戏或者其它图形程序的时候它会带了方便。
注意:并非所有的编译器都会自动定位这些文件。有时候你需要配置你的项目以寻找到DirectX SDK文件夹。如果你使用Visual Studio,我给你写好了一个关于如何做的快速迷你课程。
#include <d3dx10.h>
Direct3D 11实际上是Direct3D 10的扩展。鉴于此它借用了很多来自Direct3D 10的宏命令,函数和类。这个头文件允许我们在我们的程序中使用它们。
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")
这个包含了Direct3D 11的库文件。‘#pragma comment’指令把某条信息放到你的工程对象文件中。第一个参数lib指定我们想要给项目添加库文件。然后我们指定“d3d11.lib”,"d3dx.lib",“d3dx10.lib”作为实际内容。
ID3D11Device *dev;
这个变量是一个指向设备的指针。在Direct3D中,device是一个代表显示适配器的代理对象。这行代码意味着我们将要创建一个ID3D11Device类型的COM对象。当COM产生这个对象的时候我们将忽略它而使用这个指针间接访问它。
ID3D11DeviceContext *devcon;
正如上节介绍的,交换链是一系列轮流被渲染的缓冲。这个变量就是指向这么个链的指针。
注意这个对象并不属于Direct3D,但是属于DXGI(Direct3D的基础)。
启动Direct3D
实际代码的第一步就是创建上面的3个COM对象并初始化他们。这里用单个函数和一个包含图形设备信息的结构就做到了。让我们看一下这个函数,因为都是全新的东东所以没有加粗体。
// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
// create a struct to hold information about the swap chain
DXGI_SWAP_CHAIN_DESC scd;
// clear out the struct for use
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
// fill the swap chain description struct
scd.BufferCount = 1; // one back buffer
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
scd.OutputWindow = hWnd; // the window to be used
scd.SampleDesc.Count = 4; // how many multisamples
scd.Windowed = TRUE; // windowed/full-screen mode
// create a device, device context and swap chain using the information in the scd struct
D3D11CreateDeviceAndSwapChain(NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
NULL,
NULL,
NULL,
D3D11_SDK_VERSION,
&scd,
&swapchain,
&dev,
NULL,
&devcon);
}
如果代码中的注释对你来说已经足够了,那太好了,否则就参考以下描述吧。
DXGI_SWAP_CHAIN_DESC scd;
在入门和高级游戏编程中有某些因子需要在开始的时候传入某些信息给Direct3D。
DXGI_SWAP_CHAIN_DESC是一个成员包含交换链描述的结构 。我们将仔细检查我们需要的并介绍新的成员(当他们出现在教程中的时候)。
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
我们使用ZeroMemory()快速初始化全部的scd结构为NULL。这样我们就不用检查结构的每个成员和分别初始化它们。
scd.BufferCount = 1;
这个成员包含我们交换链中的后台缓冲。我们只用一个后台缓冲/一个前台缓冲,所以我们设置它的值为1.我们可以用更多,但是1已经足够我们将做的任何事了。
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
下个成员将用来设置颜色格式。对于前台和后台缓冲,每个像素都根据颜色来存储。这个值决定了数据以什么格式被存储。
这里我们设置格式为 DXGI_FORMAT_R8G8B8A8_UNORM。这是一个指定格式的编码标志。目前你还不用去急着更改它。
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
这些名称变得越来越长!这个成员告诉我们试图如何使用交换链。下面的表格由俩常用值。
scd.OutputWindow = hWnd;
这个值设置使用Direct3D进行绘制的窗口的句柄。
scd.SampleDesc.Count = 1;
这个成员告诉Direct3D如何执行多重采样抗锯齿(MSAA)渲染。基本上抗锯齿通过轻微地混合每个像素和它周围的像素使形状的边渲染平滑。
在左边你可以看到线产生了一个阶梯效果。右边的图像因为Direct3D混合了像素而稍微平滑。
这个值告诉Direct3D抗锯齿时应该保留多少细节,值越大越好,Direct3D 11显卡可以支持到4但最小值是1.
scd.Windowed = TRUE;
当我们在一个窗口中运行Direct3D时这个值要设为TRUE。否则设置成FALSE全屏模式。
注意:在进入全屏之前你还有其它东西需要更改。改变这个值并不会使你的程序合适全屏。
scd.Flags
尽管在例子代码中还没有标记,但介绍一下它们也是极好的。
D3D11CreateDeviceAndSwapChain()
这是个大块儿头,但是实际上相当简单。在你写的每个游戏中大部分参数都将保持不变。
这个函数做的就是创建设备,设备描述表和交换链COM对象。
一旦我们创建了它们,就可以使用它们执行实际渲染。
让我们看一下函数原型。
HRESULT D3D11CreateDeviceAndSwapChain(
IDXGIAdapter *pAdapter,
D3D_DRIVER_TYPE DriverType,
HMODULE Software,
UINT Flags,
D3D_FEATURE_LEVEL *pFeatureLevels,
UINT FeatureLevels,
UINT SDKVersion,
DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
IDXGISwapChain **ppSwapChain,
ID3D11Device **ppDevice,
D3D_FEATURE_LEVEL *pFeatureLevel,
ID3D11DeviceContext **ppDeviceContext);
IDXGIAdapter *pAdapter,
这个值指定Direct3D应该使用的图形适配器。图形适配器通常指的是GPU和它的显存、数模转换器等等。
这里我们设置值为NULL,DXGI决定使用默认的适配器。
D3D_DRIVER_TYPE DriverType,
这个参数用来决定Direct3D是应该使用硬件还是软件来渲染。你可以用许多标记来决定。它们被列在下表。
HMODULE Software,
它使用标记D3D_DRIVER_TYPE_SOFTWARE来设置软件代码。它很慢,所以我们不会使用它。
UINT Flags,
标记很简单!这里有一些标记值可以改变Direct3D的运行方式。
这些标记可以逻辑或在一起。幸运的是我们不需要这些标记让事情变得方便。
D3D_FEATURE_LEVEL *pFeatureLevels,
每个Direct3D主版本会有一系列显卡特征需求。如果你知道你的硬件满足的版本,你可以更容易地明白硬件的功能(鉴于你的客户会有不同的视频卡)。
这个参数允许你创建一个特性等级列表。这个列表告诉Direct3D你期望在程序中处理的特性。
本课程你需要一个Direct3D 11显卡,所以我们不使用这个参数。设置为NULL,我们无需担心它。
UINT FeatureLevels,
这个参数指定你的列表里有多少特征级,我们设置为NULL。
UINT SDKVersion,
这个参数总是一样的:D3D11_SDK_VERSION.
在不同的SDK版本中,这个值返回不同的数字。你最好不要修改这个值。
DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
这是个指向交换链描述表结构的指针,我们给它‘&scd’。
IDXGISwapChain **ppSwapChain,
这是个指向交换链对象指针的指针。这个函数将为我们创建对象和对象的地址(将存储到这个指针)。简单!
我们做的就是给出指针的位置'&swapchain'。
ID3D11Device **ppDevice,
这是个指向设备对象指针的指针。我们定义它为’dev‘,所以我们设参数值为’&dev‘。
像交换链一样,这个函数将会创建设备并把地址存放到指针‘dev’。
D3D_FEATURE_LEVEL *FeatureLevel,
这个指针指向特征级变量。当函数完成时,变量会以找到的最高特征级标记被填充。这让程序员知道什么硬件对他是可用的。我们设置它为NULL。
ID3D11DeviceContext **ppImmediateContext
这是个指向设备描述表对象的指针的指针。我们将这个指针定义为‘devcon’,所以在这里参数值为'&devcon'。它会以设备描述表对象的地址来填充。
好!让我们后退一步看一下事情的全貌:
D3D11CreateDeviceAndSwapChain(NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
NULL,
NULL,
NULL,
D3D11_SDK_VERSION,
&scd,
&swapchain,
&dev,
NULL,
&devcon);
没有那么难对吗?
现在我们初始化了Direct3D,让我们继续看如何关闭它。
关闭Direct3D
Direct3D无论何时被创建,它都必须被关闭。这很容易。
我们只有3个指令:
// this is the function that cleans up Direct3D and COM
void CleanD3D()
{
// close and release all existing COM objects
swapchain->Release();
dev->Release();
devcon->Release();
}
我们调用每个来自我们创建的接口(dev,devcon和swapchain)的Release()函数。没有参数。只是清理每个东东。
为什么?主要是如果你创建一个COM对象但不关闭它,它会继续在计算机后台运行(哪怕关闭了程序)直到你重启。尤其是如果你的游戏中有大量的资源这简直糟透了。让我们释放COM对象并允许Windows回收内存。
完成项目
哇!这只是开始,但已经是个高峰。
让我们看一下我们刚才做了什么。下面是我们添加到程序的代码,新的部分用粗体显示。
当你运行这个代码的时候有几个事情要注意。
首先,如果你还没有一个兼容DirectX 11的显卡,你需要使用引用模式代替硬件模式,否则你的程序将会崩溃。
第二,并不是所有版本的Visual Studio 都会正确定位头文件和库文件。
// include the basic windows header files and the Direct3D header files
#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>
// include the Direct3D Library file
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")
// global declarations
IDXGISwapChain *swapchain; // the pointer to the swap chain interface
ID3D11Device *dev; // the pointer to our Direct3D device interface
ID3D11DeviceContext *devcon; // the pointer to our Direct3D device context
// function prototypes
void InitD3D(HWND hWnd); // sets up and initializes Direct3D
void CleanD3D(void); // closes Direct3D and releases memory
// the WindowProc function prototype
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
// the entry point for any Windows program
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nCmdShow)
{
HWND hWnd;
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
wc.cbSize = sizeof(WNDCLASSEX);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)COLOR_WINDOW;
wc.lpszClassName = L"WindowClass";
RegisterClassEx(&wc);
RECT wr = {0, 0, 800, 600};
AdjustWindowRect(&wr, WS_OVERLAPPEDWINDOW, FALSE);
hWnd = CreateWindowEx(NULL,
L"WindowClass",
L"Our First Direct3D Program",
WS_OVERLAPPEDWINDOW,
300,
300,
wr.right - wr.left,
wr.bottom - wr.top,
NULL,
NULL,
hInstance,
NULL);
ShowWindow(hWnd, nCmdShow);
// set up and initialize Direct3D
InitD3D(hWnd);
// enter the main loop:
MSG msg;
while(TRUE)
{
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
if(msg.message == WM_QUIT)
break;
}
else
{
// Run game code here
// ...
// ...
}
}
// clean up DirectX and COM
CleanD3D();
return msg.wParam;
}
// this is the main message handler for the program
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch(message)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
} break;
}
return DefWindowProc (hWnd, message, wParam, lParam);
}
// this function initializes and prepares Direct3D for use
void InitD3D(HWND hWnd)
{
// create a struct to hold information about the swap chain
DXGI_SWAP_CHAIN_DESC scd;
// clear out the struct for use
ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));
// fill the swap chain description struct
scd.BufferCount = 1; // one back buffer
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; // use 32-bit color
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; // how swap chain is to be used
scd.OutputWindow = hWnd; // the window to be used
scd.SampleDesc.Count = 4; // how many multisamples
scd.Windowed = TRUE; // windowed/full-screen mode
// create a device, device context and swap chain using the information in the scd struct
D3D11CreateDeviceAndSwapChain(NULL,
D3D_DRIVER_TYPE_HARDWARE,
NULL,
NULL,
NULL,
NULL,
D3D11_SDK_VERSION,
&scd,
&swapchain,
&dev,
NULL,
&devcon);
}
// this is the function that cleans up Direct3D and COM
void CleanD3D(void)
{
// close and release all existing COM objects
swapchain->Release();
dev->Release();
devcon->Release();
}
如果你跑通了这个程序你会得到一个简单空白窗口,跟之前的一样。只是这次后台有Direct3D在运行!
你准备好继续了吗?
你已经创建了一个窗口,并使DirectX开启并关闭。在每节课的最后我会问一些问题并给一些练习。如果你做了这些东西,你会为后面的课程做好准备。
问题
1.如何调用一个基于COM的函数?为什么必须释放COM?
2.硬件模式和参考模式有什么不同?
3.设备和设备描述表有何不同?
练习
1.尝试不同的标记组合看你会用到什么。
2.学习调试程序。注释掉Release()函数以便查看调试警告。