【DirectX11】【学习笔记(1)】初始化DirectX11

首先要涉及到的是Directx11环境的配置以及基础Win32程序框架的编写,这一部分和DirectX9.0是类似的。

这里就不再赘述,写完DirectX11后会再开一个DirectX9专栏,记录上述部分。这里先预留。不过相关代码本节内容都会贴出来,所以没有看到以上两步的朋友也没有关系。接下来就直接进入主题:对DirectX11对象初始化。

首先是本次笔记思维导图

一.创建全局对象

首先确定:Swapchain是一个COM接口对象

COM是什么?

根据MSDN文档所述:COM是一种允许对象跨进程和计算机边界进行交互的技术。也就是说它允许我们跨过我们自身创建的进程,可以和别的进程进行交互,包括数据访问之类的。COM通过指定操作与对象关联的数据的唯一方法是通过对象上的接口。 

学过C++的同学肯定对C++接口有一定了解。COM在某种意义上使用单词interface,与Visual C ++编程中通常使用的不同。 C ++接口指的是类支持的所有函数,并且对象的客户端可以调用它来与之交互。 COM接口是指COM类实现的预定义相关函数组,但特定接口不一定代表该类支持的所有函数(可能只代表其中一部分函数)

1.Swapchain

Swapchain用来交换前后台缓冲之间的数据。这个技术也叫做双缓存技术。当我们渲染场景的时候,一般我们都是把它渲染到后台缓冲中,当我们把后台缓冲展示到屏幕上的时候,它已经被渲染好了。否则的话,我们就会在屏幕上看到一个扫描线(这是光栅化阶段,对屏幕进行填充颜色,之后的章节会讲到)

2.代表GPU硬件设备的接口

而DitectX11把D3d11Device接口一分为二,这样可以支持多线程。

1.ID3D11DeviceContext接口(用来处理和调用渲染相关的函数)

2.ID3D11Device(用来处理调用和渲染无关的函数)

一分为二的原因:当我们在加载一个模型或者创建一个物体的时候,我们可以调用ID3D11Device对象,这个时候,我们可以同时调用ID3D11DeviceContext对象来对我们的场景进行渲染。

3.render target view

上文已经提到过了,我们需要先渲染到后台缓存,接着再把后台缓存的数据渲染到屏幕上。

render target view就是我们的后台缓存,本质上是个2d的纹理图。之后会被渲染管线中的其它部分调用,最终渲染到屏幕上。

以上四个全局对象声明代码如下:

IDXGISwapChain* SwapChain;
ID3D11Device* d3d11Device;
ID3D11DeviceContext* d3d11DevCon;
ID3D11RenderTargetView* renderTargetView;

【总结】我们把内容先渲染到render target view中,通过SwapChain交换到屏幕上。ID3D11Device用于调用非渲染函数,ID3D11DeviceContext用于调用渲染函数。

二.相关函数

程序中函数执行代码如下:

if(!InitializeDirect3d11App(hInstance))    //Initialize Direct3D
{
    MessageBox(0, L"Direct3D Initialization - Failed",
        L"Error", MB_OK);
    return 0;
}

if(!InitScene())    //Initialize our scene
{
    MessageBox(0, L"Scene Initialization - Failed",
        L"Error", MB_OK);
    return 0;
}

messageloop();

ReleaseObjects();    

以下分别介绍上面所用到的函数

2.1Initializing Direct3D 11

(参数为我们程序实例的句柄,关于句柄的理解可参考我这一篇博客

由于该函数涵盖内容较多,我写了一个思维导图

函数实现如下:

bool InitializeDirect3dApp(HINSTANCE hInstance)
{
HRESULT hr;

//Describe our Buffer
DXGI_MODE_DESC bufferDesc;

ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));

bufferDesc.Width = Width;
bufferDesc.Height = Height;
bufferDesc.RefreshRate.Numerator = 60;
bufferDesc.RefreshRate.Denominator = 1;
bufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

//Describe our SwapChain
DXGI_SWAP_CHAIN_DESC swapChainDesc; 
    
ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

