DirectX教程(6):初始化Direct3D

  在上一节,我们已经研究了关于Direct3D的基本概念,现在让我们开始构建一个简单的Direct3D程序,以便深入探讨实际问题。在这个程序中,我们只是初始化Direct3D,然后将其关闭。虽然内容不多,甚至连一个“hello world”级别都不算,但这是一个好的开始。

COM

  COM是组件对象模型(Component Object Model)的缩写,它是一种创建非常高级对象的方法,它的行为就像乐高一样,可以将普通的乐高积木粘在一起就可以组装出更高级的形状。积木和积木之间相互独立但彼此兼容,你只需要将它们粘在一起就行。如果需要更换零件,只用拔下一块积木,然后再放置另一块。COM也是如此,COM对象实际上就是C++类或类组,你可以从中调用函数并实现某些目标。没有一个类需要另一类来进行操作,同时它们也不需要真正一起去完成工作,但是你可以根据需要将它们插入或拔出,而无需更改程序的其余部分。
  例如,你有一个广泛分布的游戏,并且想对其进行升级。你与其跟踪并向购买游戏的每个用户发送新的副本,还不如让他们下载更新的COM对象,然后将新对象直接插入到程序中,而无任何麻烦。我们不会对COM进行太详细的介绍,因为对我们来说太过复杂。COM目的就是消除所有复杂的内容,让你能够轻松进行开发,我们只需要懂得如何使用就行,不必去深究COM的原理了。
  那为什么要学习COM?实际上,DirectX就是一系列的COM对象,其中之一就是Direct3DDirect3D其实就是一个COM对象,其中包含了其他的COM对象,最终,它包含了运行2D和3D图形所需的一切软件和硬件。由于Direct3D已经存储在类中,因此我们可以通过箭头访问运算符访问Direct3D类中的CreateRenderTargetView()Release()函数。后面在实践中,我们会对此进行详细的介绍。

device->CreateRenderTargetView()
device->Release()

  即使COM隐藏了很多细节,但是你仍然需要了解以下四件事:1、COM对象就是一个类或一组类,这些类通过接口控制。一个接口就是一组函数,这些函数可以很好的控制COM对象。例如上面的示例中,“device”就是一个COM对象,并由函数进行控制;2、每种类型的COM对象都有唯一的ID。例如,Direct3D对象有自己的ID,DirectSound对象也有自己的ID,有时你需要在代码中使用此ID;3、使用COM对象完成操作之后,必须调用Release()函数,这么做的目的是为了告诉对象释放其内存并关闭其线程;4、COM对象很容易识别,因为它们通过以“I”(大写的i)开头,例如"ID3D10Device"。下面,让我们继续学习实际的代码。

Direct3D头文件

  在开始实际的Direct3D代码之前,让我们先谈谈头文件和库文件。在我们的演示程序中,我们将这些内容放在顶部,从而使我们能够在全局访问DIrect3D。下面,让我们看看代码:

// 包括基本的Windows头文件和Direct3D头文件
#include <windows.h>
#include <windowsx.h>
#include <d3d11.h>
#include <d3dx11.h>
#include <d3dx10.h>

// 包括Direct3D库文件
#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")

// 全局声明
IDXGISwapChain *swapchain;             // 指向交换链接口的指针
ID3D11Device *dev;                     // 指向Direct3D设备接口的指针
ID3D11DeviceContext *devcon;           // 指向Direct3D设备上下文的指针

// 函数原型
void InitD3D(HWND hWnd);     // 设置并初始化Direct3D
void CleanD3D(void);         // 关闭Direct3D并释放内存
// WindowProc函数原型
LRESULT CALLBACK WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

Direct3D 11头文件

#include <d3d11.h>
#include <d3dx11.h>

  这两行代码包含了Direct3D 11头文件,这两个头文件由Direct3D 11库中包含的实际方法的各种声明组成。这两个头文件包含不同的内容,d3d11.h头文件包含Direct3D的核心部分,d3dx11.h头文件包含Direct3D的扩展部分,这些扩展对于图形库不是必需的,但是在编写游戏或其他图形程序时可以非常方便。
  注意:并非所有编译器都能自动找到这些文件。在某些情况下,你需要设置你的项目,以便可以查看DirectX SDK的文件夹。如果你使用的是Visual Studio,我在此处编写了一个简短的mini课程

