NeHe OpenGL教程 (十四)

來源:互聯網
上載者:User

第14課
  
  圖形字型:

在一課我們將教你繪製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,                // 第一個顯示列表的名稱

 That's not all however. We then set the deviation level. The closer to 0.0f, the smooth the font will look. After we set the deviation, we get to set the font thickness. This describes how thick the font is on the Z axis. 0.0f will produce a flat 2D looking font and 1.0f will produce a font with some depth.

The parameter WGL_FONT_POLYGONS tells OpenGL to create a solid font using polygons. If we use WGL_FONT_LINES instead, the font will be wireframe (made of lines). It's also important to note that if you use GL_FONT_LINES, normals will not be generated so lighting will not work properly.

The last parameter gmf points to the address buffer for the display list data.  
  
                0.0f,                // 字型的光滑度,越小越光滑,0.0為最光滑的狀態
                0.2f,                // 在z方向突出的距離
                WGL_FONT_POLYGONS,            // 使用多邊形來產生字元,每個頂點具有獨立的法線
                gmf);                //一個接收字形度量資料的數組的地址,每個數組元素用它對應的顯示列表字元的資料填充
}

 The following code is pretty simple. It deletes the 256 display lists from memory starting at the first list specified by base. I'm not sure if Windows would do this for you, but it's better to be safe than sorry :)  
 
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;                        // 成功返回
}
  
 在這節課結束的時候,你應該已經學會在你的OpenGL程式中使用輪廓字型了。就像第13課,我曾在網上尋找一篇與這一課相似的教程,但是也沒有找到。或許我的網站是第一個涉及這個主題同時又把它解釋的簡單易懂的C代碼的網站吧。享用這篇教程,快樂編碼!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.