本文由哈利_蜘蛛侠原创,转载请注明出处!有问题欢迎联系[email protected]
注:我给的电子版是700多页,而实体书是800多页,所以我在提到相关概念的时候,会使用章节号而非页码。同样的情况适合于“龙书”第二版。
上一期的地址:
DX 11游戏编程学习笔记之4
这一期我们进入本书的第二部分了!第二部分名叫“Direct3D Foundations”(Direct3D基础),包括第4章到第13章,占据了本书的大半了。学好这一部分很重要!其实这也是本书最难的部分,尤其是第4章和第6章。一旦过了第6章,情况就会好起来的(至少我是这么认为的。如果读过“龙书”第二版的话,会发现第6章后面的内容很容易理解)。
===============================================================================
第4章和第6章应该是全书最难的部分了,所以大家一定要仔细看!也许没法一次性理解,所以后面学习的过程中你也许会经常回顾的!所以我也尽量把相应的学习笔记弄详细些,便于大家参考。
这一章是讲DX 11中Direct3D 的初始化的,大致对应于“龙书”第二版的第4章以及第5章关于计时的部分。
本章的学习目标:
1、 To obtain a basic understanding of Direct3D’s role in programming 3D hardware.(对Direct3D在3D硬件编程中的作用有一个基本的理解。)
2、 To understand the role COM plays with Direct3D.(理解COM在Direct3D中扮演的角色。)
3、 To learn fundamental graphics concepts, such as how 2D images are stored, page flipping, depth buffering, and multisampling.(学习基础的图形概念,例如2D图像如何被存储、页面翻转、深度缓存以及多重采样。)
4、 To learn how to use the performance counter functions for obtaining high-resolution timer readings.(学习如何使用性能计数函数来获得高精度的计时器读数。)
5、 To find out how to initialize Direct3D.(知道如何初始化Direct3D。)
6、 To become familiar with the general structure of the application framework that all the demos of this book employ.(熟悉本书所有的示例程序所采用的应用程序框架的基本结构。)
由于DX 11与DX 9之间的巨大区别,所以我在这里不会太经常地提到“龙书”第二版了。而且下面的重点基本上每节都有。
这一节的内容还是挺多的,虽然结构和龙书第二版的差不多,但是很多细节都发生了比较大的变化:
这一小节首先就给我们展示了一个全新的函数(相对于DX 9而言)ID3D11DeviceContext::ClearRenderTargetView。这给了我们当头一棒,让我们知道这今后的路可能不太好走啊!然后值得注意的是,在编写DX 11程序时,我们不再需要检查硬件的性能了,因为DX 11对硬件的性能还是比较高的,如果显卡不满足某个性能,那么便不能够运行DX 11程序!
这一小节和“龙书”第二版的没区别,不过我还是把重点提一下:
1、 我们通过特殊的函数,或者通过另一个COM接口的方法来获得一个指向一个COM接口的指针,而不是使用C++的new关键字;
2、 在使用完毕后,我们需要使用Release方法来释放一个COM接口,而非使用C++的delete关键字;
3、 关于如何识别一个东西是不是COM接口,注意看它的前缀:COM接口名字都以I为前缀。
这一小节是全新的小节,跟“龙书”第二版第1节的surfaces和pixel formats这两小节有点平行关系,但是区别还是蛮大的。
这里的textures实际上和“龙书”第二版中的surfaces是对应的。不过这里的textures所能存储的数据格式与“龙书”第二版中的pixel format 还是不一样的,连前缀都改变了(变成了以DXGI_FORMAT_为前缀)具体的还是参看书本吧!
这一小节与“龙书”第二版的4.2.2节在内容上基本一致,不过这里提到了一个交换链(swap chain)需要用一个IDXGISwapChain 接口来表示,而这个接口提供了ResizeBuffers 以及Present 方法。这是与之前很不相同的。
这一小节和“龙书”第二版的4.2.5 节基本一致。说得挺多的,其实思想很简单。不过在跟代码相关的部分,还是有值得注意的地方的。在接近结尾的地方,提到The depth buffer is a texture. 看来DX 11丰富了texture 的涵义呀!
这又是全新的一小节,所以某些同学不要徒劳地试图在“龙书”第二版中找到“原型”了。这一小节提出了两个新的概念:一个是bind flag,另一个是resource view。这里比较重要的一句话是:
For each way we are going to use a texture, Direct3D requires that we create a resource view of that texture at initialization time.
这一小节也是比较新的,虽然“龙书”第二版也讲了多重采样(multisampling),但是点到为止;而这里讲述得比较详细,还将supersampling 和multisampling 进行了对比,让我们更加深刻地理解(尽管不是很好懂)。
这一小节也算是全新的,告诉我们需要填充一个描述multisampling 属性的DXGI_SAMPLE_DESC 结构体以初始化Direct3D。
还是全新的一小节。这一小节告诉我们,我们可以通过使用feature levels 来测试用户的电脑显卡水平,从高到低选择第一个被用户电脑显卡支持的Direct3D版本。不过在此书中我们一直假定用户的电脑是支持DX 11的。
这一节比较冗长,讲述了初始化Direct3D的八大步骤:
1、 使用D3D11CreateDevice函数来创建ID3D11Device和ID3D11DeviceContext接口。
2、 使用ID3D11Device::CheckMultisampleQualityLevels 方法来检查4X MSAA quality level支持。
3、 通过填充一个DXGI_SWAP_CHAIN_DESC 的实例来描述我们要创建的交换链的特征。
4、 Query 用于创建设备的IDXGIFactory实例,并创建一个IDXGISwapChain实例。
5、 创建交换链的后台缓存的rendertarget view。
6、 创建depth/stencil缓存及其相关的depth/stencilview。
7、 将render targetview 和depth/stencil view绑定到渲染管线的output merger stage上,以便它们可以被Direct3D所使用。
8、 设置视口。
这些跟DX 9.0c 的区别是相当大的,甚至是很简单的设置视口(set the viewport)的部分也进行了变动,可以让你一次性设置一个视口数组。由于这部分的内容与DX 9.0c 的变化太大,所以需要仔细认真的研究,基本上全都是重点,这里就不赘述了。
这一节相当于“龙书”第二版的第5章第一节的升级版,因为这里设计了一个类来处理计时,功能还算完善。这里面没有什么难点,读起来也比较轻松愉快。
这一节讲述的是本书的所有代码(除了前面几章)所用到的Common 文件夹内的d3dUtil.h, d3dApph.h 和d3dApp.cpp 文件里的内容。如果你看了里面的代码的话,你会看到里面包含的文件远远不止这几个,而其他的那些文件会随着学习的深入而分别讲述到。
这一节的内容并不难,但是代码还是有点小复杂的,需要花点时间来研究。当你研究透了这些文件内的代码后,基本上就掌握了这一章的知识了!当然,这里面包含了一些以后才讲到的东西,比如说d3dUtil.h 中除了一开始的跟debug 有关的代码和稍后的宏定义以外,基本上就是跟后面的内容相关的了;暂时先无视吧!
当然,本节最后还讲到了Demo 文件的代码,这当然也是必需的。
细心的同学会发现:这本书没有讲Direct Input 部分!这本书中都是通过鼠标来进行输入的,而且使用的是Windows 消息处理函数。当然,这样的效率是比较低下的,而且用起来不太方便(更何况没有添加对键盘输入的支持;当然你可以继续添加几个响应键盘输入的函数,不过那个太麻烦了)。所以我把龙书第二版的关于Direct Input 的代码加进了Common文件夹,使得我们可以在主程序中轻松地对键盘和鼠标输入进行响应(当然Common文件夹中其他几个函数也会进行少许的变动;另外主程序的WinMain 函数还需要实例化一个DirectInput 类)。这种更新后的Common 文件夹,跟原版的Common 文件夹一起,会在本期最后给出下载地址。当然,这个更新后的Common 文件夹还有一些变动,不过现在说起来为时尚早,等到第6章的时候我再进行说明。
这一节跟龙书第二版的4.7 节内容很像,不过讲的内容少了,没有讲enable Direct3D debugging的部分(这一部分可以参考本人更新后的“龙书”第二版)。这里也没有什么可讲的东西。
===============================================================================
===============================================================================
这一章很难得得没有什么错误,只是图4.11 的位置太靠后了……
===============================================================================
===============================================================================
本来习题解答应该发上代码的下载地址的,但是这一章的习题比较特殊,因为涉及对Common 文件夹内部的代码的修改。所以我只能够把改动的部分发上来给大家看了。另外提醒大家注意,为了防止将代码改动得一团糟,最好先将Common 文件夹进行备份!
习题1:
这道题很简单,实际上就是让我们来研究IDXGIFactory::MakeWindowAssociation方法。这道题的解决方法其实题目本身已经告诉你了:在d3dApp.cpp 文件的InitDirect3D 成员函数内部的
HR(dxgiFactory->CreateSwapChain(md3dDevice,&sd, &mSwapChain));
语句后紧接着添上以下语句:
HR(dxgiFactory->MakeWindowAssociation(mhMainWnd,DXGI_MWA_NO_WINDOW_CHANGES));
这样你就没法在全屏模式和窗口模式之间进行切换了,一开始是什么模式,就一直是什么模式。当然,你也可以这样写:
HR(dxgiFactory->MakeWindowAssociation(mhMainWnd,DXGI_MWA_NO_ALT_ENTER));
这个语句的作用是防止程序对ALT+ENTER 的消息做出相应。由于程序默认是对ALT+ENTER 的消息产生在全屏模式和窗口模式之间进行切换的效果,所以这也就间接地阻止了对模式的切换;当然这样做的副作用就是少了一个潜在的消息(你可能希望用ALT+ENTER 来实现其他的功能)。
习题2-5:
这四道题是紧密相关的,所以我就放在一起说了。
总体来说,就是在d3dApp.cpp 的bool D3DApp::InitDirect3D() 函数的
HR(dxgiFactory->CreateSwapChain(md3dDevice,&sd, &mSwapChain));
和
ReleaseCOM(dxgiDevice);
语句之间插入下列代码:
// To determine how many adapters are on the system. // ---- Exercise 2 of Chapter 4. UINT numAdapters = 0; IDXGIAdapter * pAdapter; std::vector <IDXGIAdapter*> vAdapters; while (dxgiFactory->EnumAdapters(numAdapters, &pAdapter) != DXGI_ERROR_NOT_FOUND) { vAdapters.push_back(pAdapter); ++numAdapters; } using namespace std; ofstream outFile; outFile.open("disp.txt"); outFile << "*** NUM of ADAPTERS = " << numAdapters << endl; for (int adapterInd = 0; adapterInd < numAdapters; ++adapterInd) { DXGI_ADAPTER_DESC adapterDesc; HR((vAdapters[adapterInd]->GetDesc(&adapterDesc))); outFile << "*** Adapter " << adapterInd << "'s name:" << endl; outFile << "*** " << adapterDesc.Description << endl; // Check to see if the adapter support Direct3D 11. // ---- Exercise 3 of Chapter 4. if (vAdapters[adapterInd]->CheckInterfaceSupport(__uuidof(ID3D11Device), NULL) == S_OK) outFile << "*** D3D11 SUPPORTED FOR ADAPTER " << adapterInd << endl; else if (vAdapters[adapterInd]->CheckInterfaceSupport(__uuidof(ID3D11Device), NULL) == DXGI_ERROR_UNSUPPORTED) outFile << "*** D3D11 NOT SUPPORTED FOR ADAPTER " << adapterInd << endl; // To determine the number of outputs for this adapter. // ---- Exercise 4 of Chapter 4. UINT numOutputs = 0; IDXGIOutput * pOutput; std::vector<IDXGIOutput*> vOutputs; while (vAdapters[adapterInd]->EnumOutputs(numOutputs, &pOutput) != DXGI_ERROR_NOT_FOUND) { vOutputs.push_back(pOutput); ++numOutputs; } outFile << "*** NUM OUTPUTS FOR ADAPTER " << adapterInd << " = " << numOutputs << endl; // For each output, show the width, height, and refresh rate // of each display mode the output supports for the // DXGI_FORMAT_R8G8B8A8_UNORM format using the // IDXGIOutput::GetDisplayModeList method. // ---- Exercise 5 of Chapter 4. for (UINT outputInd = 0; outputInd < numOutputs; ++outputInd) { UINT numModes = 0; DXGI_FORMAT format = DXGI_FORMAT_R8G8B8A8_UNORM; UINT flags = DXGI_ENUM_MODES_INTERLACED; vOutputs[outputInd]->GetDisplayModeList(format, flags, &numModes, 0); DXGI_MODE_DESC * pModeDescs = new DXGI_MODE_DESC[numModes]; vOutputs[outputInd]->GetDisplayModeList(format, flags, &numModes, pModeDescs); for (UINT i = 0; i < numModes; ++i) { outFile << "***WIDTH = " << pModeDescs[i].Width << " HEIGHT = " << pModeDescs[i].Height << " REFRESH = " << pModeDescs[i].RefreshRate.Numerator << "/" << pModeDescs[i].RefreshRate.Denominator << endl; } } ReleaseCOM(pOutput); outFile << "\n\n"; // In order to distinguish different adapters. } outFile.close(); ReleaseCOM(pAdapter);
这代码的结构还算合理吧!我把这代码大致分为4块了,分别对应这四道题。
然后这是本人运行的结果:
书上让我们用OutputDebugString 函数来显示结果。这个函数其实比较好用,只有一个参数;不过问题是你需要用特定的方法才能够看到输出结果。我觉得有点麻烦,便直接输出到一个文本文件中了。
你可以看到:本人的电脑是“不支持”Direct3D 11的。真的是这样的吗?
事实上,如果你运行这个程序的话,也会得到这样的结果。为什么呢?根据MSDN网站上的叙述:
You can use CheckInterfaceSupport onlyto check whether a Direct3D 10.x interface is supported, and only on WindowsVista SP1 and later versions of the operating system. If you try to use CheckInterfaceSupport tocheck whether a Direct3D 11.x and later version interface is supported, CheckInterfaceSupportreturns DXGI_ERROR_UNSUPPORTED. Therefore, do notuse CheckInterfaceSupport. Instead, to verify whether the operating systemsupports a particular interface, try to create the interface. For example, ifyou call the ID3D11Device::CreateBlendState method and it fails,the operating system does not support theID3D11BlendState interface.
所以这个第3题完全是个坑货!另一方面,我们如果能够运行程序的话,那么就说明我们的电脑是支持Direct3D 11 的了,否则的话,根据代码,程序会直接结束的。
另外一个你也许会注意到的问题是:我有两个显卡,但是为何第二个显卡的output 数目是0?其实我也纳闷。我的两个显卡是集成的Intel 显卡和独立的AMD 显卡,都还不错。现在是开着独立显卡的。更加神奇的事情是,如果打开集成显卡的话,那么输出结果只有1个显卡!这让我很郁闷。
还有一件事情就是我想要获得各个显卡的型号名字。结果失败了,输出的是两串一样的字符。在DirectX 9.0c 中,有一个IDirect3D9::GetAdapterIdentifer 方法可以获得显卡的ID,从中可以获得显卡的名字;但是在DirectX 11中没有这样的函数,我也不知道怎么获得显卡的型号……
习题6:
这道题也很简单,我们只需要把d3dApp.cpp文件中的void D3DApp::OnResize() 函数内部结尾处的
mScreenViewport.TopLeftX= 0; mScreenViewport.TopLeftY= 0; mScreenViewport.Width = static_cast<float>(mClientWidth); mScreenViewport.Height = static_cast<float>(mClientHeight); mScreenViewport.MinDepth= 0.0f; mScreenViewport.MaxDepth= 1.0f;
改成
mScreenViewport.TopLeftX= 0.25f*static_cast<float>(mClientWidth); mScreenViewport.TopLeftY= 0.25f*static_cast<float>(mClientWidth); mScreenViewport.Width= 0.5f*static_cast<float>(mClientWidth); mScreenViewport.Height= 0.5f*static_cast<float>(mClientHeight); mScreenViewport.MinDepth= 0.0f; mScreenViewport.MaxDepth= 1.0f;
就行了。需要注意的是:虽然可以让视口的边缘越界(就是视口不完全在主窗口之内),但是不建议这样做,因为你不知道这会导致什么结果。(在DirectX 9.0c 中是不可以让视口的边缘越界的,否则会出现运行时错误!)另外,由于我们目前只能够光秃秃的一个背景色,所以我们看不出来这样设置视口后有什么变化。其实视口外的部分的颜色就是你的背景色,所以如果你的游戏场景中背景没有被物体(比如说地面或者天空)所填充的话,那么也是很难发现区别的。
===============================================================================
下面是原版的Common 文件夹和本人更新后的Common文件夹的下载地址:
原版Common和更新版Common的下载地址
注意:
上述的DirectInput 不支持扫描码大于127的键位,所以当你试图对四个方向键的状态做出响应时,你编译代码的时候会提示警告,并且运行的时候会发生很有趣的事情。之所以会这样,是因为VS 默认的char 是有符号的。要支持一般键盘上的所有按键的状态,只需要把DirectInput.h 和DirectInput.cpp 中出现的所有的char 都改成unsigned char 就可以了。祝使用愉快!
这个是书上的示例程序的原版和更新版的下载地址:
Init Direct3D原版和更新版代码
注意:
所有的代码都需要Common 文件夹的支持,而这些代码的具体使用方法参见代码文件夹内的ReadMe – DX 11.txt 文本文档的描述。这些东西以后都同理,所以以后就不会再赘述了。特别是,我以后各期将只给出非Common 部分的代码,Common 部分的地址只在这里给出一次。不过也不用担心,我每个代码的描述中都会给出Common 文件夹的下载地址的。
最后是第4章到第8章的原版高清彩图,以供参考:
第4-8章的高清彩图