详解DX9下3D游戏编程1

Introduction to 3D Game Programming with DirectX 9.0
原著: Frank D. Luna
第二部分 
第一章    D3D初始化
D3D的初始化以前一直被认为是一项乏味琐碎的工作。幸运的是8.0版本简化了初始化模式并且DX9.0也使用和它相同的模式。然而,在这个过程中仍需要我们熟知图形学的基础知识和D3D的基本类型,本章的前几节将讲述这方面的内容。在余下的小节里将解释初始化的过程。
目的:
1.    学习D3D怎样与图形硬件交互作用
2.    弄懂COM在D3D中所扮演的角色
3.    学习基础图形学知识,如2D图片是如何存储的、页面切换和深度缓冲
4.    学习如何初始化D3D
5.    熟悉本书例程中的一些常用的结构

1.1    Direct3D概述
D3D是一种低层图形API,它主要使用3D硬件加速来进行3D世界的渲染。我们可以把D3D看作是应用程序和图形设备之间的中介。例如通知图形设备清空屏幕,应用程序将调用D3D的IDirect3DDevice9::Clear方法。图1.1显示了应用程序、D3D和图形设备之间的关系。

此图片被缩小显示,点击查看大图

图1.1:应用程序、D3D和硬件间的关系。
图中所表示的D3D是已定义的供程序员使用的D3D接口和函数的集合。这些接口和函数代表了当前版本的D3D所支持的全部特性集。注意:仅仅因为D3D支持某种特性,并不意味着你使用的显卡会支持它。
如图1.1所示,在D3D和图形设备之间有一层中介——叫做硬件抽象层(HAL,Hardware Abstraction Layer)。D3D不能直接作用于图形设备,因为现在市面上的显卡种类实在是太多了并且每种显卡都有不同的性能和处理事件的方式。例如,两种不同的显卡实现清屏的方式也可能是不同的。因此,D3D要求设备制造商实现HAL。HAL是一组指示设备执行某种操作的特殊设备代码的集合。用这种方法,D3D避免了必须去了解某个设备的特殊细节,使它能够独立于硬件设备而存在。
设备制造商在HAL中实现他们的产品所支持的所有特性。HAL将不会实现那些D3D支持但硬件产品不支持的特性。调用一个HAL中没有实现的D3D的函数将会出错,除非它是顶点处理操作,因为这个功能可以由软件模拟来实现。因此当使用某些仅由市面上少数显卡所支持的高级特性时,必须检测一下设备是否支持。
1.1.1    The REF Device
你也许想把一些你的设备不支持的D3D函数写入程序中,为了达到这个目的,D3D提供了REF设备(reference rasterizer),它用软件模拟了所有的D3DAPI。这允许你写并测试那些你的显卡不支持的D3D特性的代码。例如在本书的第四部分,某些人的显卡可能会不支持顶点和像素着色器。如果你的显卡不支持着色器,你仍然能够使用REF设备测试示例代码。另外,REF设备实在是太慢了,除了测试外它没有任何利用价值。
1.1.2 D3DDEVTYPE
在程序中,我们用D3DDEVTYPE_HAL来定义HAL设备,它是D3DDEVTYPE枚举类型的一个成员。同样的,REF设备则由D3DDEVTYPE_REF来定义,它也属于D3DDEVTYPE枚举类型。这个类型很重要,因为在创建设备的时候需要指定它。
1.2 COM
组件对象模型(COM, Component Object Model)是一种能使DirectX独立于编程语言和具有向下兼容性的技术。我们通常把COM对象作为一个接口,你可以把它当作达到某种目的的C++类来使用它。当使用C++写DirectX程序的时候,COM的大部分细节对我们来说是透明。但是有一件事,我们必须知道,那就是我们通过某个特殊的COM接口的函数或指针获得了另一个COM接口指针,而不是通过C++的新关键字来创建它。当我们使用完某个接口后,调用它的Release方法比直接Delete它更好。COM对象具有它们自己的内存管理。
注意:COM接口都具有前缀大写字母“I”,例如表示一个表面的COM接口叫做IDirect3DSurface9。

1.3 Some Preliminaries(一些准备工作)
D3D的初始化过程要求我们对图形学基础知识和D3D类型有一定了解。本节将介绍这些知识和类型以确保下一节能把更多的时间放在D3D初始化的讨论上。
1.3.1 Surfaces
表面是一个像素点阵,在D3D中主要用来存储2D图形数据。图1.2指明了表面的一些成分。由图可以看出表面数据就像一个矩阵,像素数据实际上存储在线性数组里面。

