使用 VS2017 开始 DirectX 之旅

在很多 DX11 的学习资料中,都告诉你,需要安装 DirectX SDK。但微软在发布 Windows 8 操作系统之后,就把 DirectX SDK 集成进了 Windows SDK,所以用 Visual Studio 2017 的小伙伴就不再需要安装任何额外的安装包了,只要在安装 VS 的时候装了 Visual C++ 和 Windows SDK,就可以直接愉快的开始 DX 的学习之旅了。

虽说不用安装,不代表在第一次学习 DX 的时候不会掉进坑里,所以这里手把手的从安装 VS2017 开始,到成功运行第一个 DirectX 程序。

1. 安装 Visual Studio 2017

官方下载网址

下载 Visual Studio 2017 社区版,到本地双击运行,等它安装完成后看到这个界面:

选择安装内容
还要安装这个选项

勾选之后,点击右下角安装按钮,等待一段时间,安装 Visual Studio。建议安装完成后不要删除这个安装管理器,以后会有用的。

注:因为我的电脑上已经安装了 Visual Studio ,所以右下角显示的修改按钮。

2. 运行 Visual Studio 2017

安装完成后,第一次运行 VS,可以看到这个界面。

运行 VS 2017

点击左上角 文件-> 新建 -> 项目,可以看到如下的项目导航界面:

新建项目

在这里,你只要正确安装了 Visual Studio 2017 ,就可以在右边选择你想创建的项目类型,而我们的 DirectX 项目在这里创建。

VS 提供的 DX 项目模板

但今天我不算这样创建,我们从一个空项目开始,一步一步构建一个 DirectX 项目,这样也方便我们了解 DX 代码的结构。

3. 创建空项目

初学 C++ 的时候,大家肯定都写过控制台程序。而开发 DX ,我们就不能再用控制台程序了,因为黑乎乎的控制台是显示不出丰富多彩的图形的。所以这里要创建桌面引用程序。

这时,可能有的以前用过 VS2015 或者 VS2012 的朋友第一用 VS2017 的时候会有点犯糊涂,因为以前最经典的Win32 项目创建选项没有了,我第一次用也蒙了很久。原来再升级到 VS2017 时,原来的 Win32 桌面应用的选项被改成了 Windows 桌面向导。当你用这个开始创建项目的时候,你会发现它还是原来的味道。

注:如果用 VS2015 的朋友,这里就选择创建 Win32 桌面程序,项目的设置和这里用 VS2017 创建的设置一模一样。

选择项目
项目设置

创建完成发现什么代码都没有,这很完美,干干净净的。下面就开始写代码

4. DirectX 的代码

因为是 Windows 应用程序,自然绕不开 Win32 这道坎。这里我选择写的代码是 DirectX11 的,至于 DirectX 12,还没开这个坑。

4.1 创建 d3dUtility.h 头文件

将下面代码复制进去:

#pragma once
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib, "d3dcompiler.lib")
#pragma comment(lib, "dxguid.lib")
#pragma comment(lib, "winmm.lib")

#ifndef __d3dUtilityH__
#define __d3dUtilityH__

#include <Windows.h>

// 数学库
#include <DirectXMath.h>
#include <DirectXPackedVector.h>
#include <DirectXColors.h>

// DirectX11 相关库
#include <d3d11.h>
#include <d3dcompiler.h>

namespace d3d {
    // 初始化D3D
    bool InitD3D(HINSTANCE hInstance, int width, int height,
        ID3D11RenderTargetView** renderTargetView,
        ID3D11DeviceContext** immediateContext,
        IDXGISwapChain** swapChain,
        ID3D11Device** device,
        ID3D11Texture2D** depthStencilBuffer,
        ID3D11DepthStencilView** depthStencilView);

    // 消息循环
    int EnterMsgLoop(bool(*ptr_display)(float timeDelta));

    // 回调函数
    LRESULT CALLBACK WndProc(
        HWND,
        UINT msg,
        WPARAM,
        LPARAM lParam
    );
}
#endif // !__d3dUtilityH__

4.2 创建 d3dUtility.cpp 文件

将下面代码复制进去

//这是我们自己创建的"d3dUtility.h"头文件
#include "d3dUtility.h"

//D3D初始化
//这个函数中包括两个部分:第一部分:创建一个窗口;第二部分:初始化D3D
//函数参数包括:
//1. HINSTANCE hInstance  当前应用程序实例的句柄
//2. int width            窗口宽 
//3. int height           窗口高
//4. ID3D11RenderTargetView** renderTargetView 目标渲染视图指针
//5. ID3D11DeviceContext** immediateContext    设备上下文指针,设备上下文包含设备的使用环境和设置
//6. IDXGISwapChain** swapChain                交换链指针,用于描述交换链的特性
//7. ID3D11Device** device                     设备用指针,每个D3D程序至少有一个设备
//8. ID3D11Texture2D** depthStencilBuffer;      //深度/模板缓冲区
//9. ID3D11DepthStencilView** depthStencilView;     //深度/模板视图

