?? 第11小節.txt
字號:
傻馬亂踢 譯
游戲編程起源(初學者)ⅩⅠ
★ DirectDraw的位圖化圖形
☆ 簡介
終于,你已經掌握了制作一個完整游戲的基礎知識了,只不過你現在還只能使用GDI。今天,我們就學習使用DirectX來執行每一件你以前用GDI完成的工作,以及一些關于DirectX其它的東東。具體內容是:裝載(調用)位圖,使用位塊傳輸,填充表面,使用剪裁板、顏色鍵等拷貝位圖。
你可以在不了解前一章內容的基礎上學習本章,但象素格式是很重要的,我將經常直接或間接的提到它,所以你至少應該看看上一章關于象素格式的部分!^_^ 另外,我假設你已經本系列的第一、二、三、四章,并且擁有一個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表面指針取代了它,然后函數從表面得到設備上下文,用它來拷貝圖形,最終釋放設備上下文。(可能這里我說的有些混亂,但你看一下下面的程序代碼就都明白了^_^):
int LoadBitmapResource(LPDIRECTDRAWSURFACE7 lpdds, int xDest, int yDest, int 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
int nHeight, nWidth; // bitmap dimensions
// first load the bitmap resource
if ((hbitmap = (HBITMAP)LoadImage(hinstance, MAKEINTRESOURCE(nResID), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION)) == NULL)
return(FALSE);
// create a DC for the bitmap to use
if ((hSrcDC = CreateCompatibleDC(NULL)) == NULL)
return(FALSE);
// select the bitmap into the DC
if (SelectObject(hSrcDC, hbitmap) == NULL)
{
DeleteDC(hSrcDC);
return(FALSE);
}
// get image dimensions
if (GetObject(hbitmap, sizeof(BITMAP), &bmp) == 0)
{
DeleteDC(hSrcDC);
return(FALSE);
}
nWidth = bmp.bmWidth;
nHeight = bmp.bmHeight;
// retrieve surface DC
if (FAILED(lpdds->GetDC(&hDestDC)))
{
DeleteDC(hSrcDC);
return(FALSE);
}
// copy image from one DC to the other
if (BitBlt(hDestDC, xDest, yDest, nWidth, nHeight, hSrcDC, 0, 0, SRCCOPY) == NULL)
{
lpdds->ReleaseDC(hDestDC);
DeleteDC(hSrcDC);
return(FALSE);
}
// kill the device contexts
lpdds->ReleaseDC(hDestDC);
DeleteDC(hSrcDC);
// return success
return(TRUE);
}
上面這段代碼被設計成從資源調用位圖,但你可以很容易就把它修改成從外部文件調用位圖,或者更理想的是,首先你從資源調用位圖,如果失敗,再試圖從外部文件調用位圖。從外部調用,需要記住的是調用LoadImage()函數時加上LR_LOADFROMFILE標志。最美妙的事情是,函數BitBlt()自動完成象素格式的轉換。舉例說,當我們把24-bit的位圖放入內存設備上下文,再把它傳送(拷貝)到16-bit色彩深度的表面,所有的顏色將得到正確的顯示,不用顧忌象素格式是555還是565,很方便吧,哦?
如果你要控制位圖傳遞的實際過程,而不是使用BitBlt()這樣簡單的函數,你有兩個選擇。第一個,你可以修改這個函數,需要利用BITMAP結構的bmBits成員,它是一個組成圖象的位的LPVOID指針變量。第二種方法,如果你真的想控制圖象的調用過程,你可以自己編寫函數,思路是使用標準的I/O函數來打開圖象文件,然后讀取它。要這樣做,你需要了解位圖文件的結構。我們將不涉及這種函數的編寫,因為目前的對我們來說已經足夠了,但我還是要為你將來的大展鴻圖做一點點鋪墊。^_^
☆ 位圖格式
令人高興的是,要自己寫一個調用位圖的函數,有一個Win32結構的位圖頭文件可以利用。讀取這個頭文件的信息,用fread()這樣簡單的函數就可以了。所有的位圖文件都有這樣一個頭文件,它包含了位圖的全部信息。BITMAPFILEHEADER就是這個頭文件結構的名字,下面是它的原形:
typedef struct tagBITMAPFILEHEADER { // bmfh
WORD bfType; // file type - must be "BM" for bitmap
DWORD bfSize; // size in bytes of the bitmap file
WORD bfReserved1; // must be zero
WORD bfReserved2; // must be zero
DWORD bfOffBits; // offset in bytes from the BITMAPFILEHEADER
// structure to the bitmap bits
} BITMAPFILEHEADER;
我就不詳細介紹這些成員了,因為注釋里已經說得很清楚了,只要使用fread()讀取它們就可以了。注意要檢測bfType成員是否等于字符“BM”,若是,說明你正在處理一個有效的位圖。在此之后,有另一個頭文件需要讀取,它包含位圖的尺寸、壓縮類型等圖象信息。以下是它的結構:
typedef struct tagBITMAPINFOHEADER{ // bmih
DWORD biSize; // number of bytes 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 bytes 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,我們就不討論這種壓縮格式了,但它真的很簡單,很容易理解,你如果要了解它是不會有任何麻煩的。^_^
第二個,對于高色彩的位圖,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調色板的數組。記得要把每個PALETTEENTRY的peFlag設置成PC_NOCOLLAPSE。
之后呢(調色板信息不一定存在,因為高彩模式下就沒有),你將發現圖象位(image bits),你可能會想到建立一個指針,在內存中分配足夠的空間來控制這些圖象位數據,然后讀取它們。對極了,我正要這樣干。假設把存儲在BITMAPINFOHEADER結構中的信息頭文件稱作info,你的圖象位指針稱作fptr,實施的代碼如下:
UCHAR* buffer = (UCHAR*)malloc(info.biSizeImage);
fread(buffer, sizeof(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%左右,但如果有硬件加速卡支持(硬件加速卡主要就是為位塊傳輸服務的),二者的速度就差不多了,而且現在大多數的機器都有硬件加速卡,所以我總是使用Blt()。讓我們仔細看看這個神奇的東東:
HRESULT Blt(
LPRECT lpDestRect,
LPDIRECTDRAWSURFACE7 lpDDSrcSurface,
LPRECT lpSrcRect,
DWORD dwFlags,
LPDDBLTFX lpDDBltFx
);
由于Blt()所擁有的最后一個參數,使其能做很多特殊的事兒。該參數配有一個標志常量列表,我將會向你介紹其中最有用的幾個。另外,注意在把位圖從一個表面向另一個表面傳遞時,你應該調用目的表面的Blt(),不是源表面的。好了嗎?以下是函數的參數說明:
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -