此回说说怎样用DirectDraw向表面上贴图,包括创建离屏表面,设置调色板,载入位图到表面,透明色,页面丢失等。
二. 用DirectDraw贴图DirectX学习手记(二)
先让我们回忆一下上一回的内容。为了初始化DirectDraw我们首先创建了一个DirectDraw对象,然后
设置了协作模式(全屏+独占),设置显示模式,然后创建主表面,提取后台缓冲表面指针。至此可以在后台
表面上进行操作,然后flip到前台显示出来。最后程序结束前释放所有的directdraw对象。好了,现在说说怎
样向后台表面贴图,即让屏幕显示图片。
第一步:创建离屏表面
离屏表面是你永远看不到的表面(所谓离屏),它通常被用来存放位图。通常的做法是把离屏表面
上的位图用Blt的方法贴到后台表面,后台表面再flip为前台表面。看看它的创建方法:
//声明用来存放图像的离屏表面(声明为全局变量)
LPDIRECTDRAWSURFACE lpDDSPic ;
//在InitDDraw()中创建保存图像的离屏表面
ddsd.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH ;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN ;
ddsd.dwHeight = 480 ;
ddsd.dwWidth = 640 ;
if ( lpDD->CreateSurface(&ddsd,&lpDDSPic,NULL)!=DD_OK )
return FALSE ;
首先要声明一个表面,然后填充DDSURFACEDESC表面描述结构体。dwFlags填DDSD_CAPS表示ddsCaps域有效,
DDSD_HEIGHT和DDSD_WIDTH表示要指定页面的大小。ddsCaps.dwCaps填DDSCAPS_OFFSCREENPLAIN表示创建
的是离屏表面。dwHeight和dwWidth填页面的大小,单位是像素。然后就可以用DirectDraw对象的CreateSurface
方法创建lpDDSPic了。注意CreateSurface的第三个参数必须填NULL,该参数将允许与今后的 COM 集合特性相兼容。
整个创建过程和创建主表面非常相似,只是ddsd的填充不同。这里稍微谈一下离屏表面的大小问题。
在 DirectX 5.0 以前的版本中,离屏表面的宽度的最大值不能超出于主表面的宽度.而从 DirectX 5.0 版开始,你
可以创建任何宽度的离屏页面,只要你的显存和内存足够大.缺省情况下,离屏表面是被创建在显存中的,但如果
显存不够用,它就会被置于内存中,当然你可以指明只在显存中创建表面(可在 DDSCAPS 结构的 dwCaps 成员中
包含进 DDSCAPS_SYSTEMMEMORY 或 DDSCAPS_VIDEOMEMORY 标志符,以明确的表明你希望将表面置于何处),如果
这样的话,当表面太大而超出显存的容纳范围时,就会创建失败。
另外,以上面的方法创建的离屏表面的像素格式(可以简单理解为表示几位的颜色)是和主表面相同的,当然
也可以创建一个像素格式与主表面不同的离屏表面.然而,在这种情况下,该离屏表面将被限制于系统内存中。具体的
做法有点烦,各位感兴趣的可以查一下手册,:) 。
第二步:读入调色板并设置上
我们的主表面被设置为基于调色板(8位色深度)。一个基于调色板的表面是一些数字的集合,
其中的每一个数字代表一个像素.每一个数字的值都对应于一个色彩表(color table)中的项,这个表告诉 DirectDraw
对这个像素使用什么样的颜色. 这个表就是调色板(Palette)。使用调色板是为了尽量降低对显存的需求,它用一个
颜色索引(Color Index)来代表各个像素点的颜色,而不是直接用红,绿,蓝三基色的亮度值来确定每个像素点的颜色。
调色板包含了若干颜色索引和该索引所对应的真实颜色值。调色板的颜色索引主要采用4或8两种位深度。要在表面上
正常显示图像就必须将表面的调色板设置为该图像的调色板。如下面的代码片断:
//先声明调色板对象(其实是指针类型,就这么说吧,上面提到的各个对象都是这样的)
LPDIRECTDRAWPALETTE lpDDPal ;
//从位图设置调色板
lpDDPal = DDLoadPalette(lpDD, szBitmap);
if (lpDDPal)
lpDDSPrimary->SetPalette(lpDDPal);
else
return FALSE ;
DDLoadPalette是DDutil.cpp里面的函数,happyfire也不大懂,作为初学者拿来用就是了。
第三步:将位图文件读入已创建好的离屏表面中
这一步我们用DirectDraw APIs里面的一个函数DDLoadBitmap来实现,具体用法是:
lpDDSPic = DDLoadBitmap ( lpDD, szBitmap, 0, 0 ) ; //szBitmap是位图文件名,例如".\\pic\\background.bmp"
使用了DDLoadBitmap这一步变得非常简单,但是要把Ddutil.cpp和Ddutil.h加到工程中才行。你可以看看
Ddutil.cpp中该函数的实现,它调用了Windows的API LoadImage,并且用作为参数传进来的directdraw对象
创建了一个表面并返回了它的指针。
第四步:使用blit的方法,将离屏表面上的图片传送到后台缓冲
何谓blit?Bit block transfer(位块传送),即将内存中的数据块从一处传送到另一处。DirectDraw
提供了两个方法,Blt和BltFast。Blt功能强大,BltFast速度较快。这里我们先不讨论Blt,只用BltFast。看如何实现:
RECT rcRect ;
rcRect.left= 0;
rcRect.top= 0 ;
rcRect.right= 640;
rcRect.bottom=480;
lpddsBack->BltFast( 0, 0, lpddsPic, &rcRect, DDBLTFAST_SRCCOLORKEY|DDBLTFAST_WAIT);
RECT是windows的矩形结构(所以要包含windows.h),rcRect这个矩形用来确定源表面(这里是离屏表面)上的哪
一块区域要被传送到目标表面(这里是后台缓冲表面)上去。BltFast的前两个参数是两个DWORD值,表示目标表面
上的一个点的坐标x和y,它决定了传送过去的图形在目标表面上所处的位置。你可以改变x,y和rcRect看看效果。
最后一个参数是传送类型:DirectX学习手记(二)
DDBLTFAST_DESTCOLORKEY
指定进行一次带透明的位块传送,使用目标表面的关键色(color key)。
DDBLTFAST_NOCOLORKEY
指定进行一次普通的复制,不带透明成分。
DDBLTFAST_SRCCOLORKEY
指定进行一次带透明的位块传送,使用源表面的关键色。
DDBLTFAST_WAIT
如果位块传送器正忙,延迟 DDERR_WASSTILLDRAWING 消息的发送,直到位块传送器准备好或发生其它错误时才返回。
看看这个color key,这可是很有用的!所谓关键色,即我们说的透明色,如果在源表面上指定了一个颜色为关键色,
那么在blit操作中,将视具有这种颜色的区域为透明,不会被传送到目标页面上。这样的话,虽然传送过去的是一个
矩形,但矩形上是关键色的部分是不会传送的,从而看上去是有轮廓的图形,比如一个精灵。
当然必须先为一个表面设置关键色才行。在InitDDraw中我们用DDSetColorKey ( lpDDSPic, RGB(255,0,255) ) ;为
创建好的离屏表面lpDDSPic设置了紫红色的关键色。你可以在图片中画一个精灵,然后把所有非精灵的部分用紫红色
填充。然后将BltFast的最后一个参数设为DDBLTFAST_SRCCOLORKEY.这样传送过去的就是一个精灵的样子了。
用DDBLTFAST_DESTCOLORKEY可以为目标表面设置关键色,有所不同的是,目标表面上颜色只有为关键色才能被覆盖,即
染色。
最后在说说目标表面和原表面的区别。其实它们都是相对的。调用BltFast的为目标表面,作为参数的为源表面。一般的
用法,后台表面调用BltFast,参数为离屏表面。
贴图的问题就说这么多吧,其实这里面的内容还是挺多的。还有非调色板模式的16位,24位,32位RGB格式,16位RGB对应于
不同的显卡还有555,565两种模式。但我们是初学者嘛,这些东西...:) 有待研究。(如果你真的立志于作游戏,呵呵,
准备学汇编吧,以后还要和MMX处理器的指令什么的打交道啊)
最后要说的是第四步的操作要放在MainLoop中,这样让它不停的Blt再Flip,如果你在程序中设计按了某个键贴不同的图并改
变贴图的位置,就可以做出精灵动画了,基本的原理就是这样的。
三.浅谈表面丢失的处理
如果你的程序从全屏模式下用Alt+Tab切出去,再切回来,你可能发现图片不见了。因为当代表页面内存的
DirectDrawSurface对象被不得已的释放时,与该对象相关联的页面内存也会被释放。当一个DirectDrawSurface对象丢失其
页面内存的时候,它的许多函数将返回DDERR_SURFACELOST,并且不进行任何其它操作。先看一下这个片断:
while (1){
hRst = lpDDSPrimary->Flip(NULL,0) ;
if ( hRst==DD_OK )
break ;
if ( hRst==DDERR_SURFACELOST ){
if ( RestoreAllDDS()!=DD_OK )
break ;
}
if (hRst != DDERR_WASSTILLDRAWING){
break;
}
}
上回flip中没提到flip有失败的可能,上面的片断是解决的方法。如果flip返回DDERR_WASSTILLDRAWING,那么是由于上一次flip
操作尚未完成(flip指令已发出,但directX还没有完成操作),我们可以做的只能是让while循环下去,直到
hRst != DDERR_WASSTILLDRAWING。如果flip返回DD_OK,那表示flip操作完成,可以break出去了。如果flip返回
DDERR_SURFACELOST,表示发生了表面失效,这时我们需要自己处理它。看看我的RestoreAlDDS()函数是怎么工作的:
HRESULT RestoreAllDDS( void )
{
HRESULT hRst ;
hRst = lpDDSPrimary->Restore();
if (hRst)
hRst = lpDDSBack->Restore () ;
if (hRst)
hRst = lpDDSPic->Restore() ;
DDReLoadBitmap ( lpDDSPic,szBitmap ) ;
return hRst ;
}
在这个函数里面,我们先后调用了主表面,后台表面,离屏表面的Restore方法。Restore方法可以为这些丢失了内存的页面重新
分配内存,并且将这些内存与DirectDrawSurface对象联系上。但重建内存并不会使以前存在于该页面上的图象重新显现出来,
因此在调用Restore函数重建之后,必须亲手重新绘制所有的图象。在这里我用了DDReLoadBitmap函数,这个函数也是
Ddutil.cpp里面的。需要说明的是,如果用DDLoadBitmap代替DDReLoadBitmap是不行的!你可以看看这两个函数的实现,比较一下。DirectX学习手记(二)