(一)顯示英文 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;
}