此图片被缩小显示,点击查看大图

图1.2:表面
表面的Width和Height是按像素计算的。Pitch以字节为单位。而且Pitch有可能比Width大且依赖于低层硬件,所以不能单纯的认为Pitch = Width * sizeof (pixelFormat)。
在程序中,可以使用IDirect3DSurface9接口来描述表面。这个接口提供若干方法来直接读写表面数据并且还有一个方法用来返回表面信息。IDirect3DSurface9中最重要的方法是:
1.    LockRect——使用这个方法,我们将获得一个指向表面内存的指针,然后,通过一系列指针运
算,我们可以对表面上任一个像素点进行读、写操作。
2.    UnlockRect——当你调用了LockRect和完成了对表面内存的访问后,你必须调用这个方法给表              
面解锁。
3.    GetDesc——这个方法将通过填充D3DSURFACE_DESC结构来返回表面的描述信息。
下面的代码块表示将表面锁定并将每一像素染成红色:

// Assume _surface is a pointer to an IDirect3DSurface9 interface.
// Assumes a 32-bit pixel format for each pixel.

// Get the surface description.
D3DSURFACE_DESC surfaceDesc;
_surface->GetDesc(&surfaceDesc);

// Get a pointer to the surface pixel data.
D3DLOCKED_RECT lockedRect;
_surface->LockRect(
&lockedRect,     // pointer to receive locked data
0,              // lock entire surface
0);             // no lock flags specified

// Iterate through each pixel in the surface and set it to red.
DWORD* imageData = (DWORD*)lockedRect.pBits;
for (int i = 0; i < surfaceDesc.Height; i++)
{
for (int j = 0; j < surfaceDesc.Width; j++)
{
// index into texture, note we use the pitch and divide by
// four since the pitch is given in bytes and there are
// 4 bytes per DWORD.
int index = i * lockedRect.Pitch / 4 + j;
imageData[index] = 0xffff0000; // red
}
}
_surface->UnlockRect();
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
程序中D3DLOCKED_RECT结构的定义如下:
typedef struct _D3DLOCKED_RECT {
INT Pitch;                // the surface pitch
void * pBits;              // pointer to the start of the surface memory
} D3DLOCKED_RECT;
在程序中,我使用DWORD来定义一个32位的像素,我们暂时不用去关心为什么0xffff0000表示红色,关于颜色的说明将在第四章谈到。
1.3.2 Multisampling
由于使用像素矩阵来表示图像,在显示时会出现锯齿状,Multisampling就是使其变得平滑的技术。它的一种最普通的用法即为——全屏抗锯齿。
D3DMULTISAMPLE_TYPE枚举类型使我们可以指定全屏抗锯齿的质量等级:
1.    D3DMULTISAMPLE_NONE——不使用全屏抗锯齿。
2.    D3DMULTISAMPLE_1_SAMPLE…D3DMULTISAPLE_16_SAMPLE——设定1~16级的等级。
本书的示例程序中没有使用全屏抗锯齿的功能,因为它大大的降低了程序运行速度。如果你实在很想使用它的话,要记住使用IDirect3D9::CheckDeviceMultisampleType来检测你的显卡是否支持。
1.3.3 Pixel Formats(像素格式)
当我们创建一个表面或纹理时候,经常需要指定这些D3D资源的像素格式。它是由D3DFORMAT枚举类型定义的。这里例举一部分:
1.    D3DFMT_R8G8B8——表示一个24位像素,从左开始,8位表红色,8位表绿色,8位表蓝色。
2.    D3DFMT_X8R8G8B8——表示一个32位像素,首8位不用,其余同上。
3.    D3DFMT_A8R8G8B8——32位像素,首8位为ALPHA通道,其余同上。
注意:这三种格式是最常用并为大部分显卡所支持。但浮点像素格式或其它一些类型的支持并不是很广泛,在使用它们前请先检测你的显卡,看是否支持。
1.3.4 Memory Pools(内存池)
表面和其它一些D3D资源被放在多种内存池中。内存池的种类由D3DPOOL枚举类型的一个成员来指定。它可以为下列几种:
1.    D3DPOOL_DEFAULT——表示D3D将根据资源的类型和用途把它们放在最合适的地方。这有可能是显存、AGP内存或者系统内存中。值得注意的是,这种内存池中的资源必须要在IDirect3DDevice9::Reset被调用之前消毁掉,然后必须重新初始化。
2.    D3DPOOL_MANAGED——资源将由D3D管理并且按设备的需要来指定放在显存还是放在AGP内存中,同时备份这些资源到系统内存中。当应用程序访问和改变资源,也会对系统内存造成影响。
3.    D3DPOOL_SYSTEMMEM——指定资源放在系统内存中。
4.    D3DPOOL_SCRATCH——规定资源放在系统内存中,它与D3DPOOL_SYSTEMMEM不同之处在于使用这个参数使图形设备不能访问本内存池的资源,但资源可以被复制出去。
1.3.5 The Swap Chain and Page Flipping(交换链和页面切换)
D3D通常创建2~3个表面组成一个集合,即为交换链,通常由IDirect3DSwapChain接口来表示。但是我们很少用到这个东东,通常让D3D自己去管理它。所以我们只要大概的了解一下它就可以了。

此图片被缩小显示,点击查看大图

图1.4:一个双表面的交换链。
如图1.4,Front Buffer将用来在屏幕显示,我们所看到的就是这个表面。但是监视器并不是实时显示这个表面的,通常情况下,它是每六十分之一秒刷新一次,即刷新率为60赫兹。

此图片被缩小显示,点击查看大图

  图1.5

应用程序的帧数经常与监视器的刷新率不同步。虽然Front Buffer对应于显示屏幕,但我们并不直接在Front Buffer上绘制动画,而是在Back Buffer进行绘制。当监视器将Front Buffer显示出来后,Front Buffer就被放到交换链的末端,即变成图中的Back Buffer,而Back Buffer就会变成交换链中的Front Buffer。这个过程就叫做presenting 。这与DirectDraw中的 Flip相似。图1.5表示了交换的整个过程。
因此,我们绘图代码的结构就会像下面这样:
1.    Render to back buffer
2.    Present the back buffer
3.    Goto (1)
1.3.6 Depth Buffers(深度缓冲)
深度缓冲也是一个表面,但它不是用来存储图像数据而是用来记录像素的深度信息,它将确定哪一个像素最后被绘制出来。所以,如果绘制的图片是640*480的分辨率,那就会有640*480个深度值。

此图片被缩小显示,点击查看大图

图1.6:物体是重叠的。
图1.6展示了一个简单的场景,在这个场景里,一个物体把将另一个物体的一部分遮住了。为了使D3D能确定物体的前后关系并正确的绘制出来,我们使用深度缓冲,又叫做z-buffering技术。
深度缓冲为每一个像素计算深度值并进行深度测试。通过深度测试我们可以比较得出哪个像素离摄相机更近并将它画出来。这样就可以只绘制最靠近摄相机的像素,被遮住的像素就不会被画出来。
深度缓冲的格式决定着深度测试的精确性。一个24位的深度缓冲会比16位的深度缓冲更精确。通常,应用程序能够很好的工作在24位深度缓冲下,但是D3D也支持32位的深度缓冲。
1.    D3DFMT_D32——32位深度缓冲
2.    D3DFMT_D24S8——24位深度缓冲并保留8位模版缓冲(stencil buffer)
3.    D3DFMT_D24X8——24位深度缓冲
4.    D3DFMT_D24X4S4——24位深度缓冲并保留4位模版缓冲
5.    D3DFMT_D16——16位深度缓冲
注意:关于模版缓冲的问题将在第八章说明。
1.3.7 Vertex Processing
顶点能够通过两种不同的方法被处理,一种是software vertex processing,一种是hardware vertex processing,前者可以在任何显卡上使用,而后者则需要硬件支持。
使用硬件顶点处理比软件更快,而且不占用CPU资源,这意味差CPU可以有更多的空闲时间进行别的计算。
注意:如果一块显卡支持硬件顶点处理的话,也就是说它也支持几何转换和光源。
1.3.8 Device Capabilities
D3D支持的每一项特性都对应于D3DCAPS9结构的一个数据成员。如果你想在程序中检测你的设备的是否支持某一特性,只需要检测D3DCAPS9结构中相对应的某一成员就可以了。
下面将举例说明,假设我们想要检测显卡是否支持硬件顶点处理(换句话说,就是显卡是否支持几何转换和光源)。通过查阅SDK中的D3DCAPS9结构,可以得知数据成员D3DCAPS9::DevCaps中的D3DDEVCAPS_HWTRANSFORMANDLIGHT位表示硬件是否支持硬件顶点处理即几何变换和光源。程序如下:


bool supportsHardwareVertexProcessing;

// If the bit is “on” then that implies the hardware device
// supports it.
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{
// Yes, the bit is on, so it is supported.
supportsHardwareVertexProcessing = true;
}
else
{
// No, the bit is off, so it is not supported.
hardwareSupportsVertexProcessing = false;
}
注意:DevCaps即为“device capabilities”
   下一节将学习怎样根据硬件的实际情况来初始化D3DCAPS9
      建议阅读SDK中关于D3DCAPS9的说明

1.4 Initializing Direct3D
下面几点说明怎样初始化D3D:
1.    通过IDirect3D9获得一个指针。这个接口用于获得物理设备的信息和创建一个IDirect3DDevice9接口,它是一个代表我们显示3D图形的物理设备的C++对象。
2.    检查设备的技术特性(D3DCAPS9),搞清楚是否支持硬件顶点处理。
3.    初始化一个D3DPRESENT_PARAMETERS结构实例,这个结构包含了许多数据成员允许我们指定将要创建的IDirect3DDevice9接口的特性。
4.    创建一个基于D3DPRESENT_PARAMETERS的IDirect3DDevice9接口。
请注意,本书使用主显示设备绘制3D图形,如果你的机子只有一块显卡,那它就是主显示设备。如果你有多个显卡,那么你当前使用的显卡将会成为主显示设备(如:用来显示桌面的显卡)。
1.4.1 Acquiring an IDirect3D9 Interface
首先通过IDirect3D9接口初始化D3D,这很简单,代码如下:
IDirect3D9* _d3d9;
_d3d9 = Direct3DCreate9(D3D_SDK_VERSION);
Direct3DCreate9的唯一一个参数总是D3D_SDK_VERSION,这可以保证应用程序通过正确的头文件被生成。如果失败,将返回一个空指针。
IDirect3D9通常有两个用途:设备列举和创建IDirect3DDevice9对象。设备列举即为查明设备的技术特性,显示模式、格式,和其它每一种显卡各自支持的特性。但是这种检测实在太慢了,为了使D3D运行得尽可能快,我们通常不使用这个测试,除了下一节所谈到的一项测试。为了安全跳过测试阶段,我们通可以选择总是被所有显卡都支持的“安全”配置。
1.4.2 Checking for Hardware Vertex Processing
在我们创建一个IDirect3DDevice9来表示主显示设备时,必须要设定其顶点处理的类型。如果可以的话,当然要选用硬件顶点处理,但是如果显卡不支持的话,也只有用软件方法了。
首先我们要根据主显示设备的技术特性来初始化D3DCAPS9结构。可以使用如下方法:
HRESULT IDirect3D9::GetDeviceCaps(
UINT Adapter,
D3DDEVTYPE DeviceType,
D3DCAPS9 *pCaps
);
Adapter——指定要获得哪个显示适配器的特性
DeviceType——指定设备类型(硬件设备(D3DDEVTYPE_HAL),软件设备(D3DDEVTYPE_REF))
PCaps——返回一个已填充的D3DCAPS9结构
然后,我们可以开始检测了,代码如下:

// Fill D3DCAPS9 structure with the capabilities of the
// primary display adapter
.
D3DCAPS9 caps;
d3d9->GetDeviceCaps(
D3DADAPTER_DEFAULT, // Denotes primary display adapter.
deviceType,          // Specifies the device type, usually D3DDEVTYPE_HAL.
&caps);          // Return filled D3DCAPS9 structure that contains
// the capabilities of the primary display adapter.

// Can we use hardware vertex processing?
int vp = 0;
if( caps.DevCaps & D3DDEVCAPS_HWTRANSFORMANDLIGHT )
{
// yes, save in ‘vp’ the fact that hardware vertex
// processing is supported.
vp = D3DCREATE_HARDWARE_VERTEXPROCESSING;
}
else
{
// no, save in ‘vp’ the fact that we must use software
// vertex processing.
vp = D3DCREATE_SOFTWARE_VERTEXPROCESSING;
}
在程序中,我们使用变量vp来存储顶点处理类型。这是因为在稍后创建IDirect3DDevice9时要求指定其顶点处理的类型。
注意:标识符D3DCREATE_HARDWARE_VERTEXPROCESSING
D3DCREATE_SOFTWARE_VERTEXPROCESSING是预定义的值,它们分别代表硬件顶点处理和
软件顶点处理。
      如果一个应用程序在你的机子上不能运行,说明它用到的一些特性可能你的显卡并不支持,可以试试把设备类型换成REF。