swapChainDesc.BufferDesc = bufferDesc;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.OutputWindow = hwnd; 
swapChainDesc.Windowed = TRUE; 
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;


//Create our SwapChain
hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL,
    D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);

//Create our BackBuffer
ID3D11Texture2D* BackBuffer;
hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer );

//Create our Render Target
hr = d3d11Device->CreateRenderTargetView( BackBuffer, NULL, &renderTargetView );
BackBuffer->Release();

//Set our Render Target
d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, NULL );

return true;
}

HRESULT对象,用来进行错误检查。

2.1.1后台缓存描述

函数中第一件事是描述后台缓冲,为此我们创建了一个DXGI_MODE_DESC对象,然后我们清空对象内存(防止其中的某个参数已经被赋值),然后我们再填充这个结构体。DXGI_MODE_DESC 结构体:

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, *LPDXGI_MODE_DESC;

每个参数解释如下:

Width - 缓冲区宽度

Height - 缓冲区高度

RefreshRate - 这是一个DXGI_RATIONAL类型值,描述了刷新率,单位HZ。

Format - 这是一个DXGI_FORMAT枚举值 描述了我们展示的格式。我们可以采用DXGI_FORMAT_R8G8B8A8_UNORM

这是一个32位无符号整形,RBGA分别占8位。

ScanlineOrdering - DXGI_MODE_SCANLINE_ORDER 枚举值 描述了光栅器渲染平面的方式,因为在上文Swapchain中讲过:这个过程我们看不到。所以我们把值设为DXGI_MODE_SCALING_UNSPECIFIED,意思是不指定,方式不重要。

Scaling - DXGI_MODE_SCALING枚举值,解释了一个图像时如何被拉伸显示到显示器中的。

    DXGI_MODE_SCALING_UNSPECIFIED, 意思是不指定值。

    DXGI_MODE_SCALING_CENTERED,意思是图像位于屏幕中间,不拉伸

    DXGI_MODE_SCALING_STRETCHED,意思是图像被拉伸到屏幕大小。

上述赋值过程代码如下:

DXGI_MODE_DESC bufferDesc;

ZeroMemory(&bufferDesc, sizeof(DXGI_MODE_DESC));

bufferDesc.Width = Width;
bufferDesc.Height = Height;
bufferDesc.RefreshRate.Numerator = 60;
bufferDesc.RefreshRate.Denominator = 1;
bufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
bufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
bufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;

2.1.2SwapChain描述

在上面一个步骤,我们已经把后台缓冲描述结构填好了。现在我们创建一个SwapChain描述结构体

同样先清除内存,再填充结构。结构体如下:

typedef struct DXGI_SWAP_CHAIN_DESC {
  DXGI_MODE_DESC   BufferDesc;
  DXGI_SAMPLE_DESC SampleDesc;
  DXGI_USAGE       BufferUsage;
  UINT             BufferCount;
  HWND             OutputWindow;
  BOOL             Windowed;
  DXGI_SWAP_EFFECT SwapEffect;
  UINT             Flags;
} DXGI_SWAP_CHAIN_DESC;

BufferDesc - 这是一个DXGI_MODE_DESC 结构,我们把之前填充好的后台结构填到这里。

SampleDesc - 这是一个DXGI_SAMPLE_DESC结构,描述多重采样。(多重采样是用来使图像更加平滑,毕竟像素块不是无限小的,小的像素可能像一个个小方块,有点马赛克的感觉)

BufferUsage - 这是一个DXGI_USAGE 枚举值,描述了CPU对于后台缓冲的权限。我们这里对其赋值DXGI_USAGE_RENDER_TARGET_OUTPUT ,因为我们会对它进行渲染。

BufferCount- 我们用到的后台缓冲数。双缓冲,我们设置为1,三缓冲,设置为2。依次类推

OutputWindow - 窗口句柄

Windowed - true为窗口化,false为全屏(全屏退出可能导致程序冻结)

SwapEffect - 这是一个DXGI_SWAP_EFFECT 枚举值,描述了显卡驱动如何交换前后缓冲。我们这里设置为DXGI_SWAP_EFFECT_DISCARD ,让驱动自己决定最有效的方式。

