開發環境:Visual Studio 2015,語言:C++。熟悉 Cocos2d-x 的開發者對 Sprite 不陌生——本質上就是將圖片從磁碟載入記憶體並繪製到螢幕。本文示範如何在 DirectX Windows 程式中實現同樣效果。
範例使用 24 位元 BMP 格式。需注意:普通圖片格式無法直接使用,需透過特定工具(文章提供百度網盤連結)繪製並儲存為 300×300 的 BMP 檔案。

#define BITMAP_ID 0x4D42
typedef struct BITMAP_FILE_TAG {
BITMAPFILEHEADER bitmapfileheader;
BITMAPINFOHEADER bitmapinfoheader;
PALETTEENTRY palette[256];
UCHAR *buffer;
} BITMAP_FILE, *BITMAP_FILE_PTR;
BITMAP_FILE_PTR picture1;
// BMP 影像垂直翻轉(BMP 為自下而上儲存)
int Flip_Bitmap(UCHAR *image, int bytes_per_line, int height) {
UCHAR* buffer = (UCHAR*)malloc(bytes_per_line * height);
if (!buffer) return 0;
memcpy(buffer, image, bytes_per_line * height);
for (int index = 0; index < height; ++index) {
memcpy(&image[((height - 1) - index) * bytes_per_line],
&buffer[index * bytes_per_line], bytes_per_line);
}
free(buffer);
return 1;
}
// 載入 BMP 檔案
int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char* filename) {
int file_handle;
OFSTRUCT file_data;
if (-1 == (file_handle = OpenFile(filename, &file_data, OF_READ))) return 0;
_lread(file_handle, &bitmap->bitmapfileheader, sizeof(BITMAPFILEHEADER));
if (bitmap->bitmapfileheader.bfType != BITMAP_ID) {
_lclose(file_handle); return 0;
}
_lread(file_handle, &bitmap->bitmapinfoheader, sizeof(BITMAPINFOHEADER));
_llseek(file_handle, -(int)(bitmap->bitmapinfoheader.biSizeImage), SEEK_END);
if (bitmap->bitmapinfoheader.biBitCount == 24) {
if (bitmap->buffer) free(bitmap->buffer);
bitmap->buffer = (UCHAR*)malloc(bitmap->bitmapinfoheader.biSizeImage);
if (!bitmap->buffer) { _lclose(file_handle); return 0; }
_lread(file_handle, bitmap->buffer, bitmap->bitmapinfoheader.biSizeImage);
} else {
_lclose(file_handle); return 0;
}
_lclose(file_handle);
Flip_Bitmap(bitmap->buffer,
bitmap->bitmapinfoheader.biWidth * (bitmap->bitmapinfoheader.biBitCount / 8),
bitmap->bitmapinfoheader.biHeight);
return 1;
}
注意:_lread、_llseek 是 Windows API,非 C 執行階段庫的 io.h 函式,切勿混淆。
int Game_Main(void* params = NULL) {
if (KEYDOWN(VK_ESCAPE)) PostMessage(main_window_handle, WM_CLOSE, 0, 0);
DDRAW_INIT_STRUCT(ddsd);
if (FAILED(lpddsback->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL))) return 0;
UINT *video_buffer = (UINT*)ddsd.lpSurface;
// 清除背景為白色
for (int x = 0; x < 640; ++x)
for (int y = 0; y < 480; ++y)
Plot_Pixel_Fast32_2(x, y, 255, 255, 255, 128, video_buffer, ddsd.lPitch);
// 繪製圖片
int pos_x = 170, pos_y = 180;
for (int x = pos_x; x < 300 + pos_x; ++x)
for (int y = pos_y; y < 300 + pos_y; ++y)
Plot_Pixel_Fast32_2(x, y,
picture1->buffer[(y-pos_y) * 300 * 3 + (x-pos_x)*3 + 2], // R
picture1->buffer[(y-pos_y) * 300 * 3 + (x-pos_x)*3 + 1], // G
picture1->buffer[(y-pos_y) * 300 * 3 + (x-pos_x)*3 + 0], // B
0, video_buffer, ddsd.lPitch);
lpddsback->Unlock(NULL);
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));
return 1;
}


因 24 位元 BMP 無 Alpha 通道,雲朵會遮蓋樹木。解決方案是指定透明色(Color Key),DirectX 會自動將該顏色設為透明。
前面範例將圖片置於系統記憶體,每幀需搬運像素到顯存,效率較低。離屏表面 同樣是一塊緩衝區,但位於顯存,繪圖效率大幅提升。
LPDIRECTDRAWSURFACE7 lpdds_off = NULL;
int Game_Init(void* params = NULL) {
// ... 基礎設定與載入圖片略 ...
DDRAW_INIT_STRUCT(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
ddsd.dwWidth = 300;
ddsd.dwHeight = 300;
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_VIDEOMEMORY; // 關鍵:置於顯存
if (FAILED(lpdd->CreateSurface(&ddsd, &lpdds_off, NULL))) return 0;
// 將圖片像素複製到離屏表面
lpdds_off->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);
UINT *buffer = (UINT*)ddsd.lpSurface;
for (int x = 0; x < 300; ++x)
for (int y = 0; y < 300; ++y)
Plot_Pixel_Fast32_2(x, y,
picture2->buffer[y * 300 * 3 + x * 3 + 2],
picture2->buffer[y * 300 * 3 + x * 3 + 1],
picture2->buffer[y * 300 * 3 + x * 3 + 0],
0, buffer, ddsd.lPitch);
lpdds_off->Unlock(NULL);
return 1;
}
int Game_Main(void* params = NULL) {
// ... 前置程式碼略 ...
RECT dest_rect = {pos_x, pos_y, pos_x + 299, pos_y + 299};
RECT src_rect = {0, 0, 299, 299};
// 使用 Blt 將離屏表面高速複製到後備表面
if (FAILED(lpddsback->Blt(&dest_rect, lpdds_off, &src_rect, DDBLT_WAIT, NULL))) return 0;
while (FAILED(lpddsprimary->Flip(NULL, DDFLIP_WAIT)));
return 1;
}
關鍵點:Blt 內部已處理鎖定/解鎖,呼叫端不可重複 Lock/Unlock。視覺結果相同,但像素處理已全在顯存完成,效能顯著提升。
書中尚有視窗化模式實作,但在 Windows 10 上表現不佳,暫不介紹。下一步可探討 32 位元圖片(含 Alpha)或 PNG 載入。