站内搜索:     
站点首页破釜学院编程开发其他相关 → 游戏开发新手入门之位图化图形
正在加载相关信息.....
Web 站内搜索
游戏开发新手入门之位图化图形
】【打印】【加入收藏】【关闭收藏到新浪ViVi】【收藏到365KEY】 浏览字号:
日期:2006-08-09 人气: 出处:天极

  使用位块传输

   位块传输是显示卡操控位图数据的一部分,你同样可以用它来进行颜色填充。就像我们过一会儿看到的,随着硬件的性能提高,会有很多经典的技巧。DirectX有权使用硬件的加速功能,但要记住,如果DirectX使用的加速功能不被机器硬件支持,将自动启用硬件仿真层(HEL),但这也并非万无一失,因为有些功能靠硬件仿真层是无法实现的(否则谁还买3D加速卡^_^),所以你需要检测你的函数是否调用成功。

   GDI位块传输可以在DirectDraw编程中使用,而且有时也的确是这样做的。然而DirectDraw具有其自身的位块传输函数,它们通常更加适合于编程环境,而且比GDI的相应的函数执行得更快。DirectDraw位块传输函数名为Blt()和BltFast(),都是有IDirectDrawSurface7接口提供的。两者不同处是BltFast()不处理剪切、放缩等其它Blt()做的有趣的事情。如果在硬件仿真层上,BltFast()要比Blt()快10%左右,但如果有硬件加速卡支持(硬件加速卡主要就是为位块传输服务的),二者的速度就差不多了,而且现在大多数的机器都有硬件加速卡,所以我总是使用Blt()。让我们仔细看看这个神奇的东东:

HRESULT Blt(
  LPRECT lpDestRect,
  LPDIRECTDRAWSURFACE7 lpDDSrcSurface,
  LPRECT lpSrcRect,
  DWORD dwFlags,
  LPDDBLTFX lpDDBltFx
);
   由于Blt()所拥有的最后一个参数,使其能做很多特殊的事儿。该参数配有一个标志常量列表,我将会向你介绍其中最有用的几个。另外,注意在把位图从一个表面向另一个表面传递时,你应该调用目的表面的Blt(),不是源表面的。好了吗?以下是函数的参数说明:

   ※ LPRECT lpDestRect:参数lpDestRect为指向结构RECT的指针,它给出了位块传输操作的目标表面的左上角和右下角的坐标。如果源表面和目标(目的)表面的大小不一致,Blt()将把源表面的图象自动按照比例适应目标表面的大小。如果此参数为NULL,则使用整个目标表面。

   ※ LPDIRECTDRAWSURFACE7 lpDDSrcSurface:参数lpDDSrcSurface为指向DirectDraw表面的指针,该DirectDraw表面为位块传输之源表面。如果你只是要用颜色填充目的表面,你可以把它设置为NULL。

   ※ LPRECT lpSrcRect:参数lpSrcRect为指向结构RECT的指针,它给出了位块传输(有的书上也叫作“位转换”)操作的源表面的左上角和右下角的坐标。如果此参数为NULL,则使用整个源表面。

   ※ DWORD dwFlags:对于这个参数有一个巨大的标志常量列表,可以用“|”组合使用标志常量。其中一些是为Direct3D服务的,所以我将把我们常用的列出来: · DDBLT_ASYNA:位块传输异步的以先入先出(FIFO)的顺序接收。如果没有空间可用于FIFO硬件,则该调用失败。

   · DDBLT_COLORFILL:使用DDBLTFX结构的数据成员dwFillColor作为RGB颜色填充目标表面的矩形。

   · DDBLT_DDFX:DDBLTFX结构的dwDDFX成员指定了位块传输的使用效果。

   · DDBLT_DDROPS:DDBLTFX结构的dwDDROP成员指定了光栅操作(ROPS),该操作不是Win32 API的一部分。

   · DDBLT_KEYDEST:颜色键与目标表面相关联。

   · DDBLT_KEYDESTOVERRIDE:DDBLTFX结构的dckDestColorkey成员是目标表面的颜色键。

   · DDBLT_KEYSRC:颜色键与源表面相关联。

   · DDBLT_KEYSRCOVERRIDE:DDBLTEX结构的dckSrcColorkey成员是源表面的颜色键。

   · DDBLT_ROP:DDBLTFX结构的dwROP成员是位块传输的ROP(光栅操作代码),这些ROP与Win32 API中定义的那些相同。

   · DDBLT_ROTATIONANGLE:DDBLTFX结构的dwRotationAngle成员是表面的旋转角度,其单位为1/100度。

   · DDBLT_WAIT:在位块传输器忙的情况下,推迟DDERR_WASSTILLDRAWING返回值(位块传输函数调用失败返回的值之一),而当位块传输开始或发生另一个错误时立即返回。

   我几乎总是使用DDBLT_WAIT标志。颜色键标志也是很重要的,我们过一会儿再说它。现在,还有最后一个Blt()参数需要说一下:

   ※ LPDDBLTFX lpDDBltFX:这是一个指向DDBLTFX结构的指针,它可以包含各种特殊要求的信息。如果没有什么特殊要求,你就设置为NULL好了。让我们仔细看看这个结构。我警告你,它是很魁梧的:^_^