Flags - DXGI_SWAP_CHAIN_FLAG枚举值,这是一个描述SwapChain其他行为的标志位。现在可能比较有用的是DXGI_SWAP_CHAIN_FLAG_ALLOW_MODE_SWITCH,该值允许我们转换Windowed 参数时,改变显示区域大小。

填充代码如下:

DXGI_SWAP_CHAIN_DESC swapChainDesc; 
    
ZeroMemory(&swapChainDesc, sizeof(DXGI_SWAP_CHAIN_DESC));

swapChainDesc.BufferDesc = bufferDesc;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 1;
swapChainDesc.OutputWindow = hwnd; 
swapChainDesc.Windowed = TRUE; 
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;

2.1.3创建设备和SwapChain

现在我们要创建第一部分提到的全局对象。包括:direct3d device, device context, 和Swap Chain。

通过调用D3D11CreateDeviceAndSwapChain()函数。函数参数如下:

HRESULT D3D11CreateDeviceAndSwapChain(
  __in   IDXGIAdapter *pAdapter,
  __in   D3D_DRIVER_TYPE DriverType,
  __in   HMODULE Software,
  __in   UINT Flags,
  __in   const D3D_FEATURE_LEVEL *pFeatureLevels,
  __in   UINT FeatureLevels,
  __in   UINT SDKVersion,
  __in   const DXGI_SWAP_CHAIN_DESC *pSwapChainDesc,
  __out  IDXGISwapChain **ppSwapChain,
  __out  ID3D11Device **ppDevice,
  __out  D3D_FEATURE_LEVEL *pFeatureLevel,
  __out  ID3D11DeviceContext **ppImmediateContext
);

pAdapter- 指向一个视频容器。我们设为NULL

DriverType - 一个D3D_DRIVER_TYPE 枚举值。描述了direct3d如何被实施,我们设为D3D_DRIVER_TYPE_HARDWARE ,表示我们程序将在GPU运行

Software - HMODULE 句柄,用来实施软件光栅化。

Flags - 可以设置多个D3D11_CREATE_DEVICE_FLAG 类型值

pFeatureLevels - 指向一堆D3D_FEATURE_LEVEL枚举值的指针,表明现在Directx特点的版本,我们设为NULL,使用最高版本。

FeatureLevels - 上一个参数中的元素个数,设为NULL

SDKVersion - SDK版本。我们设为D3D11_SDK_VERSION
pSwapChainDesc - 指向DXGI_SWAP_CHAIN_DESC 结构体的指针,我们在上面已经创建好了。

ppSwapChain - 指向接口对象的指针,用来接收创建好的对象。

ppDevice - 指向设备的指针。

pFeatureLevel - 指向一个D3D_FEATURE_LEVEL 值的指针,保存最高等级特点。

ppImmediateContext - 指向ID3D11DeviceContext 指针。

函数调用如下:

hr = D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, NULL, NULL, NULL,
    D3D11_SDK_VERSION, &swapChainDesc, &SwapChain, &d3d11Device, NULL, &d3d11DevCon);

2.1.4创建后台缓冲

创建后台缓冲,然后这个缓冲会用来创建我们的our render target view。

首先调用SwapChain对象的getbuffer函数

HRESULT GetBuffer(
  [in]       UINT Buffer,
  [in]       REFIID riid,
  [in, out]  void **ppSurface
);

Buffer - 因为我们把swapChainDesc.SwapEffect设置成了DXGI_SWAP_EFFECT_DISCARD。所以驱动只能获得第一块缓存,我们设为0

riid - 这是更改后台缓冲区的接口类型的引用ID。我们把它设为2d texture (ID3D11Texture2D)

ppSurface - 指针指向创建的后台缓冲

调用代码如下:

ID3D11Texture2D* BackBuffer;
hr = SwapChain->GetBuffer( 0, __uuidof( ID3D11Texture2D ), (void**)&BackBuffer );

2.1.5创建Render Target

创建Target View ,这个之后会发送给管线的其他阶段。

调用函数

