基于Dx11写一个自己的游戏引擎--6

使用d3d绘图

上一篇使用d2d绘图,d3d有一点稍微不同的是会引入着色器,而且初始化比d2d会麻烦一些。

首先头文件引用以及库的链接。
注意这里将dx11改成了dx11.1,主要是考虑到11.1多了一些比11.0好很多的特特性。
考虑到D3D的复杂程度,引入了方便调试的库。
具体参考:
DirectX11--HR宏关于dxerr库的替代方案

如下:

#pragma once
#include <Windows.h>

//Comptr智能指针库
#include <wrl/client.h>
// Direct2D 头文件
#include <d2d1.h>
//Direct3D 头文件
#include <d3d11_1.h>


//Debug模式所用调试库
#include <assert.h>
#include"DXTrace.h"

//链接相关库

#pragma comment(lib,"d2d1.lib")
#pragma comment(lib,"d3d11.lib")
#pragma comment(lib,"dxgi.lib")

1.D3D的初始化

龙书第四章有初始化D3D的具体方式、步骤,这里只大概记录一下:

1. 创建ID3D11Device和ID3D11DeviceContext

类声明中添加
~~~ c++

//D3D资源
ComPtr<ID3D11Device>            m_pd3dDevice;
ComPtr<ID3D11DeviceContext>     m_pd3dDeviceContext;

//D3D
bool InitDirect3D();    //Direct3D初始化
void FinalizeDirect3D();//Direct3D释放资源

~~~

C++方法:
~~~ c++
//1. 创建设备和上下文
/
创建设备
HRESULT WINAPI D3D11CreateDevice(
In_opt IDXGIAdapter
pAdapter, //适配器类型,先选择主显卡,以后可能会增加功能,切换集显独显之类的
D3D_DRIVER_TYPE DriverType, //驱动类型,一般选择HARDWARE,软件模拟效率低还烫
HMODULE Software, //直接填NULL,因为不考虑软件模拟
UINT Flags, //调试时可以设置为Debug,发布时设为NULL或者Release
In_reads_opt( FeatureLevels ) CONST D3D_FEATURE_LEVEL* pFeatureLevels, //特征等级数组,用于测试顺序,一般由高到低.也可以不填
UINT FeatureLevels, //上一个数组的长度,为null的话这里填0
UINT SDKVersion, //始终为D3D11_SDK_VERSION
COM_Outptr_opt ID3D11Device** ppDevice, //返回设备对象的地址
Out_opt D3D_FEATURE_LEVEL* pFeatureLevel, //返回上述数组中的第一个可用元素。为null的话返回最高等级
COM_Outptr_opt ID3D11DeviceContext** ppImmediateContext ); //返回创建后的设备上下文

*/

//Flag
UINT createDeviceFlag = 0;              //默认为release模式,如果调试模式启动,设为DEBUG

if defined( DEBUG ) || defined( _DEBUG )

createDeviceFlag = D3D11_CREATE_DEVICE_DEBUG;

endif

//特征类型数组
D3D_FEATURE_LEVEL featureLevels[] = {D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0};
UINT              numFeatureLevels = ARRAYSIZE( featureLevels );
D3D_FEATURE_LEVEL featureLevel;

//创建设备和上下文

hr=D3D11CreateDevice( 0, D3D_DRIVER_TYPE_HARDWARE, NULL, createDeviceFlag, featureLevels, numFeatureLevels, D3D11_SDK_VERSION, m_pd3dDevice.GetAddressOf(), &featureLevel,
                   m_pd3dDeviceContext.GetAddressOf() );

if (FAILED(hr)){
    MessageBox(0,L"创建设备失败!" ,L"Error",0);
    return false;
}

~~~

2. 使用D3D的方法检测4X多重采样等级

类声明:
~~~ c++
UINT m_4xMsaaQuality; // 检测4xMsaa质量支持

~~~
方法:
~~~ c++
//检测4XMSAA支持的质量等级
m_pd3dDevice->CheckMultisampleQualityLevels( DXGI_FORMAT_R8G8B8A8_UNORM, 4, &m_4xMsaaQuality );

assert( m_4xMsaaQuality > 0 );

~~~

3. 创建交换链,交换链可以实现多缓冲

由于d3d11.0与11.1的SwapChain Desc结构有些许不同,这一块就比较复杂。
不过说复杂也不太准确,主要是创建交换链所需要的结构体属性太多,只看逻辑的话还是很简单的。

类声明:

//D3D11资源
    ComPtr<ID3D11Device>            m_pd3dDevice;
    ComPtr<ID3D11DeviceContext>     m_pd3dDeviceContext;
    ComPtr<IDXGISwapChain>          m_pSwapChain;


    //11.1 资源,支持11.1的话直接将原指针作为11.1的指针用
    ComPtr<ID3D11Device>        m_pd3dDevice1;
    ComPtr<ID3D11DeviceContext> m_pd3dDeviceContext1;
    ComPtr<IDXGISwapChain1>     m_pSwapChain1;

方法:
~~~ c++

