首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Windows DirectX 程式設計:記憶體載入圖片與離屏表面實作

     編輯:關於C++

DirectX 圖片載入與離屏表面技術

開發環境:Visual Studio 2015,語言:C++。熟悉 Cocos2d-x 的開發者對 Sprite 不陌生——本質上就是將圖片從磁碟載入記憶體並繪製到螢幕。本文示範如何在 DirectX Windows 程式中實現同樣效果。

準備工作:BMP 圖片格式

範例使用 24 位元 BMP 格式。需注意:普通圖片格式無法直接使用,需透過特定工具(文章提供百度網盤連結)繪製並儲存為 300×300 的 BMP 檔案。

樹的 BMP 圖片

核心程式碼:載入 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 載入。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved