天天看點

OpenGL采用顯示清單顯示文字完整範例

(一)顯示英文 OpenGL并沒有直接提供顯示文字的功能,并且, OpenGL也沒有自帶專門的字庫。是以,要顯示文字,就必須依賴作業系統所提供的功能了。

各種流行的圖形作業系統,例如 Windows系統和 Linux系統,都提供了一些功能,以便能夠在 OpenGL程式中友善的顯示文字。

最常見的方法就是,我們給出一個字元,給出一個顯示清單編号,然後作業系統由把繪制這個字元的 OpenGL指令裝到指定的顯示清單中。當需要繪制字元的時候,我們隻需要調用這個顯示清單即可。假如我們要顯示的文字全部是 ASCII字元,則總共隻有 0到 127這 128種可能,是以可以預先把所有的字元分别裝到對應的顯示清單中,然後在需要時調用這些顯示清單。

Windows系統中,可以使用wglUseFontBitmaps函數來批量的産生顯示字元用的顯示清單。函數有四個參數:

第一個參數是HDC,學過Windows GDI的朋友應該會熟悉這個。如果沒有學過,那也沒關系,隻要知道調用wglGetCurrentDC函數,就可以得到一個HDC了。具體的情況可以看下面的代碼。

第二個參數表示第一個要産生的字元,因為我們要産生0到127的字元的顯示清單,是以這裡填0。

第三個參數表示要産生字元的總個數,因為我們要産生0到127的字元的顯示清單,總共有128個字元,是以這裡填128。

第四個參數表示第一個字元所對應顯示清單的編号。假如這裡填1000,則第一個字元的繪制指令将被裝到第1000号顯示清單,第二個字元的繪制指令将被裝到第1001号顯示清單,依次類推。我們可以先用glGenLists申請128個連續的顯示清單編号,然後把第一個顯示清單編号填在這裡。

還要說明一下,因為wglUseFontBitmaps是Windows系統特有的函數,是以在使用前需要加入頭檔案:#include <windows.h>。

現在讓我們來看具體的代碼:

#include <windows.h>

// ASCII字元總共隻有0到127,一共128種字元

#define MAX_CHAR       128

void drawString(const char* str) {

    static int isFirstCall = 1;

    static GLuint lists;

    if( isFirstCall ) { // 如果是第一次調用,執行初始化

                        // 為每一個ASCII字元産生一個顯示清單

        isFirstCall = 0;

        // 申請MAX_CHAR個連續的顯示清單編号

        lists = glGenLists(MAX_CHAR);   //編号分别是lists, lists + 1, lists + 2, lists + MAX_CHAR -1

        // 把每個字元的繪制指令都裝到對應的顯示清單中

        wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);  //從基數lists開始依次顯示各個字元

    }

    // 調用每個字元對應的顯示清單,繪制每個字元

    for(; *str!='\0'; ++str)

        glCallList(lists + *str);

}

顯示清單一旦産生就一直存在(除非調用glDeleteLists銷毀),是以我們隻需要在第一次調用的時候初始化,以後就可以很友善的調用這些顯示清單來繪制字元了。

繪制字元的時候,可以先用glColor*等指定顔色,然後用glRasterPos*指定位置,最後調用顯示清單來繪制。

void display(void) {

    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0f, 0.0f, 0.0f);

    glRasterPos2f(0.0f, 0.0f);

    drawString("Hello, World!");

    glutSwapBuffers();

}

指定字型

在産生顯示清單前,Windows允許選擇字型。

我做了一個selectFont函數來實作它,大家可以看看代碼。

void selectFont(int size, int charset, const char* face) {

    HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,

        charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,

        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);

    HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);

    DeleteObject(hOldFont);

}

void display(void) {

    selectFont(48, ANSI_CHARSET, "Comic Sans MS");

    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0f, 0.0f, 0.0f);

    glRasterPos2f(0.0f, 0.0f);

    drawString("Hello, World!");

    glutSwapBuffers();

}

完整的源碼:

#include <GL/glut.h>

#include <windows.h>

#define MAX_CHAR       128

void drawString(const char* str) {

    static int isFirstCall = 1;

    static GLuint lists;

    if( isFirstCall ) { // 如果是第一次調用,執行初始化

// 為每一個ASCII字元産生一個顯示清單

        isFirstCall = 0;

        // 申請MAX_CHAR個連續的顯示清單編号

        lists = glGenLists(MAX_CHAR);

        // 把每個字元的繪制指令都裝到對應的顯示清單中

        wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);

    }

    // 調用每個字元對應的顯示清單,繪制每個字元

    for(; *str!='\0'; ++str)

        glCallList(lists + *str);

}

void selectFont(int size, int charset, const char* face) {

    HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,

        charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,

        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);

    HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);

    DeleteObject(hOldFont);

}

void display(void) {

    selectFont(48, ANSI_CHARSET, "Comic Sans MS");

    glClear(GL_COLOR_BUFFER_BIT);

    glColor3f(1.0f, 0.0f, 0.0f);

    glRasterPos2f(0.0f, 0.0f);

    drawString("Hello, World!");

    glutSwapBuffers();

}

void init (void) 

{

glClearColor (0.0, 0.0, 0.0, 0.0);

//  glMatrixMode(GL_PROJECTION);

//  glLoadIdentity();

//  glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); //注意該視景體的範圍和幾何中心

}

int main(int argc, char** argv)

{

glutInit(&argc, argv);

glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);

glutInitWindowSize (500, 500);  //改為glutInitWindowSize (250, 250); 可以看出變換的僅僅是物體按比例大小的變換

glutInitWindowPosition (100, 100);

glutCreateWindow ("First");

init ();

glutDisplayFunc(display); 

glutMainLoop();

return 0; 

}

最主要的部分就在于那個參數超多的 CreateFont函數,學過 Windows GDI的朋友應該不會陌生。沒有學過 GDI的朋友,有興趣的話可以自己翻翻 MSDN文檔。這裡我并不準備仔細講這些參數了,下面的内容還多着呢 :(

如果需要在自己的程式中選擇字型的話,把 selectFont函數抄下來,在調用 glutCreateWindow之後、在調用 wglUseFontBitmaps之前使用 selectFont函數即可指定字型。函數的三個參數分别表示了字型大小、字元集(英文字型可以用 ANSI_CHARSET,簡體中文字型可以用 GB2312_CHARSET,繁體中文字型可以用 CHINESEBIG5_CHARSET,對于中文的 Windows系統,也可以直接用 DEFAULT_CHARSET表示預設字元集)、字型名稱。

(二)顯示中文

原則上,顯示中文和顯示英文并無不同,同樣是把要顯示的字元做成顯示清單,然後進行調用。

但是有一個問題,英文字母很少,最多隻有幾百個,為每個字母建立一個顯示清單,沒有問題。但是漢字有非常多個,如果每個漢字都産生一個顯示清單,這是不切實際的。

我們不能在初始化時就為每個字元建立一個顯示清單,那就隻有在每次繪制字元時建立它了。當我們需要繪制一個字元時,建立對應的顯示清單,等繪制完畢後,再将它銷毀。

這裡還經常涉及到中文亂碼的問題,我對這個問題也不甚了解,但是網上流傳的版本中,使用了MultiByteToWideChar這個函數的,基本上都沒有出現亂碼,是以我也準備用這個函數:)

通常我們在C語言裡面使用的字元串,如果中英文混合的話,例如“this is 中文字元.”,則英文字元隻占用一個位元組,而中文字元則占用兩個位元組。用MultiByteToWideChar函數,可以轉化為所有的字元都占兩個位元組(同時解決了前面所說的亂碼問題:))。

轉化的代碼如下:

// 計算字元的個數

// 如果是雙位元組字元的(比如中文字元),兩個位元組才算一個字元

// 否則一個位元組算一個字元

len = 0;

for(i=0; str[i]!='\0'; ++i)

{

    if( IsDBCSLeadByte(str[i]) )

        ++i;

    ++len;

}

// 将混合字元轉化為寬字元

wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));

MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);

wstring[len] = L'\0';

// 用完後記得釋放記憶體

free(wstring);

加上前面所講到的wglUseFontBitmaps函數,即可顯示中文字元了。

void drawCNString(const char* str) {

    int len, i;

    wchar_t* wstring;

    HDC hDC = wglGetCurrentDC();

    GLuint list = glGenLists(1);

    // 計算字元的個數

    // 如果是雙位元組字元的(比如中文字元),兩個位元組才算一個字元

    // 否則一個位元組算一個字元

    len = 0;

    for(i=0; str[i]!='\0'; ++i)

    {

        if( IsDBCSLeadByte(str[i]) )

            ++i;

        ++len;

    }

    // 将混合字元轉化為寬字元

    wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));

    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);

    wstring[len] = L'\0';

    // 逐個輸出字元

    for(i=0; i<len; ++i)

    {

        wglUseFontBitmapsW(hDC, wstring[i], 1, list);

        glCallList(list);

    }

    // 回收所有臨時資源

    free(wstring);

    glDeleteLists(list, 1);

}