1.4.3 Filling Out the D3DPRESENT_PARAMETERS Structure
现在我们要填充一个D3DPRESENT_PARAMETERS结构的实例了。这个结构用于设定我们将要创建的IDirect3DDevice9对象的一些特性,它的定义如下:

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;
下面介绍其比较重要的数据成员,至于更详细的信息,请查阅SDK:
BackBufferWidth——后备表面的宽度(以像素为单位)
BackBufferHeight——后备表面的高度(以像素为单位)
BackBufferFormat——后备表面的像素格式(如:32位像素格式为D3DFMT——A8R8G8B8)
BackBufferCount——后备表面的数量,通常设为“1”,即只有一个后备表面
MultiSampleType——全屏抗锯齿的类型,详情请看SDK
MultiSampleQuality——全屏抗锯齿的质量等级,详情看SDK
SwapEffect——指定表面在交换链中是如何被交换的,取D3DSWAPEFFECT枚举类型中的一个成员。其中D3DSWAPEFFECT_DISCARD是最有效的
hDeviceWindow——与设备相关的窗口句柄,你想在哪个窗口绘制就写那个窗口的句柄
Windowed——BOOL型,设为true则为窗口模式,false则为全屏模式
EnableAutoDepthStencil——设为true,D3D将自动创建深度/模版缓冲
AutoDepthStencilFormat——深度/模版缓冲的格式
Flags——一些附加特性,设为0或D3DPRESENTFLAG类型的一个成员。下列两个最常用的标志
全部的标志请查阅SDK:
D3DPRESENTFLAG_LOCKABLE_BACKBUFFER——设定后备表面能够被锁定,这会降低应用程序的性能
D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL——深度/模版缓冲在调用IDirect3DDevice9::present方法后将被删除,这有利于提升程序性能
FullScreen_RefreshRateInHz——刷新率,设定D3DPRESENT_RATE_DEFAULT使用默认刷新率
PresentationInterval——属于D3DPRESENT成员,又有两个常用标志,其余请查SDK:
         D3DPRESENT_INTERVAL_IMMEDIATE——立即交换
         D3DPRESENT_INTERVAL_DEFAULT——D3D选择交换速度,通常等于刷新率
填充示例如下:
D3DPRESENT_PARAMETERS d3dpp;
d3dpp.BackBufferWidth = 800;
d3dpp.BackBufferHeight = 600;
d3dpp.BackBufferFormat = D3DFMT_A8R8G8B8;            //pixel format
d3dpp.BackBufferCount = 1;
d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE;
d3dpp.MultiSampleQuality = 0;
d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
d3dpp.hDeviceWindow = hwnd;
d3dpp.Windowed = false;                              // full screen
d3dpp.EnableAutoDepthStencil = true;
d3dpp.AutoDepthStencilFormat = D3DFMT_D24S8; // depth format
d3dpp.Flags = 0;
d3dpp.FullScreen_RefreshRateInHz = D3DPRESENT_RATE_DEFAULT;
d3dpp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;
1.4.4 Creating the IDirect3DDevice9 Interface
在填充完了D3DPRESENT_PARAMETERS结构后,我们就可以创建一个IDirect3DDevice9对象了,方法如下:
HRESULT IDirect3D9::CreateDevice(
UINT Adapter,
D3DDEVTYPE DeviceType,
HWND hFocusWindow,
DWORD BehaviorFlags,
D3DPRESENT_PARAMETERS *pPresentationParameters,
IDirect3DDevice9** ppReturnedDeviceInterface
);
Adapter——指定对象要表示的物理显示设备
DeviceType——设备类型,前面说过
hFocusWindow——同我们在前面d3dpp.hDeviceWindow的相同
BehaviorFlags——设定为D3DCREATE_SOFTWARE_VERTEXPROCESSING或者D3DCREATE_HARDWARE_VERTEXPROCESSING
ppReturnedDeviceInterface——返回创建的设备
例:

IDirect3DDevice9* device = 0;
hr = d3d9->CreateDevice(
D3DADAPTER_DEFAULT,     // primary adapter
D3DDEVTYPE_HAL,         // device type
hwnd,                    // window associated with device
D3DCREATE_HARDWARE_VERTEXPROCESSING, // vertex processing type
&d3dpp,               // present parameters
&device);            // returned created device
if( FAILED(hr) )

{
::MessageBox(0, "CreateDevice() - FAILED", 0, 0);
return 0;
}

1.5 Sample Application: Initializing Direct3D
在本章的例程中,初始化了一个D3D应用程序并用黑色填充显示窗口

此图片被缩小显示,点击查看大图

图1.7
本书所有的应用程序都包含了d3dUtility.h和d3dUtility.cpp这两个文件,它们所包含的函数实现了所
有D3D应用程序都要去做的一些常见的功能。例如:创建一个窗口、初始化D3D、进入程序的消息
循环等。将这些功能封装在函数中能使示例程序更加突出该章的主题。另外,在我们学习本书的过
程中还会在这两个文件中加上一些通用的代码。
1.5.1 d3dUtility.h/cpp
在开始本章的例程之前,让我们先熟悉一下d3dUtility.h/cpp所提供的函数。d3dUtility.h如下:

// Include the main Direct3DX header file. This will include the
// other Direct3D header files we need.
#include <d3dx9.h>

namespace d3d
{
bool InitD3D(
HINSTANCE hInstance,             // [in] Application instance.
int width, int height,                 // [in] Back buffer dimensions.
bool windowed,                 // [in] Windowed (true)or
// full screen (false).
D3DDEVTYPE deviceType,         // [in] HAL or REF
IDirect3DDevice9** device);         // [out] The created device.

int EnterMsgLoop(
bool (*ptr_display)(float timeDelta));
LRESULT CALLBACK WndProc(
HWND hwnd,
UINT msg,
WPARAM wParam,
LPARAM lParam);

template<class T> void Release(T t)
{
if( t )
{
t->Release();
t = 0;
}
}

template<class T> void Delete(T t)
{
if( t )
{
delete t;
t = 0;
}
}
}
InitD3D——初始化一个应用程序主窗口并进行D3D的初始化。如果成功,则输出IDirect3DDevice9接口指针。从它的参数我们可以发现,我们能够设置窗口的大小和以窗口模式运行还是全屏模式运行。要知道它实现的细节,请看示例代码
EnterMsgLoop——这个函数封装了应用程序的消息循环。它需要输入一个显示函数的函数指针,显示函数为程序中绘制图形的代码块,这样做是为了使显示函数能够在空闲的时候被调用并显示场景,它的实现如下:

int d3d::EnterMsgLoop( bool (*ptr_display)(float timeDelta) )
{
MSG msg;
::ZeroMemory(&msg, sizeof(MSG));
static float lastTime = (float)timeGetTime();

while(msg.message != WM_QUIT)
{
if(::PeekMessage(&msg, 0, 0, 0, PM_REMOVE))
{
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
else
{
float currTime = (float)timeGetTime();
float timeDelta = (currTime -
lastTime)*0.001f;
ptr_display(timeDelta);         // call display function
lastTime = currTime;
}
}
return msg.wParam;
}
与“time”有关的代码用于计算每次调用显示函数的时间间隔,即是每帧的时间。
Release——这个模函数能方便的释放COM接口并将它们的值设为NULL
Delete——这个模函数能方便的删除一个对象并将指向其的指针设为NULL
WndProc——应用程序主窗口的回调函数
1.5.2 Sample Framework
通过示例程序的框架,我们形成了一种通用的方法去构造本书的示例程序。每一个例程都有三个函数的实现,当然这不包括回调函数和WinMain主函数。这三个函数的实现不同,那么示例程序也就不同。这三个函数是:
bool Setup()——在这个函数里,我们将准备一切该程序需要用到的东西,包括资源的分配,检查设备技术特性,设置应用程序的状态
void Clearup()——这个函数将释放Setup()中分配的资源
bool Display(float timeDelta)——顾名思义,所有与我们绘图和显示有关的代码都在这个函数里实现。参数timeDelta为每一帧的间隔时间,用来控制每秒的帧数。
1.5.3 Sample: D3D Init
这个示例程序将创建并初始化一个D3D应用程序,并用黑色填充屏幕。注意,我们使用了通用函数简化了初始化过程。
首先,我们要包含d3dUtility.h头文件,并为设备声明一个全局变量:
#include "d3dUtility.h"
IDirect3DDevice9* Device = 0;
然后实现我们的框架函数:
bool Setup()
{
return true;
}
void Cleanup()
{
}
在这个程序中,我们不需要使用任何资源或触发任何事件,所以这两个函数都为空。

bool Display(float timeDelta)
{
if( Device )
{
Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
0x00000000, 1.0f, 0);
Device->Present(0, 0, 0, 0);// present backbuffer
}
return true;
}
Display方法调用了IDirect3DDevice::Clear方法分别用黑色和1.0填充后备表面和深度/模版缓冲。如果应用程序不停止的话,我们会一直执行这个操作。
IDirect3DDevice::Clear声明如下:
HRESULT IDirect3DDevice9::Clear(
DWORD Count,
const D3DRECT* pRects,
DWORD Flags,
D3DCOLOR Color,
float Z,
DWORD Stencil
);
Count——pRects组中的矩形的个数
pRects——将要清除的屏幕矩形的数组,这使我们可以清除屏幕的某一部分
Flags——指定在哪些表面上执行这个操作
         D3DCLEAR_TARGET——目的表面,通常为后备表面
         D3DCLEAR_ZBUFFER——深度缓冲
         D3DCLEAR_STENCIL——模版缓冲
