基于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

类声明中添加


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

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

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多重采样等级

类声明:

    UINT							m_4xMsaaQuality;				//	检测4xMsaa质量支持

方法:

//检测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;


方法:


	//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。

类声明:


  //通用资源
  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的结构,不过这次要先声明缓冲区,再创建视图。模板信息与深度信息共享一个缓冲区,所以只用创建一个缓冲区、一个视图。

类声明:


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


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


//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来实现。

方法:



		//将视图绑定到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()如下:

//绘制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初始化的整体框架,当然还有很多不完善的地方,日后修改~

源码地址

posted @ 2019-04-17 12:55  bCoherence  阅读(629)  评论(0编辑  收藏  举报