注意我用了wglUseFontBitmapsW函數,而不是wglUseFontBitmaps。wglUseFontBitmapsW是wglUseFontBitmaps函數的寬字元版本,它認為字元都占兩個位元組。因為這裡使用了MultiByteToWideChar,每個字元其實是占兩個位元組的,是以應該用wglUseFontBitmapsW。

void display(void) {

    glClear(GL_COLOR_BUFFER_BIT);

    selectFont(48, ANSI_CHARSET, "Comic Sans MS");

    glColor3f(1.0f, 0.0f, 0.0f);

    glRasterPos2f(-0.7f, 0.4f);

    drawString("Hello, World!");

    selectFont(48, GB2312_CHARSET, "楷體_GB2312");

    glColor3f(1.0f, 1.0f, 0.0f);

    glRasterPos2f(-0.7f, -0.1f);

    drawCNString("當代的中國漢字");

    selectFont(48, DEFAULT_CHARSET, "華文仿宋");

    glColor3f(0.0f, 1.0f, 0.0f);

    glRasterPos2f(-0.7f, -0.6f);

    drawCNString("傳統的中國漢字");

    glutSwapBuffers();

}

完整源碼:

#include <GL/glut.h>

#include <windows.h>

#define MAX_CHAR       128

void drawString(const char* str) {

    static int isFirstCall = 1;

    static GLuint lists;

    if( isFirstCall ) { // 如果是第一次調用,執行初始化

// 為每一個ASCII字元産生一個顯示清單

        isFirstCall = 0;

        // 申請MAX_CHAR個連續的顯示清單編号

        lists = glGenLists(MAX_CHAR);

        // 把每個字元的繪制指令都裝到對應的顯示清單中

        wglUseFontBitmaps(wglGetCurrentDC(), 0, MAX_CHAR, lists);

    }

    // 調用每個字元對應的顯示清單,繪制每個字元

    for(; *str!='\0'; ++str)

        glCallList(lists + *str);

}

void drawCNString(const char* str) {

    int len, i;

    wchar_t* wstring;

    HDC hDC = wglGetCurrentDC();

    GLuint list = glGenLists(1);

    // 計算字元的個數

    // 如果是雙位元組字元的(比如中文字元),兩個位元組才算一個字元

    // 否則一個位元組算一個字元

    len = 0;

    for(i=0; str[i]!='\0'; ++i)

    {

        if( IsDBCSLeadByte(str[i]) )

            ++i;

        ++len;

    }

    // 将混合字元轉化為寬字元

    wstring = (wchar_t*)malloc((len+1) * sizeof(wchar_t));

    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, str, -1, wstring, len);

    wstring[len] = L'\0';

    // 逐個輸出字元

    for(i=0; i<len; ++i)

    {

        wglUseFontBitmapsW(hDC, wstring[i], 1, list);

        glCallList(list);

    }

    // 回收所有臨時資源

    free(wstring);

    glDeleteLists(list, 1);

}

void selectFont(int size, int charset, const char* face) {

    HFONT hFont = CreateFontA(size, 0, 0, 0, FW_MEDIUM, 0, 0, 0,

        charset, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,

        DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, face);

    HFONT hOldFont = (HFONT)SelectObject(wglGetCurrentDC(), hFont);

    DeleteObject(hOldFont);

}

void display(void) {

    glClear(GL_COLOR_BUFFER_BIT);

    selectFont(48, ANSI_CHARSET, "Comic Sans MS");

    glColor3f(1.0f, 0.0f, 0.0f);

    glRasterPos2f(-0.7f, 0.4f);

    drawString("Hello, World!");

    selectFont(48, GB2312_CHARSET, "楷體_GB2312");

    glColor3f(1.0f, 1.0f, 0.0f);

    glRasterPos2f(-0.7f, -0.1f);

    drawCNString("當代的中國漢字");

    selectFont(48, DEFAULT_CHARSET, "華文仿宋");

    glColor3f(0.0f, 1.0f, 0.0f);

    glRasterPos2f(-0.7f, -0.6f);

    drawCNString("傳統的中國漢字");

    glutSwapBuffers();

}

void init (void) 

{

glClearColor (0.0, 0.0, 0.0, 0.0);

//  glMatrixMode(GL_PROJECTION);

//  glLoadIdentity();

//  glOrtho(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0); //注意該視景體的範圍和幾何中心

}

int main(int argc, char** argv)

{

glutInit(&argc, argv);

glutInitDisplayMode (GLUT_DOUBLE | GLUT_RGB);

glutInitWindowSize (500, 500);  //改為glutInitWindowSize (250, 250); 可以看出變換的僅僅是物體按比例大小的變換

glutInitWindowPosition (100, 100);

glutCreateWindow ("First");

init ();

glutDisplayFunc(display); 

glutMainLoop();

return 0; 

}

繼續閱讀