编程初学者:游戏编程起源(初学者)ⅩⅠ



★ DirectDraw位图化图形

☆ 介绍

终于你已经掌握了制作个完整游戏基础知识了只不过你现在还只能使用GDI今天我们就学习使用DirectX来执行每件你以前用GDI完成工作以及些有关DirectX其它东东具体内容是:装载()位图使用位块传输填充表面使用剪裁板、颜色键等拷贝位图
你可以在不了解前章内容基础上学习本章但象素格式是很重要我将经常直接或间接提到它所以你至少应该看看上章有关象素格式部分!^_^ 另外我假设你已经本系列、 2、 3、 4章并且拥有个DirectX SDK游戏开发平台准备好了吗?发动引擎吧女士们、先生们!

☆ 装载位图

不管你信不信确已经知道了把位图装载到DirectDraw表面大部分知识如何会这样呢?Well在Windows GDI下装载位图同在DirectDraw下极其相似只是有点点区别轻轻回忆我们曾经使用LoadImage()得到位图句柄然后把位图选入到内存设备上下文中最后利用BitBlt()把图形从内存设备上下文中拷贝到显示设备上下文中设备上下文可以用GetDC()得到如果这个承担显示任务就是DirectDraw表面(现在我们就是要用它)我们就可以针对性得到DirectDraw表面设备上下文!感谢上帝IDirectDrawSurface7接口提供了个极其简单来得到这个设备上下文:

HRESULT GetDC(HDC FAR *lphDC);

返回类型同所有DirectDraw返回类型相同如果成功参数就是个HDC类型设备上下文指针很简单吧!本章就是从把个位图装载到DirectDraw表面讲起千万要记住使用完了表面设备上下文后定要释放它哦!你可能已经想到了用表面接口ReleaseDC()完成:
HRESULT ReleaseDC(HDC hDC);
你不用回头去看有关GDI部分位图我将把适合于DirectDraw位图展现给你区别是:不是直接把设备上下文作为个参数而是用个DirectDraw表面指针取代了它然后从表面得到设备上下文用它来拷贝图形最终释放设备上下文(可能这里我说有些混乱但你看下下面代码就都明白了^_^):

LoadBitmapResource(LPDIRECTDRAWSURFACE7 lpdds, xDest, yDest, nResID)
{
HDC hSrcDC; // source DC - memory device context
HDC hDestDC; // destination DC - surface device context
HBITMAP hbitmap; // handle to the bitmap resource
BITMAP bmp; // structure for bitmap info
nHeight, nWidth; // bitmap dimensions

// first load the bitmap resource
((hbitmap = (HBITMAP)LoadImage(hinstance, MAKEINTRESOURCE(nResID), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION)) NULL)
(FALSE);

// create a DC for the bitmap to use
((hSrcDC = CreateCompatibleDC(NULL)) NULL)
(FALSE);

// select the bitmap o the DC
(SelectObject(hSrcDC, hbitmap) NULL)
{
DeleteDC(hSrcDC);
(FALSE);
}

// get image dimensions
(GetObject(hbitmap, (BITMAP), &bmp) 0)
{
DeleteDC(hSrcDC);
(FALSE);
}

nWidth = bmp.bmWidth;
nHeight = bmp.bmHeight;

// retrieve surface DC


(FAILED(lpdds->GetDC(&hDestDC)))
{
DeleteDC(hSrcDC);
(FALSE);
}

// copy image from _disibledevent=> (BitBlt(hDestDC, xDest, yDest, nWidth, nHeight, hSrcDC, 0, 0, SRCCOPY) NULL)
{
lpdds->ReleaseDC(hDestDC);
DeleteDC(hSrcDC);
(FALSE);
}

// kill the device contexts
lpdds->ReleaseDC(hDestDC);
DeleteDC(hSrcDC);

// success
(TRUE);
}

上面这段代码被设计成从资源位图但你可以很容易就把它修改成从外部文件位图或者更理想首先你从资源位图如果失败再试图从外部文件位图从外部需要记住LoadImage()时加上LR_LOADFROMFILE标志最美妙事情是BitBlt()自动完成象素格式转换举例说当我们把24-bit位图放入内存设备上下文再把它传送(拷贝)到16-bit色彩深度表面所有颜色将得到正确显示不用顾忌象素格式是555还是565很方便吧哦?
如果你要控制位图传递实际过程而不是使用BitBlt()这样简单你有两个选择你可以修改这个需要利用BITMAP结构bmBits成员它是个组成图象LPVOID指针变量第 2种思路方法如果你真想控制图象过程你可以自己编写思路是使用标准I/O来打开图象文件然后读取它要这样做你需要了解位图文件结构我们将不涉及这种编写目前对我们来说已经足够了但我还是要为你将来大展鸿图做点点铺垫^_^

☆ 位图格式

令人高兴要自己写位图个Win32结构位图头文件可以利用读取这个头文件信息用fread这样简单就可以了所有位图文件都有这样个头文件它包含了位图全部信息BITMAPFILEHEADER就是这个头文件结构名字下面是它原形:

typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType; // file type - must be \"BM\" for bitmap
DWORD bfSize; // size in s of the bitmap file
WORD bfReserved1; // must be zero
WORD bfReserved2; // must be zero
DWORD bfOffBits; // off in s from the BITMAPFILEHEADER
// structure to the bitmap bits
} BITMAPFILEHEADER;

我就不详细介绍这些成员了注释里已经说得很清楚了只要使用fread读取它们就可以了注意要检测bfType成员是否等于“BM”若是介绍说明你正在处理个有效位图在此的后有另个头文件需要读取它包含位图尺寸、压缩类型等图象信息以下是它结构:

typedef struct tagBITMAPINFOHEADER{ // bmih


DWORD biSize; // number of s required by the structure
LONG biWidth; // width of the image in pixels
LONG biHeight; // height of the image in pixels
WORD biPlanes; // number of planes for target device - must be 1
WORD biBitCount; // bits per pixel - 1, 4, 8, 16, 24, or 32
DWORD biCompression; // type of compression - BI_RGB for uncompressed
DWORD biSizeImage; // size in s of the image
LONG biXPelsPerMeter; // horizontal resolution in pixels per meter
LONG biYPelsPerMeter; // vertical resolution in pixels per meter
DWORD biClrUsed; // number of colors used
DWORD biClrImportant; // number of colors that are important
} BITMAPINFOHEADER;

只有几个成员需要解说注意压缩格式大多数位图你都需要做解压缩操作最普通位图压缩格式是run-length编码(RLE)但只能应用于4-bit或8-bit图象在此情况时成员biCompression将分别是BI_RLE4和BI_RLE8我们就不讨论这种压缩格式了但它真很简单很容易理解你如果要了解它是不会有任何麻烦^_^
第 2个对于高色彩位图biClrUsed和biClrImportant这两个成员通常设置为0所以不用太在意它们对于BI_RGB这种未压缩格式位图成员biSizeImage也将被设置为0最后针对我们其它结构成员都不是很重要我们只需要注意位图长、宽和色彩深度(biWidth、biHeight、biBitCount)
读取完了这些头文件信息后如果位图是8-bit或者以下色彩深度(也就是调色板模式)调色板信息会紧跟在这些信息的后也许出乎你意料调色板信息不是存储在PALETTEENTRY结构中而是在RGBQUAD结构中RGBQUAD结构如下:

typedef struct tagRGBQUAD { // rgbq
BYTE rgbBlue;
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
} RGBQUAD;

