標籤:可變 個數 存在 繪製 位置 運動 告訴 ota void
轉自【翻譯】NeHe OpenGL 教程
前言
聲明,此 NeHe OpenGL教程系列文章由51部落格yarin翻譯(2010-08-19),本部落格為轉載並稍加整理與修改。對NeHe的OpenGL管線教程的編寫,以及yarn的翻譯整理表示感謝。
NeHe OpenGL第十四課:圖形字型
圖形字型:
在一課我們將教你繪製3D的圖形字型,它們可像一般的3D模型一樣被變換。
這節課繼續上一節課課的內容。在第13課我們學習了如何使用位元影像字型,這節課,我們將學習如何使用輪廓字型。
建立輪廓字型的方法類似於在第13課中我們建立位元影像字型的方法。但是,輪廓字型看起來要酷100倍!你可以指定輪廓字型的大小。輪廓字型可以在螢幕中以3D方式運動,而且輪廓字型還可以有一定的厚度!而不是平面
的2D字元。使用輪廓字型,你可以將你的電腦中的任何字型轉換為OpenGL中的3D字型,加上合適的法線,在有光照的時候,字元就會被很好的照亮了。
一個小注釋,這段代碼是專門針對Windows寫的,它使用了Windows的wgl函數來建立字型,顯然,Apple機系統有agl,X系統有glx來支援做同樣事情的,不幸的是,我不能保證這些代碼也是容易使用的。如果哪位
有能在螢幕上顯示文字且獨立於平台的代碼,請告訴我,我將重寫一個有關字型的教程。
我們從第一課的典型代碼開始,添加上stdio.h標頭檔以便進行標準輸入/輸出操作,另外,stdarg.h標頭檔用來解析文字以及把變數轉換為文字。最後加上math.h標頭檔,這樣我們就可以使用SIN和COS函數在螢幕
中移動文字了。
另外,我們還要添加2個變數。base將儲存我們建立的第一個顯示列表的編號。每個字元都需要有自己的顯示列表。例如,字元‘A’在顯示列表中是65,‘B’是66,‘C’是67,等等。所以,字元‘A’應儲存在顯示列表中
的base + 65這個位置。
我們再添加一個叫做rot的變數。用它配合SIN和COS函數在螢幕上旋轉文字。我們同時用它來改變文字的顏色。
GLuint base; // 繪製字型的顯示列表的開始位置
GLfloat rot; // 旋轉字型
GLYPHMETRICSFLOAT gmf[256]用來儲存256個輪廓字型顯示列表中對應的每一個列表的位置和方向的資訊。我們通過gmf[num]來選擇字母。num就是我們想要瞭解的顯示列表的編號。在稍後的代碼中,我將說明
如何如何檢查每個字元的寬度,以便自動將文字定位在螢幕中心。切記,每個字元的寬度可以不相同。Glyphmetrics會大大簡化我們的工作。
GLYPHMETRICSFLOAT gmf[256]; // 記錄256個字元的資訊
下面這段用來構建真正的字型的代碼類似於我們建立位元影像字型的方法。和13課一樣,只是使用wglUseFontOutlines函數替換wglUseFontBitmaps函數。
base = glGenLists(256); // 建立256個顯示列表
wglUseFontOutlines( hDC, // 設定當前視窗裝置描述表的控制代碼
0, // 用於建立顯示列表字型的第一個字元的ASCII值
255, // 字元數
base, // 第一個顯示列表的名稱
0.0f, // 字型的光滑度,越小越光滑,0.0為最光滑的狀態
0.2f, // 在z方向突出的距離
WGL_FONT_POLYGONS, // 使用多邊形來產生字元,每個頂點具有獨立的法線
gmf); //一個接收字形度量資料的數組的地址,每個數組元素用它對應的顯示列表字元的資料填充
}
GLvoid KillFont(GLvoid) // 刪除顯示列表
{
glDeleteLists(base, 256); // 刪除256個顯示列表
}
下面就是我優異的GL文字程式了。你可以通過調用glPrint(“需要寫的文字”)來調用這段代碼。文字被儲存在字串text[]中。
GLvoid glPrint(const char *fmt, ...) // 自訂GL輸出字型函數
{
下面的第一行定義了一個叫做length的變數。我們使用這個變數來查詢字串的長度。第二行建立了一個大小為256個字元的字元數組,裡面儲存我們想要的文字串。第三行建立了一個指向一個變數列表的指標,我們
在傳遞字串的同時也傳遞了這個變數列表。如果我們傳遞文字時也傳遞了變數,這個指標將指向它們。
float length=0; // 查詢字串的長度
char text[256]; // 儲存我們想要的文字串
va_list ap; // 指向一個變數列表的指標
下面兩行代碼檢查是否有需要顯示的內容,如果什麼也沒有,螢幕上也就什麼都沒有。
if (fmt == NULL) // 如果無輸入則返回
return;
接下來三行代碼將文字中的所有符號轉換為它們的字元編號。最後,文字和轉換的符號被儲存在一個叫做“text”的字串中。以後我會多解釋一些有關字元的細節。
va_start(ap, fmt); // 分析可變參數
vsprintf(text, fmt, ap); // 把參數值寫入字串
va_end(ap); // 結束分析
感謝Jim Williams對下面一段代碼的建議。以前我是用手工將文字置於中心的,而他的辦法要好的多。
我們從一個迴圈開始,它將逐個檢查文本中的字元。我們通過strlen(text)得到文本的長度。設定好了迴圈以後,我們將通過加上每個字元的長度來增加length的值。當迴圈結束以後,被儲存在length中的值就是
整個字串的長度。所以,如果我們要寫的是“hello”,假設每個字元的長度都為10個單位,我們先給length的值加上第一個字母的長度10。然後,我們檢查第二個字母的長度,它的長度也是10,所以length就變成
10 + 10(20)。當我們檢查完所有5個字母以後,length的值就會等於50(5 *10)。
給出我們每個字元的長度的代碼是gmf[text[loop]].gmfCellIncX。記住,gmf儲存了我們每個顯示列表的資訊。如果loop等於0,text[loop]就是我們的字串中的第一個字元。如果loop等於1,text
[loop]就是我們的字串中的第二個字元。gmfCellIncX告訴我們被選擇的字元的長度。GmfCellIncX表示顯示位置從已繪製上的上一個字元向右移動的真正距離,這樣,字元之間就不會重疊在一起。同時,這個距
離就是我們想得到的字元的寬度。你還可以通過gmfCelllncY命令來得到字元的高度。如果你是在垂直方向繪製文本而不是在水平方向時,這會很方便。
for (unsigned int loop=0;loop<(strlen(text));loop++) // 尋找整個字串的長度
{
length+=gmf[text[loop]].gmfCellIncX;
}
最後我們取出計算後得到的length,並把它變成負數(因為我們要將文本從螢幕中心左移從而把整個文本置於螢幕中間)。然後我們把length除以2。我們並不想移動整個文本的長度,只需要一半!
glTranslatef(-length/2,0.0f,0.0f); // 把字串置於最左邊
然後我們將GL_LIST_BIT壓入屬性堆棧,它會防止glListBase影響到我們的程式中的其它顯示列表
glPushAttrib(GL_LIST_BIT); // 把顯示列表屬性壓入屬性堆棧
glListBase(base); // 設定顯示列表的基礎值為0
現在OpenGL知道字元的存放位置了,我們就可以讓它在螢幕上顯示文字了。GlCallLists會調用多個顯示列表從而把整個文字的內容同時顯示在螢幕上。
下面的代碼做後續工作。首先,它告訴OpenGL我們將要在螢幕上顯示出顯示列表中的內容。Strlen(text)函數用來計算我們將要顯示在螢幕上的文字的長度。然後,OpenGL需要知道我們允許發送給它的列表的最大
值。我們依然不能發送長度大於255的字串。所以我們使用UNSIGNED_BYTE。(用0 - 255來表示我們需要的字元)。最後,我們通過傳遞字串文字告訴OpenGL顯示什麼內容。
也許你想知道為什麼字元不會彼此重疊堆積在一起。那時因為每個字元的顯示列表都知道字元的右邊緣在那裡,在寫完一個字元後,OpenGL自動移動到剛寫過的字元的右邊,在寫下一個字或畫下一個物體時就會從GL移
動到的最後的位置開始,也就是最後一個字元的右邊。
最後,我們將GL_LIST_BIT屬性彈出堆棧,將GL恢複到我們使用glListBase(base)設定base之前的狀態。
glCallLists(strlen(text), GL_UNSIGNED_BYTE, text); // 調用顯示列表繪製字串
glPopAttrib(); // 彈出屬性堆棧
}
下面就是畫圖的代碼了。我們從清除螢幕和深度緩衝開始。我們調用glLoadIdentity()來重設所有東西。然後我們將座標系向螢幕裡移動十個單位。輪廓字型在透視圖模式下表現非常好。你將文字移入螢幕越深,文
字開起來就更小。文字離你越近,它看起來就更大。
也可以使用glScalef(x,y,z)命令來操作輪廓字型。如果你想把字型放大兩倍,可以使用glScalef(1.0f,2.0f,1.0f). 2.0f 作用在y軸, 它告訴OpenGL將顯示列表的高度繪製為原來的兩倍。如果2.0f作用
在x軸,那麼文本的寬度將變成原來的兩倍。
int DrawGLScene(GLvoid) // 此過程中包括所有的繪製代碼
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // 清除螢幕及深度緩衝
glLoadIdentity(); // 重設當前的模型觀察矩陣
glTranslatef(0.0f,0.0f,-10.0f); // 移入螢幕一個單位
在向螢幕裡移動以後,我們希望文本能旋轉起來。下面3行代碼用來在3個軸上旋轉螢幕。我將rot乘以不同的數,以便每個方向上的旋轉速度不同。
glRotatef(rot,1.0f,0.0f,0.0f); // 沿X軸旋轉
glRotatef(rot*1.5f,0.0f,1.0f,0.0f); // 沿Y軸旋轉
glRotatef(rot*1.4f,0.0f,0.0f,1.0f); // 沿Z軸旋轉
下面是令人興奮的顏色迴圈了。照常,我們使用唯一遞增的變數(rot)。顏色通過使用COS和SIN來迴圈變化。我將rot除以不同的數,這樣每種顏色會以不同的速度遞增。最終的效果非常好。
// 根據字型位置設定顏色
glColor3f(1.0f*float(cos(rot/20.0f)),1.0f*float(sin(rot/25.0f)),1.0f-0.5f*float(cos(rot/17.0f)));
我最喜歡的部分,將文字寫到螢幕上。我使用同將位元影像字型寫到螢幕上相同的函數。將文字寫在螢幕上,所有你要做的就是glPrint(“你想寫的文字”)。很簡單。
在下面的代碼中,我們要寫的是NeHe,空格,破折號,空格,然後是rot的值除以50後的結果(為了減慢計數器)。如果這個數大於999.99,左邊第四個數將被去掉(我們要求只顯示小數點左邊3位元字)。只顯示小數
點右邊的兩位元字。
glPrint("NeHe - %3.2f",rot/50); // 輸出文字到螢幕
然後增大旋轉變數從而改變顏色並旋轉文字。
rot+=0.5f; // 增加旋轉變數
return TRUE; // 成功返回
}
原文及其個版本原始碼下載:
http://nehe.gamedev.net/data/lessons/lesson.asp?lesson=14
NeHe OpenGL教程 第十四課:圖形字型