Direct3D 10头文件

#include <d3dx10.h>

  Direct3D 11实际上是Direct3D 10的扩展。因此,它从Direct3D 10借用了许多宏、函数和类。通过包含d3d10.h头文件,我们就可以在程序中使用它们。

Direct3D库文件

#pragma comment (lib, "d3d11.lib")
#pragma comment (lib, "d3dx11.lib")
#pragma comment (lib, "d3dx10.lib")

  这些代码包含了Direct3D 11的库文件。“#pragma comment”指令将某些信息放入项目的目标文件中。使用第一个参数lib,表示我们要向项目中添加库文件,然后我们指定需要添加哪些文件:“d3d11.lib”"d3dx11.lib""d3dx10.lib"

Direct3D设备接口的指针

ID3D11Device *dev;

  这个变量是指向设备的指针。在Direct3D中,设备是一个对象,作为视频适配器的虚拟表示。这行代码的意思是,我们将创建一个名为ID3D11Device的COM对象。当COM创建对象时,我们将忽略这个对象,并且仅适用此指针间接访问它。后面我们会详细介绍这种情况。

Direct3D设备上下文的指针

ID3D11DeviceContext *devcon;

  设备上下文类似于设备,但是它负责管理GPU和渲染管道(而设备主要主要负责处理显存)。该对象主要用于渲染图形并确定如何渲染图形。

交换链接口的指针

IDXGISwapChain *swapchain;

  正如我们上一课所讲,交换链是一系列缓冲区,这些缓冲区轮流呈现。此变量是指向此类链的指针。当请注意,该对象不属于Direct3D,但实际上是DXGI的一部分。

启动Direct3D

  实际编码Direct 3D的第一步是创建上述三个COM对象并对其进行初始化,这是通过一个函数以及一个包含图形设备信息的结构体来完成的。让我们先看一下这个功能,然后再看一下它的各个部分。

// 这个函数初始化并准备Direct3D以供使用
void InitD3D(HWND hWnd)
{
    // 创建一个结构体来保存有关交换链的信息
    DXGI_SWAP_CHAIN_DESC scd;

    // 清空这个结构体以供使用
    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

    // 填写交换链描述结构
    scd.BufferCount = 1;                                    // 一个后缓冲区
    scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;     // 使用32位色
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;      // 交换链如何使用
    scd.OutputWindow = hWnd;                                // 要使用的窗口
    scd.SampleDesc.Count = 4;                               // 多重采样的数量
    scd.Windowed = TRUE;                                    // 窗口全屏模式

    // 使用scd结构中的信息来创建设备、设备上下文和交换链
    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));

  我们使用ZermMemory()函数将整个scd结构体快速初始化为NULL。这样我们就不必遍历结构体的每个成员并分别将它们初始化为NULL。

scd.BufferCount = 1;

  该成员包含要在我们的交换链上使用的后台缓冲区的数量。我们将只使用一个后缓冲区和一个前缓冲区,因此我们将此值设置为1。我们可以使用更多的缓冲区,但是目前1就可以满足我们所有的需求。

scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;

  这个成员用来设置颜色的格式,在前后缓冲区中,每个像素均按颜色存储。这个值确定数据以什么格式存储,在这里我们将格式设置为 DXGI_FORMAT_R8G8B8A8_UNORM,这是一个用于指示格式的编码标志。

scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;

  这个成员用于设置我们将如何使用交换链,它有两个常用的值,这两个值也可以进行“或”运算。

描述
DXGI_USAGE_RENDER_TARGET_OUTPUT 当你希望将图形绘制到后台缓冲区中时使用此值
DXGI_USAGE_SHARED 通常,但设备创建缓冲区时,只有该设备可以使用它。此值允许缓冲区在多个设备对象之间共享

scd.OutputWindow = hWnd;

  这个成员用于设置Direct3D应该绘制的窗口的句柄,我们将一直使用相同的hWnd

scd.SampleDesc.Count = 1;

  这个成员告诉Direct3D如何执行多重采样抗锯齿(MSAA)渲染。基本上,抗锯齿通过将每个像素与周围的像素稍微融合,以使形状的边缘平滑。