不要问我为什么红、绿、蓝以倒序方式排列事实就是这样!读取RGBQUAD中数据把数据传递给DirectDraw调色板记得要把每个PALETTEENTRYpeFlag设置成PC_NOCOLLAPSE
的后呢(调色板信息不定存在高彩模式下就没有)你将发现图象位(image bits)你可能会想到建立个指针在内存中分配足够空间来控制这些图象位数据然后读取它们对极了我正要这样干假设把存储在BITMAPINFOHEADER结构中信息头文件称作info图象位指针称作fptr实施代码如下:

UCHAR* buffer = (UCHAR*)malloc(info.biSizeImage);
fread(buffer, (UCHAR), info.biSizeImage, fptr);

要记住些情况下biSizeImage值可能为0所以有必要在上面代码运行前检测它如果它被设置为0你将不得不计算图象由多少个象素构成每个象素需要多少个字节
写你自己位图并非什么难事儿但你觉得不需要就用我们开始介绍思路方法好了这个话题告段落下面让我们看看DirectDraw精华:使用位块传输



☆ 使用位块传输

位块传输是显示卡操控位图数据部分你同样可以用它来进行颜色填充就像我们过会儿看到随着硬件性能提高会有很多经典窍门技巧DirectX有权使用硬件加速功能但要记住如果DirectX使用加速功能不被机器硬件支持将自动启用硬件仿真层(HEL)但这也并非万无有些功能靠硬件仿真层是无法实现(否则谁还买3D加速卡^_^)所以你需要检测你是否成功
GDI位块传输可以在DirectDraw编程中使用而且有时也确是这样做然而DirectDraw具有其自身位块传输它们通常更加适合于编程环境而且比GDI相应执行得更快DirectDraw位块传输名为Blt()和BltFast()都是有IDirectDrawSurface7接口提供两者区别处是BltFast()不处理剪切、放缩等其它Blt()做有趣事情如果在硬件仿真层上BltFast()要比Blt()快10%左右但如果有硬件加速卡支持(硬件加速卡主要就是为位块传输服务) 2者速度就差不多了而且现在大多数机器都有硬件加速卡所以我总是使用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:把动画图像块转移到前段缓存Cache时可以使用这个参数这样位块传输操作时间会和屏幕刷新率相并使画面撕裂可能性减小到最小
◎ DDBLTFX_ROTATE180:位块传输时把表面顺时针旋转180度
◎ DDBLTFX_ROTATE270:位块传输时把表面顺时针旋转270度
◎ DDBLTFX_ROTATE90:位块传输时把表面顺时针旋转90度

需要详细解释可能只有DDBLTFX_NOTEARING游戏离不开动画动画制作者主要关心通常是动画速度和性能速度太快会导致图象质量恶化光栅扫描显示系统(我们基本上用都是这种显示器)利用电子束扫描每条水平线上屏幕象素点象素行从屏幕左上角开始更新到屏幕右下角结束各象素行都被称为扫描线电子束在每行扫描线末端被关掉而电子枪重新瞄准下起始点这个过程成为水平回扫当过程执行到屏幕扫描线最后行时电子束再次被关掉电子枪重新瞄准屏幕左上角电子枪从屏幕右下角重新瞄准到左上角过程所需时间被称为垂直回归或者屏幕空白周期如果在视频控制器显示视频数据同时视频数据被CPU做了更改这时就会产生问题在PC机中屏幕刷新率通常在60Hz到100Hz的间而现在CPU则可以在每秒处理成百上千指令这样就很可能导致位于视频内存区图象在视频系统完成显示的前发生更改图象断裂结果被称为图象撕裂就我经验而言使用了DDBLTFX结构DDBLTFX_NOTEARING后图象撕裂就不是什么问题了^_^
DWORD dwROP:使用这个标志来指定Win32模式光栅操作代码同GDIBitBlt()和StretchBlt相对应参数功能可以通过IDirectDraw7::GetCaps()得到可能光栅操作列表可以通过“|”组合标志常量确定源矩形表面和目标矩形表面是怎样结合
DWORD dwRotationAngle:这是用来旋转位图角度可以旋转任意角度这是非常棒但不幸它只能在HAL层(硬件抽象层)上工作这就意味着用户显示卡要支持加速旋转否则……但不能保证每个用户都有这种高档显示卡所以你需要考虑周全如果你真需要旋转处理你只好自己写这样这可是个大话题需要另写部指南了所以我们就越过它但请注意如果是90度倍数角度你可以使用DDBLTFX_ROTATE90等这将使你回避显示卡不干活风险^_^
DWORD dwFillColor:如果你要使用位块传输来填充颜色你必须把颜色放入到这个参数中
DDCOLORKEY ddckDestColorKeyddckSrcColorKey:你要使用颜色键时必须要指定这些成员这两个家伙是很重要但暂时我们还不讨论它们我们过会儿才讲颜色键



