本文由哈利_蜘蛛侠原创,转载请注明出处!有问题请联系[email protected]
上一期我们讲了第1节,但是纯粹是理论。这一节我们要讲理论联系实际了!
原文翻译:
===============================================================================
现在你已经熟悉了绘制3-D图像的基本知识了,那么是时候开始将知识应用于实际了。但是,在你出发之前,你得了解怎么准备图形系统以供你使用。
在本节中,我将向你介绍我在整本书中使用的DirectX Graphics组件,并且如何让图形系统运行起来并准备好绘制。
注意:
===============================================================================
虽然新的图形组件叫做DirectX Graphics,我通常称之为Direct3D,这是因为所有的3-D图形都使用它。
===============================================================================
注意:
===============================================================================
为了在你的项目中开始使用Direct3D,请包含D3D9.H以及链接D3D9.LIB库(实际上二者都是小写)。
===============================================================================
Direct3D将图形功能分割到了好几个不同的COM对象之中。每一个对象都有其自己的目的,例如IDirect3D9对象是用来控制整个的图形系统的,而IDirect3DDevice9对象是用来控制图形是如何渲染到显示屏上的。
在本书中,我只会给你们展示列在表2.1中的对象;这些对象是你在你的游戏开发项目中最有可能用到的。
表2.1 主要的Direct3D组件
组件 |
描述 |
IDirect3D9 |
使用该对象来收集图形硬件的信息,并建立设备接口。 |
IDirect3DDevice9 |
直接与3-D硬件打交道。使用它,你可以渲染图形、操控图像资源、创建并设定渲染状态和着色过滤器(shade filters),等等等等。 |
IDirect3DVertexBuffer9 |
包含了一个顶点信息的数组,用于绘制多边形。 |
IDirect3DTexture9 |
使用该对象来存储所有用于绘制3-D(以及2-D)图形的面的图像。 |
注意:
===============================================================================
虽然还有另外一些Direct3D组件可以处理,但是它们对于此书而言已经超出范围了(并且是无用的)。你可以查找DirectX SDK文档(对于DirectX June 2010来说,是windows_graphics这个.chm格式文件)来获取关于这些另外的对象的更多信息。
===============================================================================
开始使用图形系统是很简单的工作,这得归功于Direct3D的简化。下面是建立以及运行图形系统通常所需的四个步骤:
1、 获取Direct3D的一个接口;
2、 选择一个显示模式(display mode);
3、 设定显示方法(presentation method)。
4、 创建设备接口并初始化显示屏。
这真是一张简单明了的列表!我说过了,建立并运行图形系统是一件简单的事情,那么我们就继续来研究如何处理每一步吧!
使用图形的第一步是初始化一个IDirect3D9对象。Direct3DCreate9函数为你做到这件事。
IDirect3D9 *Direct3DCreate9( UINT SDKVersion); //D3D_SDK_VERSION
注意:
===============================================================================
绝大多数DirectX函数(以及所有的COM对象的函数)返回一个HRESULT值。时不时地,你会发现一些函数(例如Direct3DCreate9)返回一个非HRESULT值,所以要仔细观察!
===============================================================================
这个函数的唯一一个参数应该是D3D_SDK_VERSION,它表示你正在使用的SDK的版本。该函数的返回值是一个指向你所需要的新创建的IDirec3D9对象的指针,或者如果在创建Direct3D接口的时候发生错误的话,那么返回值是NULL。
使用这个函数就两步,第一步是实例化一个IDirec3D9对象,第二步是调用这个创建函数:
IDirect3D9 g_D3D; // global IDirect3D9 object if((g_D3D =Direct3DCreate9(D3D_SDK_VERSION)) == NULL){ //Error occurred }
在IDirect3D9对象创建完毕之后,你可以开始向它询问关于图形系统的信息了,包括Direct3D可以操控的显示模式。事实上,你还可以向IDirect3D9对象询问当前的显示模式(如果你想使用那种格式的话)。
显示模式范围其维数(以像素数表示的宽度和高度)、颜色深度(可以显示的颜色数目)以及刷新率。例如,你也许像使用640×480的分辨率、16位的颜色深度以及适配器默认的刷新率。
该显示模式信息储存在一个D3DDISPLAYMODE结构体之中:
typedef struct _D3DDISPLAYMODE { UINT Width; //Screen width in pixels UINT Height; // Screen height in pixels UINT RefreshRate; // Refresh rate (0=default) D3DFORMAT Format; // Color format } D3DDISPLAYMODE;
你可以领会宽度、高度以及刷新率,但是颜色格式呢?在图形中,你通常可以选择每个像素为了存储颜色信息而使用的位数(16位,24位或32位)。你使用的位数越多,你就可以显示出更多的颜色(并且耗费的内存也就越多)。
你通常用每一个颜色成分(红、绿、蓝,有时候还有alpha)所占据的位数来指代颜色模式。例如,如果我想要一个16位的颜色模式——红色占5位,绿色占5位,蓝色占5位,还有1位来储存alpha值。每个颜色成分使用5位的位置可以使用32种明暗值(shades)。alpha值只有1位,以为着它要么是开的,要么是关闭的。
当你称呼颜色模式的时候,你并不说16位之类的话,而是说每一个颜色成分所占的位数,例如1555(1位alpha,5位红色,5位绿色,5位蓝色)。标准的颜色模式为555(5位红色,5位绿色,5位蓝色),565(5位红色,6位绿色,5位蓝色)和888(每个颜色成分占8位)。注意alpha值有时候是不需要的。
Direct3D将这些颜色模式定义为枚举类型的值,你可以在表2.2中看到这一点。现在,比如说你想设定显示模式是640×480并且使用D3DFMT_R5G6B5颜色格式。以下是你设定D3DDISPLAYMODE结构体的模式:
D3DDISPLAYMODE d3ddm; d3ddm.Width = 640; d3ddm.Height = 480; d3ddm.RefreshRate = 0; // use default d3ddm.Format = D3DFMT_R5G6B5;
为了检查显示适配器是否可以操控你需要的颜色格式,用需要的信息填充D3DDISPLAYFORMAT结构体,然后调用以下函数:
// g_pD3D = pre-initialized Direct3D object // d3df = pre-initialized D3DFORMAT structure // Check if display mode exists if(FAILED(g_pD3D->CheckDeviceType(D3DADAPTER_DEFAULT,\ D3DDEVTYPE_HAL, &d3df, &d3df, FALSE))) { //Error occurred - color mode not supported }
设定显示模式假定你在使用全屏模式。如果相反,你想支持窗口模式(例如标准的Windows应用程序),那就让Direct3D为你填充显示模式信息。你用以下的函数调用来完成这一点:
// g_pD3D = pre-initialized Direct3D object D3DDISPLAYMODE d3ddm; if(FAILED(g_pD3D->GetDisplayMode(D3DADAPTER_DEFAULT,&d3ddm))) { //Error occurred }
注意:
==============================================================================
正如所有的COM接口一样,Direct3D返回一个HRESULT值。返回值为D3D_OK意味着函数调用是成功的;其他任何返回值意味着调用失败。你可以简单地使用标准的FAILED或者SUCCEEDED宏来测试返回的代码。
==============================================================================
如果成功的话,前面对IDirect3D9::GetDisplayMode的调用会返回一个有效的D3DDISPLAYMODE结构体。
当心:
==============================================================================
某些显示适配器不能够使用特定的显示模式。确定一个适配器能否支持各种模式是你的工作。如果你在使用窗口模式,这不是一个大问题,因为Direct3D会为你处理颜色模式设定。
==============================================================================
建立Direct3D的下一步是确定如何向用户显示图形。你是想在窗口中、整个屏幕上还是在后台缓冲区(backbuffer)(更多有关后台缓冲区的内容,参见下一个注意事项)中显示图形呢?你想使用什么样的刷新频率呢?所有这些信息(还有更多,正如你将会看到的)被储存在一个D3DPRESENT_PARAMETERS结构体中:
typedef struct_D3DPRESENT_PARAMETERS { UINT BackBufferWidth; // Width of backbuffer UINT BackBufferHeight; // Height ofbackbuffer D3DFORMAT BackBufferFormat; // Same as display mode format UINT BackBufferCount; // 1 D3DMULTISAMPLE_TYPE MultiSampleType; // 0 D3DSWAPEFFECT SwapEffect; // how to display backbuffer HWND hDeviceWindow; // NULL BOOL Windowed; // TRUE for windowed mode // FALSE for fullscreen mode BOOL EnableAutoDepthStencil; // FALSE D3DFORMAT AutoDepthStencilFormat; // 0 DWORD Flags; // 0 UINT FullScreen_RefreshRateInHz; // 0 UINT PresentationInterval; // 0 }D3DPRESENT_PARAMETERS;
注意
===============================================================================
后台缓冲区是一个离屏的(off-screen)绘图表面(与窗口或者视频屏幕一样大),它接受所有的绘制操作。为了看到绘制在后台缓冲区上的图形,你用一种叫做翻转(flip)的操作,它把后台缓冲区的内容先是到视频屏幕或者窗口上。这个操作显示了平滑的更新——除非你准备好了去显示,否则用户永远不会看到正在被绘制的东西。
你可以在图2.9中看到这一概念,图中显示了前(显示器)屏幕和后(off-screen)屏幕。你在后屏幕上绘制,然后当你绘制完毕时,你将两个屏幕进行翻转,使得后屏幕的内容显示出来。
===============================================================================
虽然这个操作也许看上去很复杂,但是实际上你不需要管D3DPRESENT_PARAMETERS结构体中的大多数成员;然而,你确实需要理解与后台缓冲区有关的成员。
这里有你能够使用的两个可能的设定,使用哪个取决于你想要用窗口模式还是全屏模式(还有一小段演示如何使用两种模式的代码):
// d3ddm = pre-initialized D3DDISPLAYMODE structure D3DPRESENT_PARAMETERS d3dpp; // Clear out the structure ZeroMemory(&d3dpp, sizeof(D3DPRESENT_PARAMETERS)); // For windowed mode, use: d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = d3ddm.Format; // use same color mode // For fullscreen mode, use: d3dpp.Windowed = FALSE; d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP; d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT; d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_DEFAULT; d3dpp.BackBufferFormat = d3ddm.Format; // use same color mode // For both windowed and fullscreen, specify the width and height d3dpp.BackBufferWidth = Width; // Supply your own width d3dpp.BackBufferHeight = Height; // Supply your own height
终于你可以创造Direct3D设备接口了,它正是3-D系统的重体力劳动者。使用你之前建立的D3DDISPLAYMODE和D3DPRESENT_PARAMETERS结构体,你通过调用IDirect3D9::CreateDevice函数来创建并初始化显示接口:
HRESULT IDirect3D9::CreateDevice( UINT Adapter,// D3DADAPTER_DEFAULT D3DDEVTYPE DeviceType, // D3DDEVTYPE_HAL HWND hFocusWindow,// window handle to use for rendering DWORDBehavior Flags, // D3DCREATE_SOFTWARE_VERTEXPROCESSING D3DPRESENT_PARAMETERS *pPresentationParameters, // d3dpp IDirect3DDevice9 *ppReturnedDeviceInterface); // device object
在CreateDevice函数中,你看到了在哪里传递你创建的presentation结构体,还有属于你的应用程序的窗口(并且Direct3D会使用它来显示渲染后的图形)的句柄。剩下的参数非常标准,你很少需要更改它们。最后的参数是你正在创建的Direct3D设备对象的指针。IDirect3D9::CreateDevice函数的一个调用也许看起来像这样:
// g_pD3D = pre-initialized Direct3D object // hWnd = window handle to use for rendering // d3dpp = pre-initialized presentation structure IDirect3DDevice9* g_pD3DDevice; if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT,\ D3DDEVTYPE_HAL, hWnd, \ D3DCREATE_SOFTWARE_VERTEXPROCESSING, \ &d3dpp, &g_pD3DDevice))) { //Error occurred }
注意:
=============================================================================
Direct3D与两种不同的设备协作——HAL(Hardware Abstraction Layer,硬件抽象层)和REF(REFerence device,引用设备)。HAL设备是你实际的图形卡,并且这是你在开发游戏时几乎总要使用的设备。REF设备(一个软件渲染对象)在你想调试你的应用程序时或者测试一种你目前的图形卡不支持的特性时使用。HAL是二者中较快的,因为它允许Direct3D与你的图形卡的硬件直接工作,而REF则在软件中工作(并且在这方面非常慢)。
=============================================================================
一般来说,设备接口会按照预期的那样工作,一切都很棒;图形被画出来了,并且内存资源被维护着。虽然认为你的设备会一直处于这种操作状态的想法很棒,但是还是有一些时候是做不到的。那好,我们就进入失去设备的世界吧。
一个失去的设备(lost device)是指因为这样或者那样的原因失去了对图形资源的控制的设备。可能是因为另外一个应用程序控制了你的图形适配器并且耗光了保存你的应用程序的图形数据的内存。也可能是Windows在进入睡眠模式的时候降低了系统的动力消耗(powered down the system)。不管是什么原因,你对图形设备的控制失去了,然后你需要把它夺回来。
你如何直到什么时候失去了控制呢?通过检查你调用的任何设备函数的返回值!例如,在本章后面的一节“展示场景”中,你会看到如何将图形先是到视频显示器上。在那个函数调用中,如果设备对象返回一个D3DERR_DEVICELOST的值,你会知道设备失去了。
重新获得设备的控制是很drastic的一步,从某种角度上说。它通过下面的函数而搞定:
HRESULT IDirect3DDevice9::Reset( D3DPRESENT_PARAMETERS *pPresentationParameters);
上述函数的唯一一个参数是拟用于初始化设备用的presentation结构体:
//g_pD3DDevice = pre-initialized device object // d3dpp = pre-setup presentation structure g_pD3DDevice->Reset(&d3dpp);
我本想说这是一个魔法般的函数,因为它在重建设备的时候为你搞定了所有事情,但是我很抱歉要告诉你一些坏消息。调用这个reset函数会重置设备并且清理掉所有的资源——这其实并不是太糟糕,因为有可能它们本来就已经丢失了(因为设备丢失了呀)。
最关键的就是你需要重新载入与图形有关的所有的资源(例如纹理),并且你需要重建设备状态(the settings)。失去的很多东西是你还没有学到的数据,所以我在将来会让你跟上进度的。
处理Direct3D有时候是一件大任务。虽然微软已经简化了许多的接口,但是你还是需要做一些工作的。为了加快项目开发的速度,微软创造了D3DX库。D3DX库充满了处理图形(例如网格、纹理、字体、数学等等)的有用的函数。在这本书中,你会看到如何利用D3DX库来让你的游戏编程冒险变得更加顺利一些。
注意:
===============================================================================
所有的D3DX函数都以D3DX前缀开始(例如,D3DXCreateFont)。D3DX库不仅仅包含函数,还包含COM对象,例如ID3DXBaseMesh。
===============================================================================
注意:
===============================================================================
为了在你的项目中使用D3DX库,你需要包含进D3DX9.H以及链接到D3DX9.LIB(同样,二者实际上是小写的)。并且,你还可能要链接D3DXOF.LIB库文件,它稍后就会有用的。
===============================================================================
===============================================================================
好啦,这个漫长的一节终于讲述完毕了!如果你能够消化的话,那么恭喜你,你已经学会了DirectX编程中最困难的一部分!剩下的都是小case啦!