bool d3d::InitD3D(
    HINSTANCE hInstance,
    int width,
    int height,
    ID3D11RenderTargetView** renderTargetView,
    ID3D11DeviceContext** immediateContext,
    IDXGISwapChain** swapChain,
    ID3D11Device** device,
    ID3D11Texture2D** depthStencilBuffer,
    ID3D11DepthStencilView** depthStencilView)
{
    //***********第一部分:创建一个窗口开始***************
    //这部分的代码和实验一中的创建窗口代码基本一致,具体参数的注释可以参考实验一
    //创建窗口的4个步骤:1 设计一个窗口类;2 注册窗口类;3 创建窗口;4 窗口显示和更新 
    //1 设计一个窗口类
    WNDCLASS wc;

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = (WNDPROC)d3d::WndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(0, IDI_APPLICATION);
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = 0;
    wc.lpszClassName = L"Direct3D11App";

    //2 注册窗口类
    if (!RegisterClass(&wc))
    {
        ::MessageBox(0, L"RegisterClass() - FAILED", 0, 0);
        return false;
    }

    //3 创建窗口
    HWND hwnd = 0;
    hwnd = ::CreateWindow(L"Direct3D11App",
        L"D3D11",
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        width,
        height,
        0,
        0,
        hInstance,
        0);

    if (!hwnd)
    {
        ::MessageBox(0, L"CreateWindow() - FAILED", 0, 0);
        return false;
    }

    //4 窗口显示和更新
    ::ShowWindow(hwnd, SW_SHOW);
    ::UpdateWindow(hwnd);
    //***********第一部分:创建一个窗口结束***************

    //***********第二部分:初始化D3D开始***************
    //初始化D3D设备主要为以下步骤
    //1. 描述交换链,即填充DXGI_SWAP_CHAIN_DESC结构
    //2. 使用D3D11CreateDeviceAndSwapChain创建D3D设备(ID3D11Device)
    //   设备上下文接口(ID3D11DeviceContext),交换链接口(IDXGISwapChain)
    //3. 创建目标渲染视图(ID3D11RenderTargetView)
    //4. 设置视口(View Port)   


    //第一步,描述交换链,即填充DXGI_SWAP_CHAIN_DESC结构
    DXGI_SWAP_CHAIN_DESC sd;                           //首先声明一个DXGI_SWAP_CHAIN_DESC的对象sd
    ZeroMemory(&sd, sizeof(sd));                   //用ZeroMemory对sd进行初始化,ZeroMemory的用法见实验一的补充知识
    sd.BufferCount = 1;                                //交换链中后台缓存数量,通常为1
    sd.BufferDesc.Width = width;                       //缓存区中的窗口宽
    sd.BufferDesc.Height = height;                     //缓存区中的窗口高
    sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; //指定32位像素格式,表示红绿蓝Alpha各8位,其他格式见书P50
    sd.BufferDesc.RefreshRate.Numerator = 60;          //刷新频率的分子为60
    sd.BufferDesc.RefreshRate.Denominator = 1;         //刷新频率的分母为1,即刷新频率为每秒6次
    sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;  //用来描述后台缓存的用法控制CPU对后台缓存的访问 
    sd.OutputWindow = hwnd;                            //指向渲染目标窗口的句柄
    sd.SampleDesc.Count = 1;                           //多重采样的属性,本例中不采用多重采样即,
    sd.SampleDesc.Quality = 0;                         //所以Count=1,Quality=0
    sd.Windowed = TRUE;                                //TRUE为窗口模式,FALSE为全屏模式
    //sd.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
    //sd.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;

    //第二步,创建设备,交换链以及立即执行上下文
    //创建一个数组确定尝试创建Featurelevel的顺序
    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_0, //D3D11 所支持的特征,包括shader model 5
        D3D_FEATURE_LEVEL_10_1, //D3D10 所支持的特征,包括shader model 4.
        D3D_FEATURE_LEVEL_10_0,
    };

    //获取D3D_FEATURE_LEVEL数组的元素个数
    UINT numFeatureLevels = ARRAYSIZE(featureLevels);

    //调用D3D11CreateDeviceAndSwapChain创建交换链,设备,和设备上下文
    //分别存入swapChain,device,immediateContext
    if (FAILED(D3D11CreateDeviceAndSwapChain(
        NULL,                       //确定显示适配器,NULL表示默认显示适配器
        D3D_DRIVER_TYPE_HARDWARE,   //选择驱动类型,这里表示使用三维硬件加速
        NULL,                       //只有上一个参数设置D3D_DRIVER_TYPE_SOFTWARE时,才使用这个参数
        0,                          //也可以设置为D3D11_CREATE_DEVICE_DEBUG开启调试模式
        featureLevels,              //前面定义的D3D_FEATURE_LEVEL数组
        numFeatureLevels,           //D3D_FEATURE_LEVEL的元素个数
        D3D11_SDK_VERSION,          //SDK的版本,这里为D3D11
        &sd,                        //前面定义的DXGI_SWAP_CHAIN_DESC对象
        swapChain,                  //返回创建好的交换链指针,InitD3D函数传递的实参
        device,                     //返回创建好的设备用指针,InitD3D函数传递的实参
        NULL,                       //返回当前设备支持的featureLevels数组中的第一个对象,一般设置为NULL
        immediateContext)))      //返回创建好的设备上下文指针,InitD3D函数传递的实参
    {
        ::MessageBox(0, L"CreateDevice - FAILED", 0, 0);  //如果创建失败,弹出消息框
        return false;
    }

    //第三步,创建并设置渲染目标视图
    HRESULT hr = 0;         //COM要求所有的方法都会返回一个HRESULT类型的错误号
    ID3D11Texture2D* pBackBuffer = NULL;      //ID3D11Texture2D类型的,后台缓存指针
    //调用GetBuffer()函数得到后台缓存对象,并存入&pBackBuffer中
    hr = (*swapChain)->GetBuffer(0,                        //缓存索引,一般设置为0
        __uuidof(ID3D11Texture2D), //缓存类型
        (LPVOID*)&pBackBuffer);   //缓存指针