以上这些就是DDBLTFX结构中比较有用成员了这就意味着你现在拥有足够知识进行位块传输了!如果你现在感觉有些混乱不要紧你实战过段时间就会好了让我们看看几个例子假设你已经有了个叫作lpddsBack后缓冲区你想把它里面内容传输到主表面很简单你看:

lpddsPrimary->Blt(NULL, lpddsBack, NULL, DDBLT_WAIT, NULL);

再轻轻回忆个参数和第 3个参数分别是位块传输目标矩形和源矩形由于我把它们都设置为NULL就介绍说明是对全部表面进行拷贝现在让我们在看看假设你有个在离屏表面里名字叫作lpddsTile16×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 _disibledevent=>
这个例子同上个例子区别处在和这个例子设置了位块传输坐标由于这两个矩形区大小区别Blt()依照比例适当变更了图形大小最后我们还得举例介绍说明下DDBLTFX结构就做个颜色填充例子吧假设你在16-bit色彩模式下是565象素格式你要把你后缓冲区填充为蓝色下面就是你应该做:

DDBLTFX fx;
INIT_DXSTRUCT(fx); // zero out the structure and dwSize
fx.dwFillColor = RGB_16BIT565(0, 0, 31); // fill color to blue
lpddsBack->Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx);

注意参数设置前 3个都是NULL你自己想想原因吧!^_^ 好了让我们看看另个位块传输BltFast它只是Blt()简化版本所以我们不需要太多时间解释它下面是它原形:

HRESULT BltFast(
DWORD dwX,
DWORD dwY,
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,
LPRECT lpSrcRect,
DWORD dwTrans
);

你可以看得出来它同Blt()极其相似它也是IDirectDrawSurface7接口成员被目标表面来看看它参数:
DWORD dwXdwY:这是Blt和BltFast的间区别地方是目标表面上进行位块传输x和y坐标如果源矩形大于目标矩形失败BltFaxt()不能干按比例变换事儿及其它能够通过Blt完成工作
LPDIRECTDRAWSURFACE7 lpDDSrcSurface:这是源表面同Blt()
LPRECT lpSrcRect:这个也同Blt()是在源表面中定义了矩形左上角和右下角RECT结构
DWORD dwTrans:定义了位块传输类型标志列表很简单只有 4个标志:

◎ DDBLTFAST_DESTCOLORKEY:使用目标颜色键透明位块传输
◎ DDBLTFAST_NOCOLORKEY:没有透明普通复制位块传输
◎ DDBLTFAST_SRCCOLORKEY:使用源颜色键透明位块传输
◎ DDBLTFAST_WAIT:如果位块传输忙不产生DDERR_WASSTILLDRAWING消息旦位块传输能够开始或者发生另时返回

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

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

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

☆ 颜色键


作者邮箱:[email protected] 要用英文给作者发信哦!
作者ICQ:53210499
【傻马乱踢:[email protected]
待续

Tags:  网络游戏的起源 网络游戏起源 游戏的起源 编程初学者

延伸阅读

最新评论

发表评论