多重采样抗锯齿
  如上图所示,左侧的线条有类似楼梯的锯齿效果,而右侧由于Direct3D融合了像素,因此显得平滑。这个值用于高速Direct3D在抗锯齿中应该加入多少细节,该值越高越好。Direct3D 11显卡在此处最多支持4个,但最小必须为1个。

scd.Windowed = TRUE;

  当我们在像现在这样的窗口中运行Direct3D时,此值设置为TRUE。否则,对于全屏模式,它将设置为FALSE。注意:进去全屏模式之前,您好需要进行其他更改,单独更改此值并不会使你的程序正确地全屏显示。

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以及显存和数模转换器等。我们通常让DXGI帮我们解决这个问题,为了告诉DXGI需要让它决定,我们在此使用NULL,以指示默认适配器
D3D_DRIVER_TYPE DriverType 此参数用于确定DIrect3D应该使用硬件还是软件进行渲染。你可以使用许多标志来确定这一点。在这里我们使用“D3D_DRIVER_TYPE_HARDWARE”,这显然是最佳选择,使用高级GPU硬件进行渲染。
HMODULE Software 我们不会涉及这个参数,设置为NULL。它与上一个参数的D3D_DRIVER_TYPE_SOFTWARE标志一起使用,以设置软件代码。它非常慢,因此我们不会使用
UINT Flags 此参数拥有一些UINT Flag可以改变Direct3D运行方式的标志,这些标志可以在一起进行“或”运算,幸运的是,即使我们不设置任何标志,设为NULL,也依然可以顺序进行
D3D_FEATURE_LEVEL *pFeatureLevels 每个Direct3D的主要版本都具有一系列所需的显卡功能,如果知道你的硬件满足哪个版本的要求,则可以更加轻松地了解硬件的功能(假设用户将使用不同的显卡)。 此参数用于创建功能列表,此列表告诉Direct3D,你期望程序使用哪些功能。 对于本程序,你将需要使用Direct 3D 11的显卡,因此不需要使用此参数,直接设置为NULL即可
UINT FeatureLevels 此参数指示列表中有多少个功能级别,我们直接设置为NULL即可。
UINT SDKVersion 此参数始终相同:D3D11_SDK_VERSION。为什么是这样呢?实际上仅与其他计算机的兼容性有关。每台机器通常具有不同次要版本的DirectX。这个参数告诉了你开发的游戏是为了哪个版本的用户DirectX。在不同版本的SDK中,此值返回不同的数字,你不需要更改,只需要使用D3D11_SDK_VERSION就能正常工作。
DXGI_SWAP_CHAIN_DESC *pSwapChainDesc 这是指向交换链描述结构的指针,我们在这里设置成“&scd”
IDXGISwapChain **ppSwapChain 这是指向交换链对象的指针,该函数为我们创建交换链对象,并将对象的地址存储在此指针中
ID3D11Device **ppDevice 这是指向设备对象的指针
D3D_FEATURE_LEVEL *FeatureLevel 有关功能级别的更多信息。这是指向功能级别变量的指针,当函数完成之后,这个变量将被找到的最高功能级别的标志填充,这使得程序员知道可以使用哪些硬件。我们通常将其设为NULL。
ID3D11DeviceContext **ppImmediateContext 这是指向设备上下文对象的指针

  现在我们已经初始化了Direct3D,让我们继续关闭它。

关闭Direct3D

  无论何时创建Direct3D,都必须将其关闭,这很简单,只需要三个命令。

// 这个函数是为了清理Direct3D和COM
void CleanD3D()
{
    // 关闭并释放所有的COM对象
    swapchain->Release();
    dev->Release();
    devcon->Release();
}

  在函数中,我们分别对三个COM对象调用Release()函数,从而释放一切。如果不这样做,将会造成糟糕的结果。如果你创建一个COM对象,但是不关闭它,那么即使在程序结束之后,它也将一直在计算机的后台运行,直到你关机重启。期间它将占用大量的资源,释放COM对象可以让一切资源都被释放,并允许Windows收回其内存。

最终的程序

// 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在后台运行。
第一个Direct3D程序

发布了19 篇原创文章 · 获赞 6 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/hjc132/article/details/104832064
今日推荐