第三章 DirectX SDK简介
第一节 关于DirectX SDK
Microsoft DirectX提供了一套非常优秀的应用程序接口,包含了设计高性能、实时应用程序的源代码。DirectX技术将帮助您建构下一代的电脑游戏和多媒体应用程序。它的内容包括了DirectDraw、DirectSound、DirectPlay、Direct3D和DirectInput等部分,它们分别主要应用在图形程序、声音程序等方面。
由于DirectX,使在Windows下运行应用程序的性能可以与在DOS或游戏平台下运行的应用程序性能相媲美,甚至超过它们。它将为您的Windows游戏开发提供一个具有鲁棒性的、标准化的操作环境。
DirectX包括两部分:运行期部分(Runtime)和SDK。在DirectX开发时,这两部分都要用到,但在DirectX应用程序运行时只用运行期部分。在Windows NT 4.0及以上版本中含有DirectX运行期部分,Win95则没有。但Win95可以很容易获得DirectX运行期部分。而Windows NT 4.0以前的版本不能运行DirectX程序。许多基于DirectX的应用程序和游戏都包含了DirectX运行期部分。
它目前有五个版本:1、2、3、5和6(没有版本4)。不同版本具有不同的运行期部分,但新版本的运行期部分可与旧版本的应用程序配合,即向上兼容。当前大部分流行的游戏都是基于版本5开发的。
第二节 DirectX5 SDK的获得
DirectX SDK包括开发DirectX应用程序所需要到的全部示例和帮助文件,但这些都是可
选资源,必须的文件是头文件(.h文件)和库文件(.lib文件)。获得DirectX SDK比获得运衅诓糠忠岩恍indows NT 4.0和Win95都不带DirectX SDK,要获得SDK可通过以下3种办法:
* 购买Visual c++5.0(包括DirectX SDK)
* 访问Microsoft Web站点的DirectX下载页
* 成为MSDN(Microsoft开发网络)用户
SDK 也可以从Microsoft Web站点上获得,下载量很大,尤其是在拨号连接时,有可能需要一整夜的时间。
成为MSDN用户是获取SDK的好办法,除非您反对通过Microsoft付费的方式获得升级及其操作系统的程序开发特权。SDK由MSDN level 2及以上提供。
第三节 元件对象模型(COM)
DirectX根据Microsoft的COM(Component Object Model,即元件对象模型)规格得以实现。COM设计成用来提供完全便携的、安全的、可升级的软件结构。COM使用一个面向对象的模型,这要比像C++等语言所用的模型更严格。例如,COM对象经常通过成员函数进行存取,而且并不具备公共数据成员。COM对继承性的支持与C++相比也是有限的。
很多DirectX中API是作为COM对象的实例来创建的。您可以这样看:对象就象一个黑盒子,它代表了硬件,从而需要通过一个接口与应用程序进行联络。通过COM接口发送和接收的命令被称为“方式(method)”。例如,方式IDirectDraw2::GetDisplayMode是通过接口IDirectDraw2发送,从而能够从DirectDraw对象得到当前显示适配器的显示模式。
COM对象提供实际函数性,而COM接口则提供存取函数性的方法。COM对象不能被直接存取,相反,所有的存取者通过接口来完成。这条规则是如此强有力的得到遵守,以致于我们不能给任何COM对象以名称。我们只能给用来存取对象的接口名称。
所有的COM接口都是从IUnknown接口中衍生出来的。“I”标志经常用于命名COM接口(它代表Interface即接口)。
IUnknown接口提供3个成员函数,所有COM接口因而继承这些函数:
●AddRef():使对象内部引用值加一。例如,当您创建一个新的接口时,构造函数将自动调用AddRef()。
●QueryInterface():让COM对象就它们是否支持特定接口进行查询。例如,升级的COM对象提供附加的接口,而非现有接口的修改版。QueryInterface()函数可以用来确定旧的接口,以决定新的接口是否被支持。如果被查询的对象不支持有问题的接口,则更替接口的指针就返回。
●Release():使对象内部引用值减一。您应该在接口的指针将要超出范围时或者要通过使用接口指针来结束时使用这个函数。
对象的每一个DirectX接口都代表了一种设备,例如IDirectDraw2、IDirectSound和IDirectPlay。DirectX对象模型象征性地为每一个设备提供了一个主对象,其它所支持的功能对象由主对象衍生而来。例如,DirectDraw对象代表了显示适配器,您可以由它创建DirectDrawSurface对象来代表显存。同样地,DirectSound对象代表声卡并可以创建DirectSoundBuffer对象代表声卡上的声音资料。
在C程序中可以调用任意的COM接口方式。下面的例子创建一个平面,使用DirectDraw对象的IDirectDraw2::CreateSurface方式:
ret = lpDD->lpVtbl->CreateSurface (lpDD, &ddsd, &lpDDS,
NULL);
这里lpDD表示与新建平面相关的DirectDraw对象。该方式填充一个平面类型的结构(&ddsd)并返回一个指向平面的指针(&lpDDS)。
同样功能用C++的实现为:
ret = lpDD->CreateSurface(&ddsd, &lpDDS, NULL)
第四节 DirectDraw
DirectDraw是DirectX SDK中的一个组件,它允许用户直接地操作显存,支持硬件覆盖、内存与显存反转。DirectDraw在提供这些功能的同时并支持基于Microsoft Windows的应用程序和设备驱动。DirectDraw作为一种软件接口,支持Windows的GDI(图像设备接口),并提供直接操作显示设备的功能。它提供了与设备无关的游戏、Windows子系统等软件开发方式。
它还能够支持许多种显示硬件,包括从简单的SVGA显示器到高级硬件性能(支持图像剪切、延伸和非RGB颜色格式)。接口的设计使您的应用程序能够知道所使用硬件的特性,从而可以支持它们的硬件加速功能。
关于DirectDraw,在以后还将有更加详细的讲解。
第五节 DirectSound
Microsoft DirectSound的API是DirectX平台软件开发工具(SDK)程序员指南的声音组件。DirectSound提供了硬件加速、对声音设备的直接操作。在提供这些功能的同时,它还保持了与当前设备驱动的兼容性。
新版本的DirectSound能够在运行中查询硬件性能以确定最优的操作方式,还具有抓获声音、低延迟混音等功能特性。它的新功能还包括支持属性集(Porperty Sets),从而即使DirectSound不直接支持新硬件的特性,它也能够利用并受益。
DirectSound是通过“硬件抽象层(HAL)”来操作声音硬件的,这是一个由声音设备驱动来实现的接口。HAL提供了以下功能:
要求和释放对声音硬件的控制;
描述声音硬件的特性;
在硬件允许的条件下实现特定的操作;
在硬件不允许的时候报告操作失败。
DirectSound能够自动地利用硬件加速,包括硬件混音和硬件声音缓冲。您的应用程序无须查询硬件和程序特性,而可以在运行期部分查询DirectSound来获得对声音设备特性的充分描述,然后根据各个特性的存在或缺少来使用不同的方法进行优化。您还可以指定接受硬件加速的声音缓冲区。
下图显示了DirectSound与系统中其它声音组件的关系:
关于DirectSound,在以后还将有更加详细的讲解。
第六节 DirectPlay
DirectPlay的主要作用在于使应用程序间通讯方式得以简单化。DirectPlay的技术不仅提供了一个方案,实现了通讯与传送协议及在线服务的无关性,而且还实现了与传输服务器和游戏服务器的无关性。蛞凰诘耐绮恢С帜骋环绞剑珼irectPlay包含了必要的代码来仿效它。
DirectPlay的service provider结构使应用程序与网络隔绝开来。应用程序通过查询DirectPlay来获得所基于网络功能特性(例如延迟、带宽等),从而相应地调整通讯方式。下面的图显示了DirectPlay service provider的结构:
使用DirectPlay的第一步是选择使用哪一种service provider,它决定了将用于通讯的网络或协议。协议可以是遍布Internet的TCP/IP、局域网中的IPX或两台计算机间的连接电缆。
DirectPlay提供了两种很有用的连接管理方式:
IDirectPlay3::EnumConnections列举了应用程序可以获得的所有连接;
IDirectPlay3::InitializeConnection初始化一种特定的连接。
在确定了网络的连接后,为了方便理解下面的内容,先来看看这样几个概念:
首先从session讲起。DirectPlay session是若干台计算机之间的通讯通道。一个应用程序只有在成为了某个session的一部分后才能够开始与其它计算机进行通讯。有两种方法可以实现:列举一个网络中所有存在的session然后加入其中的一个;或新建一个session并等待其它计算机的加入。当应用程序成为session的一部分后,它就可以创建一个player并与session中其它的player相互传递信息。
每一个session中都有一台计算机作为主机。主机是session的所有者并拥有唯一的改动session属性的权力。
下图显示了一个session模型:
Session有两种类型:对等类型和客户/服务器类型。
Player是session中逻辑概念上的对象,能够发送和接收消息。在DirectPlay Session中没有对实体计算机的表示方法。Player可以作为一个本地player(在您自己的计算机上),或作为一个远程player(在其它计算机上)。只有在拥有了至少一个player后,计算机才能够发送和接收消息。每一台计算机梢杂涤胁恢挂桓霰镜豴layer。
Group是在逻辑概念上是一些player的集合。通过创建一个group,一个应用程序可以向它发送消息使其中所有的player都收到。DirectPlay提供了管理group和成员关系的方式。
下图显示了一个session内容的逻辑结构:
假如,一台计算机在完成了连接之后,就可以调用IDirectPlay3::EnumSessions列举所有可以得到的session。然后,IDirectPlay3::Open根据session cache中的内容加入一个session,或新建一个session。
接下来,可以创建player和group。而在游戏中,更多的是通过Message Managment中的各个方式进行频繁地、大量的消息传送。
在不同类型的session中,消息的流向是不同的。在对等类型中,消息的传送是各个player之间直接进行的。对于使用multicast向group中传送消息时,则要在group中指定一个multicast server,通过它再将消息传送给group中其它的计算机。
在客户/服务器类型的session中,所有的player只与服务器进行直接通讯,再由它与其它各个客户机进行消息传送。下图表示了这样的关系:
第七节 DirectInput
DirectInput用以支持包括鼠标、键盘和游戏杆等在内的输入设备,甚至还有力度反馈的高级输入/输出设备。它也是基于COM的。
DirectInput对象由输入设备提供数据。每一个相应设备都有所谓“对象实例”,即个别的控件,如按键、按纽和游戏杆的轴向等。键盘上的每一个按键都可以作为键盘对象的一个对象实例。即使程序在后台运行,DirectInput也能够对输入设备做出响应。
一般来说,先要创建一个DirectInput对象(通过DirectInputCreat方式),表示一个DirectInput子系统。然后,列举出系统中可以得到的输入设备,再为这些个别设备创建DirectInputDevice对象(通过IDirectInput::CreateDevice方式)。这种对象的方式用于获取关于设备的信息,设置它们的属性并从它们那里得到数据。
下面给出一个使用鼠标的例子。您可以在光盘Example目录下的Scrawl.c文件中找到这些代码。
首先创建DirectInput鼠标设备
// LPDIRECTINPUT g_pdi; // 已被初始化
LPDIRECTINPUTDEVICE g_pMouse;
HRESULT hr;
hr = g_pdi->CreateDevice(GUID_SysMouse, &g_pMouse, NULL);
if (FAILED(hr)) {
Complain(hwnd, hr, "CreateDevice(SysMouse)");
return FALSE;
}
CreateDevice方式有三个参数:第一个是全局独有标志符GUID_SysMouse,这里表明为鼠标;第二个是IDirectInputDevice接口指针类型,如果这个调用成功,它就指向可用的鼠标;一般不使用COM聚合体,第三个参数为NULL。
然后,设置鼠标信息的格式:
hr = g_pMouse->SetDataFormat(&c_dfDIMouse);
if (FAILED(hr)) {
Complain(hwnd, hr, "SetDataFormat(SysMouse, dfDIMouse)");
return FALSE;
}
设置鼠标行为:
hr = g_pMouse->SetCooperativeLevel(hwnd,
DISCL_EXCLUSIVE | DISCL_FOREGROUND);
file://后面的两个标志符实现了前台运行时对输入设备的独占
if (FAILED(hr)) {
Complain(hwnd, hr, "SetCooperativeLevel(SysMouse)");
return FALSE;
}
要使用事件通告(event notification)来发觉鼠标的动作并将经过缓冲的鼠标输入读进来,这都需要进行一些设置。首先,创建一个事件,将它与鼠标联系起来。当发生硬件中断时,就可以告诉鼠标设备对象有新的数据来到。
// HANDLE g_hevtMouse; // 全局变量
g_hevtMouse = CreateEvent(0, 0, 0, 0);
if (g_hevtMouse == NULL) {
Complain(hwnd, GetLastError(), "CreateEvent");
return FALSE;
}
hr = g_pMouse->SetEventNotification(g_hevtMouse);
if (FAILED(hr)) {
Complain(hwnd, hr, "SetEventNotification(SysMouse)");
return FALSE;
}
现在您需要设定缓冲区大小。在此之前要初始化一个DIPROPDWORD结构,这当中很多数量是无关紧要的,关键是最后一个,dwData,它决定了缓冲区中可容纳项目的数量。
#define DINPUT_BUFFERSIZE 16
DIPROPDWORD dipdw =
{
// the header
{
sizeof(DIPROPDWORD), // diph.dwSize
sizeof(DIPROPHEADER), // diph.dwHeaderSize
0, // diph.dwObj
DIPH_DEVICE, // diph.dwHow
},
// the data
DINPUT_BUFFERSIZE, // dwData
};
接着按照您希望改变的属性的标志符,将头地址传送给IDirectInputDevice::SetProperty方式:
hr = g_pMouse->SetProperty(DIPROP_BUFFERSIZE, &dipdw.diph);
if (FAILED(hr)) {
Complain(hwnd, hr, "Set buffer size(SysMouse)");
return FALSE;
}
至此,设置结束。然后开始根据程序的要求操作鼠标。
第八节 Direct3D
Direct3D提供了与硬件无关的的3D视频硬件操作方式。您可以在两种模式(立即模式和保留模式)中选择其中之一使用。
立即模式较保留模式是一个低层的3D API,后者建构于前者之上。立即模式适合于开发高品质的多媒体应用程序,它在底层与加速硬件进行通讯。保留模式应用于对3D场景进行实时的操纵。
对3D场景的操作是基于矩阵和多边形的运算,这是创建和管理3D的基础知识,在很多资料中均可获得。
使用立即模式可以选择通过调用DrawPrimitive方式或利用执行缓冲区。对于初学者一般从调用DrawPrimitive方式。不过,千万不要认为两者有优劣之分。在熟悉了Direct3D后,使用哪个方法取决于您应用程序的要求。前者的方法与其它COM雷同;在利用执行缓冲区的方法时,要创建DirectDraw和Direct3D对象,设置渲染状态,填充执行缓冲区等。
无论哪种模式,应用程序与硬件的通讯都很类似。正如下图所示。由于Direct3D相当于DirectDraw对象的一个接口,这里的HAL被表示为DirectDraw/Direct3D HAL。
对保留模式的操作是通过使用一些对象来实现的。Direct3D和DirectDraw是紧密联系在一起的。一个DirectDraw对象将DirectDraw和Direct3D状态封装起来,通过IDirectDraw::QueryInterface方式将IDirect3D接口转换为DirectDraw对象。
有一个很重要的概念是z缓冲区。它决定了将很多显示内容如何覆盖和裁剪。如果没有z缓冲区,保留模式无法给覆盖层排序。没有指定z顺序的覆盖层被缺省设定为0,处于最底层。一共可以有四十亿个覆盖层(应该够用了吧!),z顺序为2的层将可能遮掩了z顺序为1的层的某些内容。记住,不能有两个层的z顺序相同。
关于3D场景,还有诸如材资、光源等概念,在此不再一一聱述。
第九节 Vc++中引入Direct SDK
一旦安装了SDK,就得马上通知Visual C++ SDK的位置。默认状态下,SDK安装在dxsdk目录下。头文件放在dxsdk/inc目录下,库文件放在dxsdk/lib目录下。
可利用下述两种方法之一通知visual C++ SDK的位置。一种方法是在使用文件时给出完整的文件路径;另一种法是将这些目录加到Visual C++的搜索路径中。第二种方法更好一些,可以通过Tools[Options]Directories对话框实现。
增加dxsdk/lib目录的方法大体上同增加dxsdk/inc目录的方法相同。
如果你获得了一个含有比Visual C++的DirectX SDK新的版本,您需要将DirectX SDK目录置于常规的Visual C++目录上面。否则就得使用旧版本。(Visual C++从上至下查找目录)
根据我们已经讨论过的内容,你应该能够编辑DirectX程序了。然而还有最后一个潜在的障碍。除非INITGUID符号已经被定义,否则在DirectX GUIDs下调用Query-Interface()函数的程序同DirectX2 SDK的链接会失败。INITGUID符号只能由一个源文件定义,并且必须出现在#include语句之前,如下列代码所示:
#define INITGUID
#include〈ddraw.h〉
file://…other includes…
对于DirectX3及以上版本,这种解决方法都是有效的,但还有一个更好的方法,即将dxguid.lib文件链接到你的工程上(Build[Settings]Link对话框),以替代INITGUID符号的定义。