//3. 创建交换链,因为dx11.1与dx1.0创建SwapChain方法不同,若在不支持11.1的机器上跑,会报错

//判断是否支持dx11.1 方法参考XJun所写方法

ComPtr<IDXGIDevice> dxgiDevice = nullptr;
ComPtr<IDXGIAdapter> dxgiAdapter = nullptr;
ComPtr<IDXGIFactory1> dxgiFactory0 = nullptr;   //D3D11.0 包含DXGI1.1
ComPtr<IDXGIFactory2> dxgiFactory1 = nullptr;   //D3D11.1 包含DXGI1.2 特有的接口类


//要使用IDXGIFACTORY创建DXGI交换链,需要dxgi.lib,IDXGIFACTORY由上面的m_pd3dDevice得来。
HR(m_pd3dDevice.As( &dxgiDevice ));
HR(dxgiDevice->GetAdapter( dxgiAdapter.GetAddressOf() ));
HR(dxgiAdapter->GetParent( __uuidof( IDXGIFactory1 ), reinterpret_cast<void **>( dxgiFactory0.GetAddressOf() ) ));

//查看该对象是否包含IDXGIFactory2的接口,如果指针地址变化,说明支持,这时候调用dx11.1的创建方法
hr = dxgiFactory0.As( &dxgiFactory1 );


//dx11.1创建交换链
if (dxgiFactory1!=nullptr){
    //这里太长就省略不放了,结构体的声明
    //...
    
    //是否开启4xMsaa?
    if (m_enable4xMsaa){
        sd.SampleDesc.Count = 4;
        sd.SampleDesc.Quality=m_4xMsaaQuality-1;
    } else {
        sd.SampleDesc.Count=1;
        sd.SampleDesc.Quality=0;
    }

    //全屏下的描述,这里相当于Dx11.0 的一些属性
    //...

    //创建交换链
    HR(dxgiFactory1->CreateSwapChainForHwnd(m_pd3dDevice.Get(),m_hMainWnd,&sd,&fd,nullptr,m_pSwapChain1.GetAddressOf()));
    m_pSwapChain1.As(&m_pSwapChain);

}
//dx11.0创建交换链
else {

//dx11.0交换链描述
    /*
    typedef struct DXGI_SWAP_CHAIN_DESC
    {
    DXGI_MODE_DESC BufferDesc;          //后台缓冲区描述
    DXGI_SAMPLE_DESC SampleDesc;        //采样器描述
    DXGI_USAGE BufferUsage;             //设为 DXGI_USAGE_RENDER_TARGET_OUTPUT,因为要渲染到后台缓冲区
    UINT BufferCount;                   //后台缓冲区Buffer数量,双缓存为1,三缓存为2
    HWND OutputWindow;                  //渲染到的窗口的句柄
    BOOL Windowed;                      //true时,程序以窗口模式运行,为false时,程序以全屏模式运行
    DXGI_SWAP_EFFECT SwapEffect;        //设为 DXGI_SWAP_EFFECT_DISCARD,最高效的显示模式
    UINT Flags;                         //使用该标志后,设为.._ALLOW_MODE_SWITCH,切换到全屏后,D3D会选择最匹配的显示模式
    }   DXGI_SWAP_CHAIN_DESC;
    

    */
    DXGI_SWAP_CHAIN_DESC sd;
    ZeroMemory( &sd, sizeof sd );

    /*
    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;
    
    */
   //...结构体的声明


    //是否开启4xMsaa?
    if ( m_enable4xMsaa ) {
        sd.SampleDesc.Count = 4;
        sd.SampleDesc.Quality = m_4xMsaaQuality - 1;
    } else {
        sd.SampleDesc.Count = 1;
        sd.SampleDesc.Quality = 0;
    }

    //创建交换链

    dxgiFactory0->CreateSwapChain(m_pd3dDevice.Get(),&sd,m_pSwapChain.GetAddressOf());


}

~~~

### 4. 创建渲染目标视图 Render Target View

Dx中(可能其他图形API也是?),资源自身不能直接被绑定到管线,必须创建相关的View才能绑定到管线中。要想把交换链中的后台缓冲区的内容绑定到Dx的管线中执行渲染时,就需要创建一个RenderTargetView。

类声明:
~~~ c++

//通用资源
ComPtr<ID3D11RenderTargetView> m_pRenderTargetView; //渲染目标视图

~~~

方法:

//4 .创建渲染目标视图

        //后台缓冲区
        ComPtr<ID3D11Texture2D> backBuffer;
        //第1个参数为索引值,只用了1个所以为0.第2个为缓冲区接口类型,通常为ITexture2D
        HR( m_pSwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), reinterpret_cast<void **>( backBuffer.GetAddressOf() ) ) );

        //第2个参数在弱类型时才用指定
        HR( m_pd3dDevice->CreateRenderTargetView( backBuffer.Get(),nullptr, m_pRenderTargetView.GetAddressOf() ) );
        
        

        //创建视图后就不需要原来的指针了,可以释放掉
        backBuffer.Reset();


5. 创建深度/模板缓冲区以及视图

