首先要涉及到的是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