转自http://blog.csdn.net/upwaker/article/details/43391 && http://dev.gameres.com/Program/Visual/2D/DDrawZL_1.htm
这并非哗众取宠, 通常学习一种电脑技术有两种方法. 一种是自己摸索, 在错误的方向上一错再错, 屡战屡败, 不过最后得道成功. 另一种是有人 或好的材料指导, 因而事半功倍, 在正确的方向上走了速成的捷径. 就象KFC 的鸡一样. 第一种学法能学出电脑天才, 因为所谓电脑高手, 其实就是排错试错的高手. 而第二种则出电脑专才. 这个两小时(?)的学习, 不能使你深入的掌握DD, 不过可以给你编制DD的框架. 能给你 一个起始点, 这个教程就算成功了.
DirectDraw编程需要一些背景知识:
DirectDraw是为在 Windows95/NT 下实现高速图形显示所写的程式库.
高速图形显示的基本方法是用一种叫做 Page Flipping的技术. 关于什么是 Page Flipping, 参见古技介绍.如果你 不急的话, 看到下面, 你也会看到.
在 Windows95/NT下做 Page Flipping 分为全屏的和窗口的两种. 在全屏下Page Flipping 叫做Flip, 在窗口下叫做 Blit.
知道了这些背景知识, 我们可以开始写程式了.
写所有 DirectDraw的程式, 差不多都有以下几个步骤,
1. 初始化, 这是每个程式都需要的劳什子.
2. 设置显示模式.
3. 在内存里建立PageFlipping所需要的两个页, 前页和后页.
4. 给显示的区域加个画框以免画到外面来.
5. 在后页画图, 然后"刷"的一下子换到前页来.
步骤一: 初始化
DirectDraw 是一个面向对象的函数库. "面向对象"的意思并不是指面对著你的女朋友, "对象" 在这里, 你可以简单地想象成是一个模板, 比方说,"政府", 一旦你说:"我成立了一个政府". 别人 就会立即把你套入"政府模板", 自然而然地认为你有印钞票的功能. 在我们的程式里, 你一旦声明 一个变量(比如 myDD)是 DirectDraw对象 (DirectDraw对象的正式名为 LPDIRECTDRAW) , 这个myDD就有了 DirectDraw对象的所有的功能和特性. 定义的语法是:
LPDIRECTDRAW pMyDD;除了 DD的对象外, 还有几个重要的对象, "页面", "裁剪板" 和 "调色板". "页对象"用来定义"前页"和"后页". 定义如下:
LPDIRECTDRAWSURFACE pMyDDSFront; //前页 LPDIRECTDRAWSURFACE pMyDDSBack; //后页一个"裁剪板对象", 在窗口模式下用来剪去画出窗口边界的部份.
LPDIRECTDRAWCLIPPER pMyClipper; //裁剪板对象"调色板"设定屏幕的颜色表, 在读取256色的 Bitmap时要用到.
LPDIRECTDRAWPALETTE myDDPal; //调色板最最重要的"对象"就是这些了. 当然 DirectX还有很多复杂晦涩的对象. 这是速成不起来的.
HWND myWnd //窗口初始化的工作还没有完, 我们要把这些对象指向一个安全的地方 Null.
pMyDD = NULL; pMyDDSFront = NULL; pMyDDSBack = NULL; pMyClipper = NULL; pMyDDPal=NULL;最后, 在 Windows系统为我们的 myDD对象开辟相应的区域:
DirectDrawCreate(NULL, //用当前的显示驱动 &pMyDD, NULL))kay, 烦人的初始化总算完了.
pMyDD->SetCooperativeLevel(AfxGetMainWnd()->GetSafeHwnd(),DDSCL_NORMAL);而在"全屏"模式下这样设置:
pMyDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE | DDSCL_FULLSCREEN );如果它们的返回值为 DD_OK表示成功. 我们就可以把屏幕调节成我们想要的样子, 例如 640x480x8. 也就是256色. 究竟有那些屏幕模式可用取决于你的显示卡
pMyDD->SetDisplayMode( 640, 480, 8 );现在, 我们已经有了一个屏幕, 不过还不能在上面画画, 我们需要步骤三来 替我们建立一个可供画画涂涂用的画板.
步骤三: 建立前后页(两块画板).
两块画板的好处是可以一边在一块上面画, 一边给别人看已经画好的另一块. 等这块画好了, 两块板就对调一下, 让别人看新画好的这块. 如果画的足够快, 换的足够快. 看的人就会看到动画了, 就象电影的效果一样. 我们把这叫做 Page Flipping.
这里先要介绍的是怎样在系统中建立两块画板( double buffering), 不过你也可以根据需要建立三块,四块画板.
{ DDSURFACEDESC ddsd; //这个结构描述"页"的特徵. ddsd.dwFlags = DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;//指定我们用的是前页. ddsd.dwSize = sizeof(ddsd); //尺寸 // 做前页: HRESULT result; result = pMyDD->CreateSurface(&ddsd, &pMyDDSFront, NULL); // 当发生错误时, 要记得 Release对象. if (result!=DD_OK) { pMyDD->Release(); pMyDD = NULL; } ddsd.dwWidth = scr_width; //设定后页的大小, ddsd.dwHeight = scr_height; //指定 我们要后页 ddsd.dwFlags = DDSD_WIDTH | DDSD_HEIGHT | DDSD_CAPS; ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; //做后页 result = pMyDD->CreateSurface(&ddsd, &pMyDDSBack, NULL); }
result = pMyDD->CreateClipper(0, &pMyClipper, NULL);创建后,把它套到窗口上去, 所以要知道是那一个窗口( Handle).
myWnd = AfxGetMainWnd()->GetSafeHwnd();// 从系统中拿到窗口的 Handle result = pMyClipper->SetHWnd(0, myWnd); // 把剪贴板加到窗口上去 result = pMyDDSFront->SetClipper(myClipper);
// 如果前页的内存被 Windows"征用"了, 这里把它要回来. 这个检察常常会被忘记. if (pMyDDSFront->IsLost() == DDERR_SURFACELOST) pMyDDSFront->Restore(); // DirectDraw 用来互换的语句有 Blt和 BltFast. BltFast据称比 Blt快10%. result = pMyDDSFront->Blt(&rcTo, pMyDDSBack, &rcFrom, DDBLT_WAIT,NULL); result = pMyDDSFront->BltFast( 0, 0, pMyDDSBack, &rcFrom, DDBLTFAST_SRCCOLORKEY);如果程式工作在"全屏"模式下. 前后页互换容易得多, 只是一句:
result = pMyDDSFront->Flip( NULL, 0 );
DirectDraw就是DirectX5的6个组件之一。DirectX5的其它5个组件分别是:
Direct3D | 提供了3D硬件接口。 |
DirectSound | 立体声和3D声音效果,同时管理声卡的内存。 |
DirectPlay | 支持开发多人网络游戏,并能处理游戏中网络之间的通信问题。 |
DirectInput | 为大量的设备提供输入支持。 |
DirectSetup | 自动安装DirectX驱动程序。 |
当翻页后,将原后备缓冲区页中的内容copy入可见主表面页,而同时将原可见主表面页的内容copy入后备缓冲区页。
显示器屏幕虽然每秒中刷新很多次,在此我们假定为85次,但每次都是一遍一遍地读取可见主表面中存储的显示页信息,而你对后备缓冲区的改动不会显示出来,并且也不会影响可见主表面的显示,而只有当施行翻页操作后,两页的内容互换,而你已经完成了的在原后备缓冲区的改动才会显示在屏幕上,而这个互相拷贝的过程几乎是瞬间完成的,这个时间比起每次刷新所用的时间少得多得多,两者几乎差了几乎几十万个数量级。而人眼是根本察觉不到的,所以用这种方法可以不闪烁,平滑,优质的动画效果
还有一种表面叫离屏表面(off_screen surface),它是不能直接见到的。离屏表面作为存储缓冲区,有助于表面之间的互相切换,它的大小是可以改变的。
主表面和离屏表面都分为有调色板的和无调色板的这两类。像素深度为8位(256色)的表面称为有调色板的表面;而像素深度为16位(64K色)、24位(16M色)的像素表面称为无调色板的表面,它们存储实际的色彩值(RGB值)。
在本文下面的程序中,我们先使用256位表面即有调色板的表面。
2. Bltting
Bltting是用于复制图形的语言,可以将图像从一处拷贝到另一处。例如大家所熟悉的CDC类(设备描述表类)的BitBlt()就是具有这样功能的函数。
在DirectDraw中,典型的blt操作是将离屏表面的内容拷贝到一个后备缓冲区,而一般的blt操作调用一个源表面和一个目标表面,把源表面的内容拷贝到目标表面中,不仅可以整体拷贝源表面,而且还可以拷贝源表面内的任何矩形区域到目标表面的任何位置。blt还支持透明拷贝,就是指表面中的某一像素在blt过程中可以不予以拷贝,而这个像素值是由色彩键码(DDCOLOR KEY )决定的。如图:
DirectDraw中有三个支持blt的函数,它们是
Blt()
BltBatch()
BltFast()
Blt( )用得最多,BltFast()的速度比Blt()要快,但功能却很有限,例如不支持拉伸、剪切等操作。
还有一个函数BltSurface(),它是DirectWin类的一个成员函数,Blt()、BltFast()更具有适应性,并且使用起来更加简单。例如,当我们把源表面拷贝到目标表面外时需要裁剪,而BltFast()不支持裁剪。这时我们使用BltSurface()函数,它在内部使用Blt()和 BltSurface()函数,并根据情况自动执行裁剪。
在此,有人会问,这种页面切换的方法到底好在那里?它到底与用一般的绘图方法的区别在什么地方?而其它的绘图方法为什么会使屏幕闪动呢?下面我们举二个例子:(我们仍然用图示的方法给您一个直观的解释)
(1) 在一些绘图程序中您会发现其用到了清屏函数,这种方式是使屏幕效果最差的,这种方法使得没有直接从第一个画面切换到第二个画面,而是先用黑色将图形数据缓冲区清空,并且还显示在屏幕上,但这个时间很短。如果反复清屏,则会产生严重的屏幕闪烁。如下图:
(2)在一些程序中用异或的方式进行绘图,即先在屏幕上画出第一个图像,然后在画第二个图像之前,再在屏幕上画一遍第一个图像,这样起到清除第一个图像的效果。如果您仔细看,会发现这样就会比上一个方法的画面效果好得多,但这并不是很完美,因为如果反复使用,虽然不会产生全屏幕的闪动,但在所清除图像处会产生闪烁。究其根本原因还是因其没能直接从第一个画面切换到第二个画面。如下图:
我想现在您应明白DirectDraw绘图的优势了吧,其实这就是对上述方法中缺点的很好的解决方法。
3.色彩键码
DirectDraw 可以把某种颜色或某个范围的颜色指定为一个颜色值,这个颜色值是由DDCOLORKEY结构即色彩键码说明的,DDCORLORKEY结构说明如下:
typedef struct _DDCOLORKEY { DWORD dwColorSpaceLowValue; //颜色范围的低端 DWORD dwColorSpaceHighValue; //颜色范围的高端 } DDCOLORKEY;当我们对表面进行拷贝操作时,表面中哪些像素不被拷贝是由色彩键码决定的。例如当DDCOLORKEY结构的两个分量都为零时,表面内所有置为零的像素都不能被拷贝。又例如,当表面是24位RGB模式时,若想指定RGB=(120,120,120)像素不被拷贝,则应该:
DDCOLORKEY ddck; ddck.dwColorSpaceLowValue=RGB(120,120,120); ddck.dwColorSpaceHighValue=RGB(120,120,120); surf→SetColorKey(DDCKEY_SRCBLT,&ddck);