Color——使用什么颜色填充清除的表面
Z——设置深度缓冲的值
Stencil——设置模版缓冲的值
屏幕被填充后,要调用IDirecte3DDevice9::Present方法进行后备表面的交换。
Windows 回调函数为一组事件集,使我们可用在按ESC键后退出程序。

LRESULT CALLBACK d3d::WndProc(HWND hwnd, UINT msg, WPARAM wParam,LPARAM lParam)
{
switch( msg )
{
case WM_DESTROY:
::PostQuitMessage(0);
break;
case WM_KEYDOWN:
if( wParam == VK_ESCAPE )
::DestroyWindow(hwnd);
break;
}
return ::DefWindowProc(hwnd, msg, wParam, lParam);
}
最后,WinMain按如下步骤运行:
1.    初始化主显示窗口和D3D
2.    调用Setup进行程序的准备工作
3.    使用Display函数作为参数进入消息循环
4.    清除应用程序最后释放IDirecte3DDevice9对象
int WINAPI WinMain(HINSTANCE hinstance,
HINSTANCE prevInstance,
PSTR cmdLine,
int showCmd)
{
if(!d3d::InitD3D(hinstance,800, 600, true, D3DDEVTYPE_HAL, &Device))
{
::MessageBox(0, "InitD3D() - FAILED", 0, 0);
return 0;
}
if(!Setup())
{
::MessageBox(0, "Setup() - FAILED", 0, 0);
return 0;
}
d3d::EnterMsgLoop( Display );
Cleanup();
Device->Release();
return 0;
}
本书的大部分程序,都是通过这三个函数的实现的。
特别注意:不要忘了在你的工程中加入d3d9.lib、d3dx9.lib、winmm.lib 这三个库!

1.6 Summary
1.D3D可以看作是应用程序和图形设备之间的中介。程序员通过调用D3D函数对物理硬件直接进行操作,当然这些都要基于设备的HAL
2.REF使开发者们能够测试那些D3D支持但显卡不支持的特性。
3.组件对象模型(COM)使D3D能独立于编程语言并具有向下兼容性。D3D程序员不需要知道COM的工作细节,他们只要知道怎样获得和释放一个COM接口就可以了。
4.表面是一种特殊的D3D接口,它用于存储2D图像。表面的像素格式用D3DFORMAT枚举类型的一个成员设定。表面和其它的D3D资源可以被存储在不同的内存池中,这要通过D3DPOOL枚举类型来设定。此外,表面还可以打开全屏抗锯齿功能来创建更平滑的图像。
5.IDirect3D9接口用来查询设备信息,并且用它来创建IDirect3DDevice9接口。
6.IDirect3DDevice9接口可以被认为是控制图形设备的软件接口。例如:调用IDirect3DDevice9::Clear方法将直接操作图形设备清空指定的表面。
7.样例框架为本书所有的程序提供了一致的接口。d3dUtility.h/cpp文件中封装的通用代码是每一个应用程序都要用到的初始化代码。通过封装这些代码,我们将其隐藏起来,使应用程序更能突出当前的主题。

你可能感兴趣的:(数据结构,游戏,编程,框架,软件测试)