与前面后台缓冲区声明类似,深度缓冲区同样是一个Texture2D的结构,不过这次要先声明缓冲区,再创建视图。模板信息与深度信息共享一个缓冲区,所以只用创建一个缓冲区、一个视图。

类声明:
~~~ c++

ComPtr<ID3D11DepthStencilView> m_pDepthStencilView; //深度模板缓冲区视图
ComPtr<ID3D11Texture2D> m_pDepthStencilBuffer;      //深度模板缓冲区(Texture2D的缓冲区i,不是Buffer)

~~~

方法(这里不复制赋值的语句了,直接看定义和注释就行):
~~~ c++

//5. 创建深度/模板缓冲区及其视图

    //要创建纹理,首先声明描述
    /*
            typedef struct D3D11_TEXTURE2D_DESC
        {
        UINT Width;                     //纹理宽带
        UINT Height;                    //纹理高度
        UINT MipLevels;                 //mipmap level的数量 ,深度缓冲区1个就够了
        UINT ArraySize;                 //纹理数组中纹理的数量,深度缓冲区1个就够了
        DXGI_FORMAT Format;             //纹理格式。必须是以下之一 : DXGI_FORMAT_D开头的4个
        DXGI_SAMPLE_DESC SampleDesc;    //多重采样描述,同之前类似
        D3D11_USAGE Usage;              //Usage。四个可选值:
                                        //Default:CPU不能读写,由GPU全权管理,现在使用这个;
                                        //IMMUTABLE 表示资源内容创建后不变,GPU只读,CPU不读不写
                                        //DYNAMIC   表示CPU会频繁更新。GPU读取,CPU写。
                                        //STAGING   表示CPU会读取副本(从显存复制到内存)
        UINT BindFlags;                 //指定资源绑定到管线哪个阶段,对于深度/模板缓冲区,设为D3D11_BIND_DEPTH_STENCIL.
                                        //其他选项
                                        //D3D11_BIND_RENDER_TARGET  作为渲染目标绑定
                                        //D3D11_BIND_SHADER_RESOURCE 作为着色器资源绑定
        UINT CPUAccessFlags;            //指定CPU访问权限,要结合Usage的值来选择
                                        //0表示不读不写
                                        //WRITE表示写
                                        //READ表示读
        UINT MiscFlags;                 //可选的标志值,与深度缓冲区无关,设为0
        }   D3D11_TEXTURE2D_DESC;
    

    */

    //结构体复制
    //....

    ////创建深度模板缓冲区

    HR(m_pd3dDevice->CreateTexture2D(&depthStencilDesc,nullptr,m_pDepthStencilBuffer.GetAddressOf()));

    //创建深度模板缓冲区视图
    HR(m_pd3dDevice->CreateDepthStencilView(m_pDepthStencilBuffer.Get(),nullptr,m_pDepthStencilView.GetAddressOf()));

~~~

6. OM阶段以及设置最终输出视口

这里绑定渲染目标和深度缓冲视图到管线的输出合并阶段(Output Merger Stage,OM),DX管线的最后一个阶段。

设置视口是因为有时我们不会把整个3D场景渲染到震哥哥后台缓冲区,可能只需要绘制到一小块后台缓冲区上,例如双人游戏的分屏等等。这就可以通过设置Viewport来实现。

方法:
~~~c++

    //将视图绑定到Output Merger Stage OM阶段,Dx管线的最后一个阶段


    m_pd3dDeviceContext->OMSetRenderTargets(1,m_pRenderTargetView.GetAddressOf(),m_pDepthStencilView.Get());



    //设置视口,绑定viewport到(Rasterize Stage,RS)光栅化阶段

    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport,sizeof(viewport));

    viewport.TopLeftX=0;
    viewport.TopLeftY=0;
    viewport.Width=static_cast<float>(m_ClientWidth);
    viewport.Height=static_cast<float>(m_ClientHeight);

    viewport.MaxDepth=1.0;
    viewport.MinDepth=0.0;

    UINT numViewports=1;
    m_pd3dDeviceContext->RSGetViewports(&numViewports,&viewport);



            return true;

~~~

以上就是InitDirect3D()方法的全部,最后在Init()中调用以初始化。

测试:
修改DrawScene()如下:
~~~c++
//绘制3D
//***************************************************************************************
assert(m_pd3dDeviceContext);
assert(m_pSwapChain);

static float black[] = {0.0f,0.0f,0.0f,1.0f};
m_pd3dDeviceContext->ClearRenderTargetView(m_pRenderTargetView.Get(),black);
m_pd3dDeviceContext->ClearDepthStencilView(m_pDepthStencilView.Get(),D3D11_CLEAR_DEPTH|D3D11_CLEAR_STENCIL,1.0f,0);

HR(m_pSwapChain->Present(0,0));


//***************************************************************************************

~~~

最后输出了一个黑色的屏幕~

以上就是D3D初始化的整体框架,当然还有很多不完善的地方,日后修改~

源码地址

猜你喜欢

转载自www.cnblogs.com/amaduse/p/10715823.html
今日推荐