//判断GetBuffer是否调用成功
    if (FAILED(hr))
    {
        ::MessageBox(0, L"GetBuffer - FAILED", 0, 0); //如果调用失败,弹出消息框
        return false;
    }

    //调用CreateRenderTargetView创建好渲染目标视图,创建后存入renderTargetView中
    hr = (*device)->CreateRenderTargetView(pBackBuffer,            //上面创建好的后台缓存
        NULL,                   //设置为NULL得到默认的渲染目标视图
        renderTargetView);     //返回创建好的渲染目标视图,InitD3D函数传递的实参
    pBackBuffer->Release();   //释放后台缓存
    //判断CreateRenderTargetView是否调用成功
    if (FAILED(hr))
    {
        ::MessageBox(0, L"CreateRender - FAILED", 0, 0);  //如果调用失败,弹出消息框
        return false;
    }
    //************************增加的步骤************************
    D3D11_TEXTURE2D_DESC dsDesc;
    dsDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;  //这里表示24位用于深度缓存,8位用于模板缓存
    dsDesc.Width = 800;                             //深度模板缓存的宽度
    dsDesc.Height = 600;                            //深度模板缓存的高度
    dsDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;    //绑定标识符
    dsDesc.MipLevels = 1;
    dsDesc.ArraySize = 1;
    dsDesc.CPUAccessFlags = 0;                      //CPU访问标识符,0为默认值
    dsDesc.SampleDesc.Count = 1;                    //多重采样的属性,本例中不采用多重采样即,
    dsDesc.SampleDesc.Quality = 0;                  //所以Count=1,Quality=0
    dsDesc.MiscFlags = 0;
    dsDesc.Usage = D3D11_USAGE_DEFAULT;

    //创建深度模板缓存
    hr = (*device)->CreateTexture2D(&dsDesc, 0, depthStencilBuffer);
    if (FAILED(hr))
    {
        MessageBox(NULL, L"Create depth stencil buffer failed!", L"ERROR", MB_OK);
        return false;
    }
    //创建深度模板缓存视图
    hr = (*device)->CreateDepthStencilView(*depthStencilBuffer, 0, depthStencilView);
    if (FAILED(hr))
    {
        MessageBox(NULL, L"Create depth stencil view failed!", L"ERROR", MB_OK);
        return false;
    }
    //将渲染目标视图和深度模板缓存视图绑定到渲染管线
    (*immediateContext)->OMSetRenderTargets(1,                   //绑定的目标视图的个数
        renderTargetView,    //渲染目标视图,InitD3D函数传递的实参
        *depthStencilView);  //绑定模板
//************************增加的步骤************************
/*
    //将渲染目标视图绑定到渲染管线
    (*immediateContext)->OMSetRenderTargets(1,                   //绑定的目标视图的个数
                                            renderTargetView,    //渲染目标视图,InitD3D函数传递的实参
                                            NULL );              //设置为NULL表示不绑定深度模板
*/
//第四步,设置视口大小,D3D11默认不会设置视口,此步骤必须手动设置  
    D3D11_VIEWPORT vp;    //创建一个视口的对象
    vp.Width = width;     //视口的宽
    vp.Height = height;   //视口的高
    vp.MinDepth = 0.0f;   //深度值的下限,**由于深度值是[0, 1]所以下限值是0
    vp.MaxDepth = 1.0f;   //深度值的上限,上限值是1
    vp.TopLeftX = 0;      //视口左上角的横坐标
    vp.TopLeftY = 0;      //视口左上角的总坐标

    //设置视口
    (*immediateContext)->RSSetViewports(1,     //视口的个数
        &vp); //上面创建的视口对象

    return true;
    //***********第二部分:初始化D3D结束***************
}


//消息循环,和之前"Hello World"程序中Run()起到同样的功能
//bool (*ptr_display)(float timeDelta)表示传递一个函数指针作为参数
//这个函数有一个float类型的参数,有一个bool类型的返回
int d3d::EnterMsgLoop(bool(*ptr_display)(float timeDelta))
{
    MSG msg;
    ::ZeroMemory(&msg, sizeof(MSG));                  //初始化内存

    static float lastTime = (float)timeGetTime();     //第一次获取当前时间

    while (msg.message != WM_QUIT)
    {
        if (::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
        {
            ::TranslateMessage(&msg);
            ::DispatchMessage(&msg);
        }
        else
        {
            float currTime = (float)timeGetTime();            //第二次获取当前时间
            float timeDelta = (currTime - lastTime)*0.001f;    //获取两次时间之间的时间差

            ptr_display(timeDelta);    //调用显示函数,这在后面实现图形的变化(如旋转)时会用到

            lastTime = currTime;
        }
    }
    return msg.wParam;
}

4.3 创建一个 Main.cpp 文件

将下面代码复制进去:

#include "d3dUtility.h"
 
ID3D11Device* device = NULL;                    // D3D11设备指针
IDXGISwapChain* swapChain = NULL;               // 交换链指针
ID3D11DeviceContext* immediateContext = NULL;   // 执行上下文
ID3D11RenderTargetView* renderTargetView = NULL;// 渲染目标视图指针
ID3D11DepthStencilView* depthStencilView = NULL;//深度模板视图
ID3D11Texture2D* depthStencilBuffer = NULL;     //深度缓存



bool Setup() {
    return true;
}

void Cleanup() {
    if (device) device->Release();
    if (immediateContext) immediateContext->Release();
    if (swapChain) swapChain->Release();
    if (renderTargetView) renderTargetView->Release();
    if (depthStencilView) depthStencilView->Release();
    if (depthStencilBuffer) depthStencilBuffer->Release();
}

bool Display(float timeDelta) {
    if (device) {
        float ClearColor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
        immediateContext->ClearRenderTargetView(renderTargetView, ClearColor);
        swapChain->Present(0, 0);
    }
    return true;
}

/*回调函数*/
LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        ::PostQuitMessage(0);
        break;

    case WM_KEYDOWN:
        if (wParam == VK_ESCAPE)
            ::DestroyWindow(hwnd);
        break;
    }
    return ::DefWindowProc(hwnd, msg, wParam, lParam);
}

int WINAPI WinMain(HINSTANCE hinstance,
    HINSTANCE prevInstance,
    PSTR cmdLine,
    int showCmd)
{

    //初始化
    //**注意**:最上面声明的IDirect3DDevice9指针,在这里作为参数传给InitD3D函数
    if (!d3d::InitD3D(hinstance,
        800,
        600,
        &renderTargetView,
        &immediateContext,
        &swapChain,
        &device,
        &depthStencilBuffer,
        &depthStencilView))// [out]The created device.
    {
        ::MessageBox(0, L"InitD3D() - FAILED", 0, 0);
        return 0;
    }

    if (!Setup())
    {
        ::MessageBox(0, L"Setup() - FAILED", 0, 0);
        return 0;
    }

    d3d::EnterMsgLoop(Display);

    Cleanup();

    return 0;
}

5. 编译运行

编译运行的结果就是这样的:

运行结果

有人会说:“我靠这不还是黑框框吗?这么多代码,说好的图形呢?”但实时结果就是如此,几百行代码下来,只是个黑框。万事开头难,这就是第一个 DirectX 项目,代码里包含了 DX 的基本结构。而这些基本结构,就留到下一篇博客吧。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342