大家好,我是Skyman(SM3D),喜歡三維編程,現在正在系統學習OpenGL,DirectX,VR等。我經常到各大論壇上去轉轉。其中去得最多的地方是NeHe的OpenGL網站(http://nehe.gamedev.net),那是我所見過的最好的學習OpenGL的網站,強烈推薦!!!這是我的第一篇關於OpenGL的文章,寫得不是很好,歡迎大家指正。大家知道,在OpenGL中輸出文本有兩個函數:wglUseFontBitmaps和wglUseFontOutlines,前者用來輸出2維文字,後者用來輸出3維文字。而要輸出漢字,必須使用TrueType字體。本文就教你如何使用wglUseFontOutlines來創建很酷的三維漢字特效。從此文,你可以學到三點知識:
- 如何從文件中載入位圖作爲紋理;
- 如何生成三維漢字;
- 如何將紋理貼到三維漢字上。
我使用的是NeHe的Simple框架。爲了簡便起見,我這裏只列出增加的代碼。首先,再頭文件定義區加上: #include <stdio.h>
接着,在bool fullscreen=TRUE後面定義要用到的變量:
GLuint texture[1]; //紋理ID
int rot=0; //三維漢字繞x軸旋轉的角度
HFONT hFont; //字體句柄
LOGFONT lf; //邏輯字體
GLYPHMETRICSFLOAT gmf[128]; //包括字形的位置和方向信息的結構
unsigned int ichar=0; //字符的整型值
char cchar; //要轉換爲顯示列表的字符
unsigned int i=0; //循環變量
unsigned int j=0; //循環變量
char Text[128]; //存放要顯示的字符的數組
BYTE TextList[128]; //顯示列表
const GLuint ListBase=1000; //顯示列表的基
然後在LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);後面定義載入文理的函數LoadBMP和LoadGLTextures,這個在NeHe的許多教程裏都有,這裏再寫一下:
AUX_RGBImageRec *LoadBMP(char* filename) // 載入位圖
{
FILE* file = NULL; // 文件句柄
if (!filename) //文件名已給了嗎?
{
return NULL; // 若沒有則返回NULL
}
file = fopen(filename"r"); // 檢查文件是否存在
if (file) // 文件存在嗎?
{
fclose(file); // 關閉文件
return auxDIBImageLoad(filename); // 載入位圖並返回位圖的指針
}
return NULL; // 若載入失敗則返回NULL
}
int LoadGLTextures() // 載入位圖並轉換爲紋理
{
int status = FALSE; // 狀態指示
AUX_RGBImageRec* textureImage[1]; // 爲紋理創建存儲空間
memset(textureImage, 0, sizeof(void *)*1); // 清零
// 載入位圖,檢查誤,如位圖不存在則退出
if (textureImage[0] = LoadBMP("data/tex.bmp")) //紋理圖片的路徑
{
status = TRUE; // 狀態設爲TRUE
glGenTextures(1, &texture[0]); // 創建紋理
glBindTexture(GL_TEXTURE_2D, texture[0]);
gluBuild2DMipmaps(GL_TEXTURE_2D, 3, textureImage[0]->sizeX, textureImage[0]->sizeY, GL_RGB, GL_UNSIGNED_BYTE, textureImage[0]->data);
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR); //過濾
glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR_MIPMAP_NEAREST); //過濾
glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); //自動生成紋理座標
glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); //自動生成紋理座標
glEnable(GL_TEXTURE_GEN_S);
glEnable(GL_TEXTURE_GEN_T);
}
if (textureImage[0]) // 如果紋理存在
{
if (textureImage[0]->data) // 如果紋理圖象存在
{
free(textureImage[0]->data); // 釋放紋理圖象所佔用的內存
}
free(textureImage[0]); // 釋放圖象結構
}
return status; // 返回狀態
}
註明:因爲三維字體是比較複雜的模型,所以要手工定義紋理座標很難。我們可以讓OpenGL自動生成紋理座標。函數glTexGen()用於自動生成紋理座標。
void gltexgen{ifd}(GLenum coord,GLenum pname,TYPE param);
參數coord必須是GL_S,GL_T,GL_R或GL_Q,表明是否要生成紋理座標s,t,r或q。參數pname爲GL_TEXTURE_GEN_MODE,表示紋理座標的生成模式。參數param可選GL_OBJECT_LINEAR,GL_EYE_LINEAR或GL_SPHERE_MAP。 GL_OBJECT_LINEAR表示紋理圖象相對於一個移動的物體靜止,本程序就採用這種方式。
然後在InitGL()函數中的glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);後面加入如下語句:
glEnable(GL_LIGHT0); // 激活默認光照 (雖然光照效果不是很好但是速度快)
glEnable(GL_LIGHTING); // 激活光照
glEnable(GL_COLOR_MATERIAL); // 激活材質顏色
glEnable(GL_TEXTURE_2D); // 激活二維紋理
glBindTexture(GL_TEXTURE_2D, texture[0]); // 綁定紋理
memset(&lf, 0, sizeof(LOGFONT)); // 清空內存
//設定邏輯字體lf的屬性
lf.lfHeight = -1; // 指定字高
lf.lfWidth = -1; // 指定字寬
lf.lfEscapement = 0; // 指定角度(1/10度)
lf.lfOrientation = 0; // 指定字符的基線與x軸的角度(1/10度)
lf.lfWeight = FW_BOLD; // 指定字體的重量(FW_BOLD=700)
lf.lfItalic = FALSE; // 指定是否斜體
lf.lfUnderline = FALSE; // 指定是否有下劃線
lf.lfStrikeOut = FALSE; // 指定是否是StrikeOut字體
lf.lfCharSet = ANSI_CHARSET; // 指定字符集
lf.lfOutPrecision = OUT_TT_PRECIS; // 指定輸出精度
lf.lfClipPrecision = CLIP_DEFAULT_PRECIS ; // 指定裁剪精度
lf.lfQuality = PROOF_QUALITY; // 指定輸出質量
lf.lfPitchAndFamily = VARIABLE_PITCH | TMPF_TRUETYPE | FF_MODERN ; // 指定字體的定位和外觀
lstrcpy (lf.lfFaceName, "宋體") ; // 指定字體名稱
hFont=CreateFontIndirect(&lf); // 創建邏輯字體
SelectObject(hDC, hFont); // 將邏輯字體選入設備環境
然後在DrawGLScene()函數中的glLoadIdentity();後面加入:
strcpy(text, "重慶大學歡迎你"); // 指定所要顯示的字符
i = 0; // 對i賦初制值
j = 0; // 對j賦初制值
while (i < 128)
{
if(IsDBCSLeadByte(text[i])) // 判斷是否爲雙字節
{
ichar = text[i];
ichar = (ichar << 8) + 256; // 256爲漢字內碼的“偏移量”
ichar = ichar + text[i + 1];
i++;
i++;
wglUseFontOutlines(
hDC, // 設備環境句柄
ichar, // 要轉換爲顯示列表的第一個字符
1, // 要轉換爲顯示列表的字符數
1000 + j, // 顯示列表的基數
0.0f, // 指定與實際輪廓的最大偏移量
0.2f, // 在Z軸負方向的值
WGL_FONT_POLYGONS, // 指定顯示列表用多邊形創建字體
&gmf[j]
); // 接受字符的地址
textList[j] = j;
j++;
}
else //若是單字節(英文)
{
cchar = text[i];
i++;
wglUseFontOutlines(
hDC, // 設備環境句柄
cchar, // 要轉換爲顯示列表的第一個字符
1, // 要轉換爲顯示列表的字符數
1000 + j, // 顯示列表的基數
0.0f, // 指定與實際輪廓的最大偏移量
0.5f, // 在Z軸負方向的值
WGL_FONT_POLYGONS, // 指定顯示列表用多邊形創建字體
&gmf[j]
); // 接受字符的地址
textList[j] = j;
j++;
}
}
glListBase(listBase); // 設置顯示列表的初始索引值
glTranslatef(-4.0f, 0.0f, -10.0f); // 要顯示的文本的位置
glRotatef(float(rot), 1.0f, 0.0f, 0.0f); // 文本繞x軸旋轉
glCallLists(strlen(Text),GL_UNSIGNED_BYTE,& TextList);//調用多顯示列表繪製文本
glFlush(); // 強制繪圖
rot = (rot + 1) % 360; // 旋轉變量遞增
註明:
- 雙字節內碼DBCS(雙字節字符集) 支持很多不同的東亞語言字母,如漢語、日語和朝鮮語等。 DBCS 使用數字 0–128 表示 ASCII 字符集。其它大於 128 的數字作爲前導字節字符,它並不是真正的字符,只是簡單的表明下一個字符屬於非拉丁字符集。在 DBCS 中,ASCII 字符的長度是一個字節,而日語、朝鮮語和其它東亞字符的長度是 2 個字節。漢字內碼的偏移量爲256。
- 執行多顯示列表可以有效的提高渲染速度。方法是將顯示列表的索引值放入一個數組中,然後調用glCallLists()命令。對於有多種字體的情況,需要爲每種字體建立一個不同的初始顯示列表索引值。這些初始索引值可以在調用glCallLists()函數之前,通過調用glListBase()函數來指定。該函數指定一個偏移量,該偏移量被加到glCallLists()函數中的顯示列表索引中,以獲取最終的顯示列表索引值。函數glCallLists(n,type,*lists)執行n個顯示列表。所執行的列表索引值是由當前顯示列表基(由glListBase()指定)指明的偏移量,加上由 lists指向的數組中的整數之和。