美文网首页
使用 VS2017 开始 DirectX 之旅

使用 VS2017 开始 DirectX 之旅

作者: 木头石头骨头 | 来源:发表于2018-12-26 18:11 被阅读0次

    在很多 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 的基本结构。而这些基本结构,就留到下一篇博客吧。

    相关文章

      网友评论

          本文标题:使用 VS2017 开始 DirectX 之旅

          本文链接:https://www.haomeiwen.com/subject/ymarkqtx.html