本系列文章由zhmxy555(毛星云)编写,转载请注明出处。
文章链接:http://blog.csdn.net/zhmxy555/article/details/8197792
作者:毛星云(浅墨)邮箱:[email protected]
这篇文章里,我们将迈出精通DirectX的第一步,先了解典型Direct3D程序的书写流程,然后学习COM接口的对象的一些思想,然后按照“四步曲”的思路,系统地学习DirectX的初始化方法,且文章最后进行了相关源代码的赏析以及源代码的下载。看完这篇文章,也许你会豁然开朗,哦,原来Direct3D程序的结构是这样组成的,原来COM接口并没有那么神秘,原来DirectX的初始化是这么清晰简单。
由于文章比较长,浅墨在这里给大家配一个目录。这篇文章大体分为以下五个部分:
一、典型Direct3D程序的流程分析
二、关于COM接口对象的一些介绍
三、DirectX初始化四步曲
Ⅰ. Direct3D初始化四步曲概述
Ⅱ.Direct3D初始化四步曲之一:创接口
Ⅲ.Direct3D初始化四步曲之二:取信息
Ⅳ.Direct3D初始化四步曲之三:填内容
Ⅴ.Direct3D初始化四步曲之四:创设备
Ⅵ.DirectX初始化四步曲代码赏析
四、关于库文件的添加
五、详细注释的源代码欣赏
3D游戏编程其实就是渲染的艺术。为了学好DirectX 3D游戏编程,Direct3D自然是需要首先精通的API,后面我们讲解DirectX,会把80%的篇幅集中在讲解Direct3D之上。
我们其实不用把3D编程想象得多么神秘,说白了,3D编程就像一只纸老虎。就像抓葡萄要抓葡萄架子,只要我们在学习过程中抓住了学习对象的关键“命门”,学习起来也就事半功倍了。
下面就来看看我们如何抓住葡萄架子,如何抓住学习的关键点。
正所谓万剑归宗,首先我们来进入第一个部分,介绍一下Direct3D应用程序的一般框架思路。
一、典型Direct3D程序流程分析
首先我们来看一张典型的Direct3D程序框架图
从上面这幅框架图中我们可以发现,Direct3D程序的基本结构是非常简单清晰的,主要可以分为下面5个部分:
1.创建一个Windows窗口。
2. Direct3D的初始化。
3.消息循环
4.渲染图形
5.结束应用程序,清除在初始化阶段所创建的COM对象,并退出程序
我们知道,DirectX的程序开发就是一个基于SDK开发包的过程。Direct3D程序创建过程大体上与普通Win32窗口程序的创建过程没有太大的的区别,首先需要创建一个具有主窗口的应用程序,并且在显示和更新窗口之前初始化Direct3D,然后在消息循环中不断对3D场景进行绘制,机制也都是消息循环的那一套机制。
其中消息循环和Direct3D的绘制过程是不断在进行的,如果程序中有消息需要处理的话,先处理消息,再进行Direct3D绘制过程。如果没有消息要处理的话,我们的程序就会不停地渲染图形,直到退出Direct3D程序。
我们可以简单地这样理解,Direct3D程序就是在普通Win32应用程序的基础上,加上了Direct3D的初始化过程和Direct3D的绘制过程。浅墨在配图过程中,专门把Direct3D独有的部分用虚线和红色的配色与蓝色的Win32部分区分开来,这样更便于理解。然后我们可以这样理解这幅Direct3D程序流程图,蓝色部分为普通Wn32窗口所拥有的常规步骤,而红色的部分为Direct3D应用程序特有的步骤。上面已经提到过,Direct3D特有的部分分为Direct3D初始化与Direct3D的绘制,本篇文章着重介绍Direct3D初始化,而Direct3D的绘制过程将在下一篇里面详细介绍。当然,还有光照、矩阵与视角变换等等额外的初始化过程,我们可以把它们都理解为在Direct3D初始化阶段完成,在这里先不涉及,后面我们会各个击破。
二、关于COM接口对象的一些介绍
在笔记三十二里面我们已经提到了COM接口,我们知道C++与COM接口配DirectX,是目前99%大型游戏采用的开发方案。在这里浅墨希望通过这一小节的介绍,我们能对COM接口有一个更深刻的印象。
COM(Component Object Model,组件对象模型)是一项能够使DirectX独立于编程语言并具备向下兼容的技术。我们常称COM对象为接口,可将其视为一个C++类来使用。在以C++编程语言和COM接口方式开发的DirectX应用程序中,可以直接访问COM接口和对象。
COM接口对象是一组特定功能的抽象集合,应用程序不能直接访问COM接口对象,而是必须通过COM接口对象的接口(interface)的指针来执行COM接口对象的功能。
COM接口对象为我们定义了可供程序调用的一组函数(或者说是方法),而接口是包含了函数指针数组的内存结构,其中每一个数组元素包含的是一个由组件所实现的函数地址,使用方法方面,类似于C++类的指针。
另外,使用COM接口的时候千万要遗忘C++中使用指针的new和delete这一套,他们和COM接口是八竿子打不着边的~因为COM接口对象与C++类的生命周期还是有很大区别的。C++类由new和delete运算符控制类对象的生命周期,而COM接口对象则通过控制对某对象的引用计数个数来决定其生命周期的。COM接口对象的引用计数器会记录某对象当前被引用的个数。这样说大家也许会有点不太好理解,举个例子吧,比如我们现在创建了一个COM接口对象,比如就是这篇文章后面会介绍到的Direct3D初始化四步曲的第一步里初始化IDirect3D9接口对象的一段代码。
LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 return E_FAIL;
当一个COM接口对象被创建时,其引用计数将为1,以后每当程序又创建了这个COM接口的新对象时,其引用计数就会加1.当程序对该对象引用结束时,我们就需要调用该对象的Release方法释放这个接口,而引用计数将减1。我们需要注意的是,即使引用计数减了1,但是还是不为0的话,这个对象所占用的内存还是没有被释放的(释放的只是这个COM接口的对象的引用,而不是这个COM接口本身)。只有当该对象的引用计数减为0的时候,也就是说所有该对象的引用都已经得到释放后,才会去释放接口的COM接口对象所占用的内存。且这个内存的释放是COM接口对象自行经营的,COM接口自有它自己的一套内存管理理念,比较智能,我们只需要自行释放COM接口对象的引用,让其引用计数为0即可(也就是养成好习惯用好Release方法就行了),COM接口对象的内存释放细节并不要我们去插手。
所以,每当程序中创建或者获得某个COM接口对象后,,我们先开开心心地使用这个接口对象,不过在使用完成之后,我们需要在适当的位置调用它们的Release方法来释放这个接口,让引用计数器减1直到为0,以便在程序退出时能够释放它们所占的内存。
下面这段代码就是与上面的代码段搭配使用的,用于释放COM接口的一段代码。
#define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //自定义一个SAFE_RELEASE()宏,便于资源的释放 SAFE_RELEASE(g_pD3D)
在心中理解上面介绍的这些COM接口相关的知识,对付正确使用DirectX已经完全足够了。如果要详细介绍COM对象的细节,需要几本书的容量,浅墨在这里就不多赘言了。
关于更多COM组件对象模型的介绍,《 COM 技术内幕》不失为一本好书,对这方面知识有兴趣的朋友们,可以去看看。
三、Direct3D初始化四步曲
这个部分就是这篇文章的正餐了,看完这部分之后我们应当对如何初始化Direc3D有一个比较深刻的认识。
这里浅墨自己总结,并引入了 “四步曲”的理念,这样我们在对Direct3D的初始化就有章可循,理解和记忆起来就很容易了。
下面正式开始。
Ⅰ. Direct3D初始化四步曲概述
首先介绍一下Direct3D初始化四步曲的组成:
1. Direct3D初始化四步曲之一:创建Direct3D接口对象。(简称:创接口)
2. Direct3D初始化四步曲之二:获取设备硬件信息。(简称:取信息)
3. Direct3D初始化四步曲之三:填充D3DPRESENT_PARAMETERS结构体。(简称:填内容)
4. Direct3D初始化四步曲之四:创建Direct3D设备接口。(简称:创设备)
所以,Direct3D初始化四步曲,也就简明扼要12个字:创接口,取信息,填内容,创设备。
Ⅱ.Direct3D初始化四步曲之一:创接口
Direct3D初始化四步曲之一,简称创接口,也就是创建Direct3D接口对象。
因为我们咬定了采用C++配合COM接口的方式来进行游戏开发,所以初始化的第一步就开门见山,与COM接口相关。
在四步曲的第一步里我们创建一个IDirect3D接口对象,这步操作主要是为后面的初始化四步曲的第二、三、四步做铺垫,因为Direct3D接口对象在手,才能以此作为媒介,完成第二步的设备硬件信息的获取,以及第四步的Direct3D设备接口的创建。
下面就开始看这一步的具体思路,首先我们定义一个LPDIRECT3D9类型的指针pD3D:
LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建
然后将该对象初始化,这时我们会用到一个叫做Direct3DCreate9的函数,下面先介绍一下这个函数:
我们可以在DirectX SDK中查到Direct3DCreate9函数的原型是这样声明的:
IDirect3D9 * Direct3DCreate9( UINT SDKVersion );
这个函数作用是返回指向IDirect3D9接口的指针,并进行DirectX的版本协商。它有一个唯一的参数,UINT类型的SDKVersion,表示当前使用的DirectX SDK的版本,用于确保我们的应用程序所包含的所有头文件在编译时能够与DirectX运行时的DLL相匹配。
如果Direct3DCreate9函数执行失败的话,就会返回NULL,表示在程序中包含的头文件的版本与运行时的DLL版本不匹配。
所以,Direct3D初始化四步曲的第一步整体来看,我们就可以这样写:
//-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象 //-------------------------------------------------------------------------------------- LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 return E_FAIL;
看到这里,大家也许会觉得累了,那么就让我们来看两幅炫酷的游戏图片,来自EA的游戏作品——《极品飞车》
学习游戏编程的路上,这还只是一小步,为了能自己编出像《极品飞车》这样华丽的游戏作品,DirectX我们非拿下不可!
好了,鼓鼓劲,下面继续来学习。
Ⅲ.Direct3D初始化四步曲之二:取信息
Direct3D初始化四步曲之二,简称取信息,也就是获取设备的硬件信息。
获取硬件设备信息包括取得系统中所有可用的显卡的性能、显示模式、格式以及其他相关信息。我们平常比较关心的是我们的显卡是否支持硬件顶点运算,因为这种方式运行起来比较高效。相比而言,软件顶点运算方式就只能自叹不如,两行清泪了。
在DirectX 9中, IDirect3D9接口为我们提供了GetDeviceCaps方法,获取指定设备的性能参数,这个方法会把所获取的硬件设备的信息保存到一个叫D3DCAPS9的预设好的结构体当中。下面我们就来看看这个GetDeviceCaps的具体使用方法,可以在DirectX SDK中查到GetDeviceCaps方法的声明如下:
HRESULT GetDeviceCaps( [in] UINT Adapter, [in] D3DDEVTYPE DeviceType, [out] D3DCAPS9 *pCaps );
◆第一个参数,UINT类型的Adapter,表示使用的显卡的序号,通常我们都使用其默认值D3DADAPTER_DEFAULT,表示当前使用的显卡。
◆ 第二个参数,D3DDEVTYPE类型的 DeviceType,表示设备的类型,取值为D3DDEVTYPE结构体的某一成员,D3DDEVTYPE结构体声明如下:
typedef enum D3DDEVTYPE { D3DDEVTYPE_HAL = 1, D3DDEVTYPE_NULLREF = 4, D3DDEVTYPE_REF = 2, D3DDEVTYPE_SW = 3, D3DDEVTYPE_FORCE_DWORD = 0xffffffff } D3DDEVTYPE, *LPD3DDEVTYPE;
一般我们只会涉及到硬件设备类型D3DDEVTYPE_HAL与软件设备类型D3DDEVTYPE_REF,了解这两个一般就足够了。
◆第三个参数,D3DCAPS9 类型的*pCaps,我们可以看到它为一个指针,指向一个前面提到过的用于接收包含设备信息的D3DCAPS9结构体的指针。
我们知道,顶点是3D图形学中的基本元素,而Direct3D可以有两种不同的顶点运算方式,也就是前面提到过的硬件顶点运算与软件顶点运算。其中,硬件顶点运算作为“高富帅”,得到了显卡的支持,可以使用硬件专有的加速功能,其执行速度将远远快于软件顶点运算方式。
所以我们在编写Direct3D的游戏程序的时候,始终应该优先考虑使用“高富帅”硬件顶点运算方式。但是,或许某些老掉牙的显卡并不支持硬件顶点运算,所以,我们就需要在Direct3D初始化过程中,通过GetDeviceCaps方法检查显卡支持的顶点运算模式.
所以,Direct3D初始化四步曲的第二步整体来看,我们就可以这样写:
//-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息 //-------------------------------------------------------------------------------------- D3DCAPS9 caps; int vp = 0; if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) { return E_FAIL; } if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的 else vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算
其中D3DDEVCAPS_HWTRANSFORMANDLIGHT宏表示显卡可以硬件支持变换和光照。
关于D3DCAPS9结构体,用于描述显卡的所有性能参数,由于里面的内容太多了,而且没有过多介绍的必要性。浅墨在这里就不多做说明了,需要的时候,我们可以在DirectX SDK中进行查阅。
Ⅳ.Direct3D初始化四步曲之三:填内容
Direct3D初始化四步曲之三,简称填内容,也就是填充D3DPRESENT_PARAMETERS结构体。
这一步非常的好理解,其实就是做一个填空题,填充D3DPRESENT_PARAMETERS结构体,完全就是是为后面的第四步创建Direct3D设备接口做准备的。但是为了便于我们的记忆与理解,在这里专门作为一个步骤列举出来。
这个结构体可谓非常的重要,使用Direct3D一般都需要进行这个结构体的填充工作,希望大家仔细理解。其实各成员的功能看一下它的变量名就可以知道,顾名思义,比如BackBufferWidth,按字面意思理解,就是后台缓冲区(BackBuffer)的宽度(Width)。
我们可以在DirectX SDK中查到D3DPRESENT_PARAMETERS的原型声明:
typedef struct D3DPRESENT_PARAMETERS { UINT BackBufferWidth; UINT BackBufferHeight; D3DFORMAT BackBufferFormat; UINT BackBufferCount; D3DMULTISAMPLE_TYPE MultiSampleType; DWORD MultiSampleQuality; D3DSWAPEFFECT SwapEffect; HWND hDeviceWindow; BOOL Windowed; BOOL EnableAutoDepthStencil; D3DFORMAT AutoDepthStencilFormat; DWORD Flags; UINT FullScreen_RefreshRateInHz; UINT PresentationInterval; } D3DPRESENT_PARAMETERS, *LPD3DPRESENT_PARAMETERS;
下面开始无脑的进行成员的分别介绍:
◆UINT类型的BackBufferWidth,指定后台缓冲区的宽度
◆UINT类型的BackBufferHeigh,指定后台缓冲区的高度
◆D3DFORMAT类型的BackBufferFormat,指定后台缓冲区的保存像素格式,可以用D3DFORMAT枚举定义。可以用GetDisplayMode获取当前像素格式。
◆UINT类型的BackBufferCount,指定后台缓冲区的宽度。
◆D3DMULTISAMPLE_TYPE类型的MultiSampleType,表示多重采样的类型。通常我们将MultiSampleType设为D3DMULTISAMPLE_NONE。
◆DWORD类型的MultiSampleQuality,表示多重采样的格式。通常我们将其设为0。
◆D3DSWAPEFFECT类型的SwapEffect,用于指定Direct3D如何将后台缓冲区的内容复制到前台的缓存中,通常我们都将其设为D3DSWAPEFFECT_DISCARD。
◆HWND类型的hDeviceWindow,很显然,就是我们熟知的窗口句柄,这里指定我们需要在哪个窗口上进行绘制。这个参数也可以设为NULL,这时就表示对当前被激活的窗口进行绘制。
◆BOOL类型的Windowed,表示绘制窗体的显示模式,为TRUE使表示使用窗口模式,为FALSE则表示使用全屏模式。
◆BOOL 类型的EnableAutoDepthStencil,表示Direct3D是否为应用程序自动管理深度缓存,这个成员为TRUE的话,表示需要自动管理深度缓存,这时候就需要对下一个成员AutoDepthStencilFormat进行相关像素格式的设置。
◆D3DFORMAT类型的AutoDepthStencilFormat,上面刚介绍过,如果我们把EnableAutoDepthStencil成员设为TRUE的话,在这里就需要指定AutoDepthStencilFormat的深度缓冲的像素格式。具体格式可以在结构体D3DFORMAT中进行选取,因为介绍起来篇幅会非常大,也没有多介绍的必要,这里就不多赘言了。
◆DWORD类型的 Flags,表示附加属性,通常都设为0.
◆UINT类型的FullScreen_RefreshRateInHz,表示在全屏模式时指定的屏幕的刷新率,,在全屏模式时在EnumAdapterModes枚举类型中进行取值,我们在全屏模式时将其设为默认值D3DPRESENT_RATE_DEFAULT,窗口模式时这个成员没有意义,我们把它就设为0了。
◆UINT类型的 PresentationInterval,用于指定指定后台缓冲区与前台缓冲区的最大交换频率,可在D3DPRESENT中进行取值。
好了,终于把这个重要的结构体介绍完了。下面我们来整体看一下如何整体填充这个结构体,也就是我们Direct3D初始化四步曲的第三步所需代码:
//-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体 //-------------------------------------------------------------------------------------- D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.BackBufferWidth = SCREEN_WIDTH; d3dpp.BackBufferHeight = SCREEN_LEIGHT; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = true; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = 0; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
Ⅴ.Direct3D初始化四步曲之四:创设备
Direct3D初始化四步曲之四,简称创设备,也就是Direct3D设备接口的正式创建。
其实就是利用第一步创建的Direct3D接口对象调用一下IDirect3D9::CreateDevice方法,非常简单。下面我们就来介绍一下这个也非常重要的IDirect3D9::CreateDevice方法。
我们可以在DirectX SDK中查到这个方法的原型如下:
HRESULT CreateDevice( [in] UINT Adapter, [in] D3DDEVTYPE DeviceType, [in] HWND hFocusWindow, [in] DWORD BehaviorFlags, [in, out] D3DPRESENT_PARAMETERS *pPresentationParameters, [out, retval] IDirect3DDevice9 **ppReturnedDeviceInterface );
接下来按顺序分别讲解每个成员的具体含义和使用方法。◆UINT类型的Adapter,表示将创建的IDirect3DDevice9接口对象所代表的显卡序号,通常我们使用D3DADAPTER_DEFAULT,或者取0,表示默认的显卡,因为在d3d9.h头文件中定义了这个宏:
#define D3DADAPTER_DEFAULT 0
◆D3DDEVTYPE类型的DeviceType,指定Direct3D的设备类型,前面第二步中讲到过,我们可以在D3DDEVTYPE枚举类型中取值,我们一般取D3DDEVTYPE_HAL,表示硬件设备类型。
◆HWND类型的hFocusWindow,一个窗口句柄,指定当Direct3D程序从前台变换到后台时的提示窗口。在全屏模式运行时,这个窗口必须是最上层显示的窗口,当窗口模式运行时,这个成员可为NULL。为了达到正确的显示效果,我们一般把这个窗口设为和Direct3D初始化四步曲的第三步里面D3DPRESENT_PARAMETERS结构体唯一的窗口句柄成员hDeviceWindow一致。
◆DWORD类型的BehaviorFlags,表示设备行为标识,我们只要知道这个参数可以取D3DCREATE_HARDWARE_VERTEXPROCESSING(硬件顶点运算)或者D3DCREATE_SOFTWARE_VERTEXPROCESSING(软件顶点运算)就可以了。
◆D3DPRESENT_PARAMETERS类型的*pPresentationParameters,在这里填一个已经完成初始化的D3DPRESENT_PARAMETERS类型的结构体,不用多介绍了吧,第三步填充的结构体就是在现在使用的。
◆IDirect3DDevice9类型的**ppReturnedDeviceInterface,即指定我们创建的Direct3D设备接口的指针,可以这样说,调用CreateDevice函数,就是为了得到这个Direct3D设备接口的指针,以完成之后的绘制过程。
介绍完这个非常重要的函数的各个参数,下面我们就来看看到底这个函数在实际情况下应该如何书写,这段代码也就是Direct3D初始化四步曲最后一步的完整代码,其实非常的简短:
if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vp, &d3dpp, &g_pd3dDevice))) return E_FAIL;
在这里我们进行了错误处理,用返回值来判断函数是否调用成功了。因为CreateDevice方法返回一个HRESULT类型的返回值,我们可以通过SUCCESSED和FAIL宏来判断这个函数的执行结果。
Ⅵ.DirectX初始化四步曲代码赏析
通过以上四个步骤的分别介绍,相信大家已经对如何初始化Direct3D有了一个深刻的认识,也就是四步曲,简明扼要,也就是十二个字:
创接口,取信息,填内容,创设备。
然后我们贴出这篇文章在上篇文章配套代码中所添加的代码,主要是加入了DirectX9的库文件,以及Direct3D初始化的相关代码,也就是Direct3D_Init()函数的实现。
//***************************************************************************************** // Desc: 库文件定义部分 //***************************************************************************************** #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") //***************************************************************************************** // Name: Direct3D_Init( ) // Desc: 初始化Direct3D // Point:【Direct3D初始化四部曲】 // 1.初始化四部曲之一,创建Direct3D接口对象 // 2.初始化四部曲之二,获取硬件设备信息 // 3.初始化四部曲之三,填充结构体 // 4.初始化四部曲之四,创建Direct3D设备接口 //***************************************************************************************** HRESULT Direct3D_Init(HWND hwnd) { //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象 //-------------------------------------------------------------------------------------- LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 return E_FAIL; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息 //-------------------------------------------------------------------------------------- D3DCAPS9 caps; int vp = 0; if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) { return E_FAIL; } if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的 else vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算 //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体 //-------------------------------------------------------------------------------------- D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.BackBufferWidth = SCREEN_WIDTH; d3dpp.BackBufferHeight = SCREEN_LEIGHT; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = true; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = 0; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口 //-------------------------------------------------------------------------------------- if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vp, &d3dpp, &g_pd3dDevice))) return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉 return S_OK; }
四、关于库文件的添加
关于库文件的添加,浅墨在这里再讲一下。我们在编写DirectX应用程序时,都需要为项目指定需要链接的DirectX库文件,一些莫名其妙的错误提示,比如“未解析的外部命令”系列错误,都是因为库文件没有添加造成的。
添加库文件一般有两种方法:
第一种方法,在程序代码中通过#pragma语句包含所需的库文件,比如:
#pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib")
第二种方法,通过在项目属性页面进行配置。
具体可以参看游戏开发笔记系列的第二十五篇文章:
【Visual C++】游戏开发笔记二十五 最简化的DirectX 11开发环境的配置
五、详细注释的源代码欣赏
理论部分讲解完成,落到实处还是需要多写代码多练习,下面我们就贴出详细注释的Direct3D初始化四步曲的完整代码:
//***************************************************************************************** // //【Visual C++】游戏开发笔记系列配套源码 三十三 浅墨DirectX提高班之二 化腐朽为神奇:Direct3D初始化四部曲 // VS2010版 // 2012年 11月18日 Create by 浅墨 //图标素材: 古剑奇谭 百里屠苏 //此刻心情:多年以后,当你回忆往昔,唯一让你觉得真实,和骄傲的,是你现在昂首挺胸、用力走过的人生。 // //***************************************************************************************** //***************************************************************************************** // Desc: 头文件定义部分 //***************************************************************************************** #include
//***************************************************************************************** // Desc: 库文件定义部分 //***************************************************************************************** #pragma comment(lib,"d3d9.lib") #pragma comment(lib,"d3dx9.lib") //***************************************************************************************** // Desc: 宏定义部分 //***************************************************************************************** #define SCREEN_WIDTH 800 //为窗口宽度定义的宏,以方便在此处修改窗口宽度 #define SCREEN_LEIGHT 600 //为窗口高度定义的宏,以方便在此处修改窗口高度 #define WINDOW_TITLE L"【Visual C++游戏开发笔记】博文配套demo之三十三 浅墨DirectX提高班之二 化腐朽为神奇:Direct3D初始化四部曲" //为窗口标题定义的宏 #define SAFE_RELEASE(p) { if(p) { (p)->Release(); (p)=NULL; } } //自定义一个SAFE_RELEASE()宏,便于资源的释放 //***************************************************************************************** // Desc: 全局变量声明部分 // //***************************************************************************************** LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; //Direct3D设备对象 //***************************************************************************************** // Desc: 全局函数声明部分 // //***************************************************************************************** LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ); HRESULT Direct3D_Init(HWND hwnd); void Direct3D_Render( ); void Direct3D_CleanUp( ); //***************************************************************************************** // Name: WinMain( ) // Desc: Windows应用程序入口函数 //***************************************************************************************** int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nShowCmd) { //开始设计一个完整的窗口类 WNDCLASSEX wndClass = { 0 }; //用WINDCLASSEX定义了一个窗口类,即用wndClass实例化了WINDCLASSEX,用于之后窗口的各项初始化 wndClass.cbSize = sizeof( WNDCLASSEX ) ; //设置结构体的字节数大小 wndClass.style = CS_HREDRAW | CS_VREDRAW; //设置窗口的样式 wndClass.lpfnWndProc = WndProc; //设置指向窗口过程函数的指针 wndClass.cbClsExtra = 0; wndClass.cbWndExtra = 0; wndClass.hInstance = hInstance; //指定包含窗口过程的程序的实例句柄。 wndClass.hIcon=(HICON)::LoadImage(NULL,L"icon.ico",IMAGE_ICON,0,0,LR_DEFAULTSIZE|LR_LOADFROMFILE); //从全局的::LoadImage函数从本地加载自定义ico图标 wndClass.hCursor = LoadCursor( NULL, IDC_ARROW ); //指定窗口类的光标句柄。 wndClass.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH); //为hbrBackground成员指定一个灰色画刷句柄 wndClass.lpszMenuName = NULL; //用一个以空终止的字符串,指定菜单资源的名字。 wndClass.lpszClassName = L"ForTheDreamOfGameDevelop"; //用一个以空终止的字符串,指定窗口类的名字。 if( !RegisterClassEx( &wndClass ) ) //设计完窗口后,需要对窗口类进行注册,这样才能创建该类型的窗口 return -1; HWND hwnd = CreateWindow( L"ForTheDreamOfGameDevelop",WINDOW_TITLE, //喜闻乐见的创建窗口函数CreateWindow WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, SCREEN_WIDTH, SCREEN_LEIGHT, NULL, NULL, hInstance, NULL ); MoveWindow(hwnd,200,50,SCREEN_WIDTH,SCREEN_LEIGHT,true); //调整窗口显示时的位置,窗口左上角位于屏幕坐标(200,50)处 ShowWindow( hwnd, nShowCmd ); //调用Win32函数ShowWindow来显示窗口 UpdateWindow(hwnd); //对窗口进行更新,就像我们买了新房子要装修一样 //Direct3D资源的初始化,成功或者失败都用messagebox予以显示 if (S_OK==Direct3D_Init (hwnd)) { MessageBox(hwnd, L"Direct3D初始化完成~!", L"浅墨的消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 } else { MessageBox(hwnd, L"Direct3D初始化失败~!", L"浅墨的消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 } //消息循环过程 MSG msg = { 0 }; //初始化msg while( msg.message != WM_QUIT ) //使用while循环 { if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) ) //查看应用程序消息队列,有消息时将队列中的消息派发出去。 { TranslateMessage( &msg ); //将虚拟键消息转换为字符消息 DispatchMessage( &msg ); //该函数分发一个消息给窗口程序。 } else { Direct3D_Render(); } } UnregisterClass(L"ForTheDreamOfGameDevelop", wndClass.hInstance); return 0; } //***************************************************************************************** // Name: WndProc() // Desc: 对窗口消息进行处理 //***************************************************************************************** LRESULT CALLBACK WndProc( HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam ) //窗口过程函数WndProc { switch( message ) //switch语句开始 { case WM_PAINT: // 客户区重绘消息 Direct3D_Render(); //调用Direct3D_Render函数,进行画面的绘制 ValidateRect(hwnd, NULL); // 更新客户区的显示 break; //跳出该switch语句 case WM_KEYDOWN: // 键盘按下消息 if (wParam == VK_ESCAPE) // ESC键 DestroyWindow(hwnd); // 销毁窗口, 并发送一条WM_DESTROY消息 break; case WM_DESTROY: //窗口销毁消息 Direct3D_CleanUp(); //调用Direct3D_CleanUp函数,清理COM接口对象 PostQuitMessage( 0 ); //向系统表明有个线程有终止请求。用来响应WM_DESTROY消息 break; //跳出该switch语句 default: //若上述case条件都不符合,则执行该default语句 return DefWindowProc( hwnd, message, wParam, lParam ); //调用缺省的窗口过程来为应用程序没有处理的窗口消息提供缺省的处理。 } return 0; //正常退出 } //***************************************************************************************** // Name: Direct3D_Init( ) // Desc: 初始化Direct3D // Point:【Direct3D初始化四部曲】 // 1.初始化四部曲之一,创建Direct3D接口对象 // 2.初始化四部曲之二,获取硬件设备信息 // 3.初始化四部曲之三,填充结构体 // 4.初始化四部曲之四,创建Direct3D设备接口 //***************************************************************************************** HRESULT Direct3D_Init(HWND hwnd) { //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之一,创接口】:创建Direct3D接口对象, 以便用该Direct3D对象创建Direct3D设备对象 //-------------------------------------------------------------------------------------- LPDIRECT3D9 pD3D = NULL; //Direct3D接口对象的创建 if( NULL == ( pD3D = Direct3DCreate9( D3D_SDK_VERSION ) ) ) //初始化Direct3D接口对象,并进行DirectX版本协商 return E_FAIL; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之二,取信息】:获取硬件设备信息 //-------------------------------------------------------------------------------------- D3DCAPS9 caps; int vp = 0; if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps ) ) ) { return E_FAIL; } if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT ) vp = D3DCREATE_HARDWARE_VERTEXPROCESSING; //支持硬件顶点运算,我们就采用硬件顶点运算,妥妥的 else vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING; //不支持硬件顶点运算,无奈只好采用软件顶点运算 //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之三,填内容】:填充D3DPRESENT_PARAMETERS结构体 //-------------------------------------------------------------------------------------- D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&d3dpp, sizeof(d3dpp)); d3dpp.BackBufferWidth = SCREEN_WIDTH; d3dpp.BackBufferHeight = SCREEN_LEIGHT; d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8; d3dpp.BackBufferCount = 1; d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; d3dpp.MultiSampleQuality = 0; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.hDeviceWindow = hwnd; d3dpp.Windowed = true; d3dpp.EnableAutoDepthStencil = true; d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; d3dpp.Flags = 0; d3dpp.FullScreen_RefreshRateInHz = 0; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE; //-------------------------------------------------------------------------------------- // 【Direct3D初始化四步曲之四,创设备】:创建Direct3D设备接口 //-------------------------------------------------------------------------------------- if(FAILED(pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hwnd, vp, &d3dpp, &g_pd3dDevice))) return E_FAIL; SAFE_RELEASE(pD3D) //LPDIRECT3D9接口对象的使命完成,我们将其释放掉 return S_OK; } //***************************************************************************************** // Name: Direct3D_Render() // Desc: 使用Direct3D进行渲染 //***************************************************************************************** void Direct3D_Render() { //暂时为空,且听浅墨下回分解 } //***************************************************************************************** // Name: Direct3D_CleanUp() // Desc: 对Direct3D的资源进行清理 //***************************************************************************************** void Direct3D_CleanUp() { //释放Direct3D设备对象 SAFE_RELEASE(g_pd3dDevice) }
其中,为了演示的方便,浅墨把Direct3D的初始化放在了显示和更新窗口之后,并且采用if…else语句配合MessageBox函数,Direct3D调用成功或者失败,都会弹出相应的消息,下面就是这段的实现代码:
//Direct3D资源的初始化,成功或者失败都用messagebox予以显示 if (S_OK==Direct3D_Init (hwnd)) { MessageBox(hwnd, L"Direct3D初始化完成~!", L"浅墨的消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 } else { MessageBox(hwnd, L"Direct3D初始化失败~!", L"浅墨的消息窗口", 0); //使用MessageBox函数,创建一个消息窗口 }
编译并运行上述完整的源代码,我们可以得到如下窗口:
消化完这篇文章里介绍的知识,我们就可以熟练地掌握初始化Direct3D的方法了,就是四步曲,非常的简单,只要记住12个字就OK:
创接口,取信息,填内容,创设备。
文章最后,依旧是放出本篇文章配套源代码的下载:
本节笔记配套源代码请点击这里下载:
【浅墨DirectX提高班】配套源代码之二
其中图标素材使用的是新生代国产武侠单机游戏《古剑奇谭》中的百里屠苏
以上就是本节笔记的全部内容,更多精彩内容,且听下回分解。
浅墨在这里,希望喜欢游戏开发系列文章的朋友们能留下你们的评论,每次浅墨登陆博客看到大家的留言的时候都会非常开心,感觉自己正在传递一种信仰,一种精神。
另外,浅墨刚刚参加了2012年CSDN年度博客之星的评选活动。
在这里,恳请支持浅墨,喜欢游戏开发系列文章的朋友们去投浅墨一票,有了大家的支持,浅墨会更用心地写出更优秀的博客文章来与大家分享,把技术分享这种信仰传递下去。大家的支持就是浅墨继续写下去的动力~~~
如果文章中有什么疏漏的地方,也请大家指正。
文章最后,依然送给大家一句话,以此共勉:
感觉累的时候,也许你正处于人生的上坡路。坚持走下去,你就会发现到达了人生的另一个高度。
下周一,让我们离游戏开发的梦想更进一步。
下周一,游戏开发笔记,我们,不见不散。