typedef struct _DDBLTFX{
  DWORD dwSize;
  DWORD dwDDFX;
  DWORD dwROP;
  DWORD dwDDROP;
  DWORD dwRotationAngle;
  DWORD dwZBufferOpCode;
  DWORD dwZBufferLow;
  DWORD dwZBufferHigh;
  DWORD dwZBufferBaseDest;
  DWORD dwZDestConstBitDepth;

  union {
   DWORD dwZDestConst;
   LPDIRECTDRAWSURFACE lpDDSZBufferDest;
  };

  DWORD dwZSrcConstBitDepth;

  union {
   DWORD dwZSrcConst;
   LPDIRECTDRAWSURFACE lpDDSZBufferSrc;
  };

  DWORD dwAlphaEdgeBlendBitDepth;
  DWORD dwAlphaEdgeBlend;
  DWORD dwReserved;
  DWORD dwAlphaDestConstBitDepth;

  union {
   DWORD dwAlphaDestConst;
   LPDIRECTDRAWSURFACE lpDDSAlphaDest;
  };

  DWORD dwAlphaSrcConstBitDepth;

  union {
   DWORD dwAlphaSrcConst;
   LPDIRECTDRAWSURFACE lpDDSAlphaSrc;
  };

  union {
   DWORD dwFillColor;
   DWORD dwFillDepth;
   DWORD dwFillPixel;
   LPDIRECTDRAWSURFACE lpDDSPattern;
  };

  DDCOLORKEY ddckDestColorkey;
  DDCOLORKEY ddckSrcColorkey;
} DDBLTFX, FAR* LPDDBLTFX;
   如果我整个详细的介绍这个结构,恐怕我们都会受不了的,并且也没有这个必要。所以我只告诉你一些重点的部分。谢天谢地,该结构的大部分都是为z缓冲区(z-buffers)和α消息服务的,我们不用理会它。嘻嘻,我的工作量变得很小了:

   ※ DWORD dwSize:象所有的DirectX的结构一样,当你初始化这个结构时,该成员放置结构的大小。

   ※ DWORD dwDDFX:这些是位块传送所能接受的一些特殊操作。列表并不长,别担心喔!

   · DDBLTFX_ARITHSTRETCHY:位块传输时,在Y轴算术拉伸位图。

   · DDBLTFX_MIRRORLEFTRIGHT:y轴上的镜像变换。表面从左到右完成镜像效果。

   · DDBLTFX_MIRRORUPDOWN:x轴上的镜像变换。表面从上到下完成镜像效果。

   · DDBLTFX_NOTEARING:把动画图像块转移到前段缓存时可以使用这个参数,这样位块传输操作的时间会与屏幕刷新率相一致,并使画面撕裂的可能性减小到最小。

   · DDBLTFX_ROTATE180:位块传输时,把表面顺时针旋转180度。

   · DDBLTFX_ROTATE270:位块传输时,把表面顺时针旋转270度。

   · DDBLTFX_ROTATE90:位块传输时,把表面顺时针旋转90度。

   需要详细解释的可能只有DDBLTFX_NOTEARING。游戏离不开动画,动画制作者主要关心的通常是动画的速度和性能,速度太快会导致图象质量的恶化。光栅扫描显示系统(我们基本上用的都是这种显示器)利用电子束扫描每一条水平线上的屏幕象素点。象素行从屏幕的左上角开始更新,到屏幕的右下角结束。各象素行都被称为扫描线。电子束在每一行扫描线的末端被关掉,而电子枪重新瞄准下一行的起始点,这个过程成为水平回扫。当过程执行到屏幕扫描线的最后一行时,电子束再次被关掉,电子枪重新瞄准屏幕的左上角。电子枪从屏幕的右下角重新瞄准到左上角的过程所需的时间被称为垂直回归或者屏幕空白周期。如果在视频控制器显示视频数据的同时,视频数据被CPU做了更改,这时就会产生问题。在PC机中,屏幕的刷新率通常在60Hz到100Hz之间,而现在的CPU则可以在每秒处理成百上千的指令,这样就很可能导致位于视频内存区的图象在视频系统完成显示之前发生更改。图象断裂的结果被称为图象撕裂。就我的经验而言,使用了DDBLTFX结构的DDBLTFX_NOTEARING后,图象撕裂就不是什么问题了。

   ※ DWORD dwROP:使用这个标志来指定Win32模式的光栅操作代码。同GDI函数BitBlt()和StretchBlt()中的相对应参数的功能一样。可以通过IDirectDraw7::GetCaps()函数得到可能的光栅操作列表,可以通过“|”组合标志常量,确定源矩形表面和目标矩形表面是怎样结合的。

   ※ DWORD dwRotationAngle:这是用来旋转位图角度的,可以旋转任意角度。这是非常棒的,但不幸的是,它只能在HAL层(硬件抽象层)上工作,这就意味着用户的显示卡要支持加速旋转,否则……,但不能保证每个用户都有这种高档的显示卡,所以你需要考虑周全。如果你真的需要旋转处理,你只好自己写这样的函数了,这可是一个大话题,需要另写一部指南了,所以我们就越过它。但请注意,如果是90度倍数的角度,你可以使用DDBLTFX_ROTATE90等。这将使你回避显示卡不干活的风险。

   ※ DWORD dwFillColor:如果你要使用位块传输来填充颜色,你必须把颜色放入到这个参数中。

   ※ DDCOLORKEY ddckDestColorKey,ddckSrcColorKey:你要使用颜色键时必须要指定这些成员。这两个家伙是很重要的。但暂时我们还不讨论它们,因为我们过一会儿才讲颜色键。

   以上这些就是DDBLTFX结构中比较有用的成员了,这就意味着你现在拥有足够的知识进行位块传输了!如果你现在感觉有些混乱,不要紧,你实践过一段时间就会好了。让我们看看几个例子。假设你已经有了一个叫作lpddsBack的后缓冲区,你想把它里面的内容传输到主表面,很简单的,你看:
lpddsPrimary->Blt(NULL, lpddsBack, NULL, DDBLT_WAIT, NULL);
   再轻轻回忆一下,第一个参数和第三个参数分别是位块传输的目标矩形和源矩形。由于我把它们都设置为NULL,就说明是对全部的表面进行拷贝。现在,让我们在看看,假设你有一个在离屏表面里的名字叫作lpddsTileset的16×16大小的图形,你想把它传输到后缓冲区,变成32×32大小的,你需要这样做:
RECT dest, src;
SetRect(&src, 0, 0, 16, 16); // the coordinates of the tile
SetRect(&dest, 0, 0, 32, 32); // where you want it to end up on the back buffer
lpddsBack->Blt(&dest, lpddsTileset, &src, DDBLT_WAIT, NULL);
   这个例子同上一个例子不同处在与这个例子设置了位块传输的坐标。由于这两个矩形区的大小不同,Blt()依照比例适当的变更了图形的大小。最后,我们还得举例说明一下DDBLTFX结构,就做一个颜色填充的例子吧。假设你在16-bit色彩模式下,是565象素格式,你要把你的后缓冲区填充为蓝色。下面就是你应该做的:
DDBLTFX fx;
INIT_DXSTRUCT(fx); // zero out the structure and set dwSize
fx.dwFillColor = RGB_16BIT565(0, 0, 31); // set fill color to blue
lpddsBack->Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);
   注意参数的设置,前三个都是NULL,你自己想想原因吧!好了,让我们看看另一个位块传输函数BltFast()吧。它只是Blt()的简化版本,所以我们不需要太多的时间解释它。下面是它的原形:
HRESULT BltFast(
  DWORD dwX,
  DWORD dwY,
  LPDIRECTDRAWSURFACE7 lpDDSrcSurface,
  LPRECT lpSrcRect,
  DWORD dwTrans
);
   你可以看得出来,它同Blt()极其相似。它也是IDirectDrawSurface7接口的成员函数,被目标表面调用。来看看它的参数:

   ※ DWORD dwX,dwY:这是Blt()和BltFast()之间不同的地方。是目标表面上进行位块传输的x和y坐标。如果源矩形大于目标矩形,则调用失败,因为BltFaxt()不能干按比例变换的事儿及其它能够通过Blt()完成的工作。

   ※ LPDIRECTDRAWSURFACE7 lpDDSrcSurface:这是源表面,同Blt()的一样。

   ※ LPRECT lpSrcRect:这个也同Blt()的一样。是在源表面中定义了矩形左上角和右下角的RECT结构。

   ※ DWORD dwTrans:定义了位块传输类型,它的标志列表很简单,只有四个标志:

   · DDBLTFAST_DESTCOLORKEY:使用目标颜色键的透明位块传输。

   · DDBLTFAST_NOCOLORKEY:没有透明的普通复制位块传输。

   · DDBLTFAST_SRCCOLORKEY:使用源颜色键的透明位块传输。

   · DDBLTFAST_WAIT:如果位块传输忙的话,不产生DDERR_WASSTILLDRAWING消息。一旦位块传输能够开始或者发生另一个错误时返回。

   就这些!BltFast()支持颜色键。下面让我们看一个简单的示例。把整个后缓冲区拷贝到主表面:

lpddsPrimary->BltFast(0, 0, lpddsBack, NULL, DDBLTFAST_WAIT);

   到现在为止,你已经是一个位块传输的专家了,还有几件事情对于DirectX程序很重要:颜色键和剪裁板。你可能知道在某些情况下关于颜色键有上百万种标志,那么到底怎样使用它呢?让我们一起看看吧!

  颜色键

   颜色键使一个位图被拷贝到另一个位图上时,不使所有的象素都显现。例如:当你把一个精灵(游戏中会动的对象一般都称作精灵)拷贝到地图上(背景上)时,这个精灵位图一般不会是一个精灵形状的位图,它通常都是一个矩形位图,位图里包含你所需要的精灵(除非你的精灵就是一个矩形机器人):

   游戏中,地图是先于精灵显示的,那么精灵走到树后时,还应有相应被遮挡的部分,这个先不讨论,下一节再说。现在,对我们更重要的是,如果不应用颜色键,这个精灵将永远带着这个黑色底框,这是绝对不能容忍的。

   为了解决这个问题,我们使用源颜色键。这个源颜色键告诉你精灵矩形的哪些颜色将不被拷贝(当然我们是让黑色不被拷了)。一个颜色键由两个值组成:一个低位颜色值,一个高位颜色值。当一个颜色键被申请使用时,在两个值之间的颜色,包括这两个值的颜色都将不会被拷贝。在DirectX中有一个结构用来处理它,叫作DDCOLORKEY,看看吧:

typedef struct _DDCOLORKEY{
DWORD dwColorSpaceLowValue;
DWORD dwColorSpaceHighValue;
} DDCOLORKEY, FAR* LPDDCOLORKEY;
   很简单的结构,我就不解释了。我将展示给你使用了颜色键之后的效果。我使用颜色键的高位和低位两个值仅仅把黑色包括在它们之间。因此,黑色是唯一不会被拷贝的颜色。

   好多了,是不是?这就是我们想得到的结果!现在,在我告诉你怎样建立和使用颜色键之前,我还有说一说目标颜色键,尽管我们的确我们不常用到它(我们常用的是源颜色键)。鉴于源颜色键定义了哪些颜色键不能被拷贝,目标颜色键定义了哪些颜色不能被写入(覆盖)。听起来很怪异,是不是?我也有同感。举个实例你就明白了。当你要把A位图拷贝到B位图的下面,意思就是把A位图作为背景,例如由于某种理由,需要把一个文本框拷贝到空的后缓冲区,然后再把背景画面拷贝到这个后缓冲区,但你又不能覆盖先前的文本框。因此,在后缓冲区里除了文本框的那些黑色的部分才能被写入象素。

   我也不清楚你什么时候需要处理这种情况,但是你的确可能用到(一旦你用到了,可千万要告诉我哦,我一直没有遇到这种情况呢^_^)。现在,你已经知道什么是颜色键了,让我们看看怎样使用它们吧!

  置颜色键

   在DirectDraw中有两种方法使用颜色键。第一种,你可以链接一个颜色键(或者两个,如果你同时使用源和目标颜色键)到表面,然后在位块传输时定义DDBLT_KEYSRC,DDBLT_KEYDEST,DDBLTFAST_SRCCOLORKEY或DDBLTFAST_DESTCOLORKEY标志,具体使用哪个标志,取决于你使用哪个位块传输函数和使用哪种颜色键。第二种,你可以创建一个颜色键,然后通过DDBLTFX结构传送给位块传输操作。当你不断地需要使用颜色键时,我向你推荐第一种方法;反之,当你偶然要使用一次颜色键,就用第二种方法吧!

   你可以把颜色键链接到已经建立好了的表面,也可以在建立表面的同时建立颜色键。两种方法我都将详细告诉你。假设你工作在16-bit显示模式下,是565象素格式,你要在后缓冲区使用一个仅包含黑色的源颜色键。如果你的后缓冲区已经建立好了,你就可以简单建立一个DDCOLORKEY结构,然后把它传递给IDirectDrawSurface7::SetColorKey()函数,如下所示:
HRESULT SetColorKey(
  DWORD dwFlags,
  LPDDCOLORKEY lpDDColorKey
);
   记住要用FAILED()宏检测这个函数的返回值,保证一切都在计划之中。函数的参数很简单:

   ※ DWORD dwFlags:决定所使用颜色键类型的标志。以下三个是你将用到的:

   · DDCKEY_COLORSPACE:该结构包含一个颜色范围,如果结构包含的是单独的颜色键则不作设置。

   · DDCKEY_DESTBLT:该结构指定颜色键或者颜色范围作为用于位块传输操作的目标颜色键。

   · DDCKEY_SRCBLT:该结构指定颜色键或者颜色范围作为用于覆盖操作的颜色键。

   ※ LPDDCOLORKEY lpDDColorKey:这是指向DDCOLORKEY结构的指针。

   就这么多。你可以根据你所需要使用的颜色键适当地定义位块传输的标志。注意,一个颜色键链接到表面,并不意味着你每一次必须使用它。如果你只定义了DDBLT-WAIT或DDBLTFAST_WAIT标志,颜色键将被忽略。下面是设置颜色键的方法:
DDCOLORKEY ckey;
ckey.dwColorSpaceLowValue = RGB_16BIT565(0, 0, 0); // or we could just say '0'
ckey.dwColorSpaceHighValue = RGB_16BIT565(0, 0, 0);
if (FAILED(lpddsBack->SetColorKey(DDCKEY_SRCBLT, &ckey)))
{
  // error-handling code here
}
   如果你要为已经建立的颜色键链接一个表面,有几件事情你需要做。首先,当你定义DDSURFACEDESC2结构的有效成员时,你需要使dwFlags成员包含DDSD_CKSRCBLT或者DDSD_CKDESTBLT标志,具体使用哪个标志,取决于你要使用哪种颜色键。回头再看看DDSURFACEDESC2结构,它包含两种DDCOLORKEY结构。一种称为ddcdCKSrcBlt,另一种称为ddcdCKDestBlt,填写适当的结构来创建表面。你就需要干这么多!以下是关于640×480大小的离屏表面的实例代码:
// set up surface description structure
INIT_DXSTRUCT(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CKSRCBLT;
ddsd.dwWidth = 640; // width of the surface
ddsd.dwHeight = 480; // and its height
ddsd.ddckCKSrcBlt.dwColorSpaceLowValue = RGB_16BIT(0,0,0); // color key low value
ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = RGB_16BIT(0,0,0); // color key high value
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // type of surface

// now create the surface
if (FAILED(lpdd7->CreateSurface(&ddsd, &lpddsBack, NULL)))
{
// error-handling code here
}
   关于颜色键的部分到此结束。现在我们可以进行本章最后一项了——剪切。


>>>> 进入论坛交流 <<<<