HRESULT CreateRenderTargetView(
  [in]   ID3D11Resource *pResource,
  [in]   const D3D11_RENDER_TARGET_VIEW_DESC *pDesc,
  [out]  ID3D11RenderTargetView **ppRTView
);

pResource - 刚才创建的后台缓冲

pDesc - D3D11_RENDER_TARGET_VIEW_DESC 结构体,我们设置NULL来创建一个访问mipmap level 0中所有子资源的视图。

ppRTView - 指向renderTargetView指针。

创建好renderTargetView之后我们就不需要后台缓冲了。(这是因为,后台缓冲指针指向了交换链中的后台缓冲BUFFER,现在rendertargetview已经获得了这个指针区域,所以不再需要另一个指针指向它了)

图解:

2.1.6设置 Render Targets

我们在初始化做的最后一件事,就是把RenderTargets绑定到管线的输出阶段上。这个功能同时也会绑定深度和模板缓存,但现在由于还没有创建,所以暂时设置为空

void OMSetRenderTargets(
  [in]  UINT NumViews,
  [in]  ID3D11RenderTargetView *const **ppRenderTargetViews,
  [in]  ID3D11DepthStencilView *pDepthStencilView
);

NumViews - 绑定的rendertargets个数

ppRenderTargetViews - 指向一组要绑定的render target views

pDepthStencilView - 指向深度模板缓存的指针。

函数调用

d3d11DevCon->OMSetRenderTargets( 1, &renderTargetView, NULL );

2.2Clean Up

在释放对象时候,我们要删除所有创建的COM对象,否则会导致内存泄漏。

2.3Initialize Scene

在这个功能里,我们会放置我们的对象,加载模型,声音。但现在还只是开始,所以我们在函数里不做任何实现

2.4Update Scene

这个功能用来更新场景,像是改变物体位置什么的。目前我们只改变背景颜色。

void UpdateScene()
{
    //Update the colors of our scene
    red += colormodr * 0.00005f;
    green += colormodg * 0.00002f;
    blue += colormodb * 0.00001f;

    if(red >= 1.0f || red <= 0.0f)
        colormodr *= -1;
    if(green >= 1.0f || green <= 0.0f)
        colormodg *= -1;
    if(blue >= 1.0f || blue <= 0.0f)
        colormodb *= -1;
}

2.5Render Scene

void DrawScene()
{
    //Clear our backbuffer to the updated color
    D3DXCOLOR bgColor( red, green, blue, 1.0f );

    d3d11DevCon->ClearRenderTargetView(renderTargetView, bgColor);

    //Present the backbuffer to the screen
    SwapChain->Present(0, 0);
}

在这个函数里我们只会做渲染场景的事情。

主要用到了SwapChain的present函数。这个功能就是交换前后缓冲。

2.5最后是我们程序的消息循环函数。这是我们代码运行的主体。

int messageloop(){
MSG msg;
ZeroMemory(&msg, sizeof(MSG));
while(true)
{
    BOOL PeekMessageL( 
        LPMSG lpMsg,
        HWND hWnd,
        UINT wMsgFilterMin,
        UINT wMsgFilterMax,
        UINT wRemoveMsg
        );

    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
        if (msg.message == WM_QUIT)
            break;
        TranslateMessage(&msg);    
        DispatchMessage(&msg);
    }
    else{
///**************new**************
        // run game code
        
        UpdateScene();
        DrawScene();
        
///**************new**************
    }
}
return msg.wParam;
}

主要是进行消息检查,当没有消息的时候,我们就更新场景,然后渲染。

好了。本节内容就到这里拉,忙忙碌碌写了一个上午,这是第一次系统记录看过的内容。还有一部分内容是关于错误检查的,但是本章内容我感觉有点太多拉,所以我就把内容放到下一节吧! 

本节内容代码可以在我的Github仓库找到!Github传送门

游戏开发路途遥远,但我相信只要坚持,总能到达彼岸!

如果我的文章对于你学习DirectX11有点帮助,欢迎评论给出建议,让我们一起学习进步!

                                                                                               ———————— 小明 2018.11.15 14.53

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值