OpenGL點陣字型繪製終極解決方案!

來源:互聯網
上載者:User

轉自http://tsuui.is-programmer.com/posts/4252.html

事情總在變化, opengl迎來了3.3以及4.1的進化, 相信今後的擴充也會朝著這個方向. 對於字型渲染方面, 也並不是什麼壞事. 今後有時間再寫篇關於3.3和4.1的全屏字型渲染的新方案, 仍然是結合freetype2的, 相信隨著freetype2的進步, 和對它的逐步認識, 應該會比現有方案更簡單高效... 現在最最最重要的事是...睡覺!!!

對於此文, 大家僅做參考吧.

經過多次修改測試,字型問題終於有了個比較完美的解決方案了,貼出來亮亮~~

此法可以說完全是“紅寶書”(即《OpenGL編程指南》)所賜, 此篇也不過是一些實踐心得和我自己對字型顯示方法的一些體會罷了。

下面就來介紹這個所謂的“終極解決方案”,對於待解決的各種問題,都有著多種可供選擇的方案,就讓我來邊比較邊描述吧:

  1. 渲染方式和幀數

不管是不是OpenGL平台, 在每個3D平台中,  點陣字型無非兩個用處: 要麼做效果,要麼做提示。效果就是標題文字、按鈕之類的,我們一般稱之為banner,titile,caption的東西; 提示就是指一些有動態更新要求的文字,如控制資訊提示,  偵錯模式下的對象名稱、座標等, 還有就是互動場合,比如聊天。

兩種應用需求有所不同,但不管是哪種,在OpenGL中我能找到的直接支援字型的,只有三種方法,選擇他們的標準只有一個——速度:

● glBindTexture, 紋理貼圖,連文字帶背景做好一張大圖,  按需地選取各個文字子映像,再貼到相應位置的矩形上。貼圖能夠實現的文字效果最多,你可以把文字紋理映射到空間任意位置的巨型上,可以隨意的旋轉縮放和變形。在不要求大量動態更新文字內容的地方,可以選用此方法。大部分的小型3D遊戲,都採用了這樣的方式顯示文字,速度夠快,能實現所有的變換效果。

不足之處是:

很難實現多顏色混合顯示的文字,因為為紋理設定顏色需要的步驟十分繁瑣,需要反覆切換和設定紋理函數和像素傳輸轉換函式,難免影響效能;

文字內容不能靈活的更換, 除非你打算用很多碎小的紋理來拼湊文章;但隨著碎小圖片的增多,頂點的和紋理對象也大量增加,需要大量額外的片段處理和過濾操作,會明顯拖慢處理流水線,在要求顯示大量動態文本的場合下力不從心。不過好在OpenGL在處理紋理對象時多數情況是使用硬體實現的,速度不會慢太多,但也絕對不夠塊(你可能玩過這樣的3D遊戲:映像效果情境規模都一般,可滑鼠速度慢得難以忍受,出現這種情況,九成的原因是頂點片元過多造成的,單次情境同時顯示的紋理片段過碎過多,都會成倍地同時增加頂點和像素片元,拖慢速度,滑鼠有時間響應,卻沒時間畫出來);

還有就是變換展開後,紋理字型會出現模糊的現象,有些人建議開啟Anisotropic Filtering(各向異性過濾)開關, 利用反走樣解決,但效果似乎也不穩定,在轉角過大、近距離或光線角度太偏的情況下,效果就越來越差了,我想這是紋理映射的通病吧,不可能就一張圖你從哪裡看都一樣的清晰啊,也有人用多等級的紋理和Mipmap解決,本人沒實驗過(比較麻煩)所以沒什麼發現權。

● glDrawPixels,像素繪製,任何紋理能夠支援的映像格式,它都能支援,縮放也很簡單,也可通過設定像素傳輸和像素封裝函數實現一些其他的效果。

缺點是:

他同紋理一樣,很難靈活設定顏色; 

只能在光柵上繪製,若需要各種變換效果,還要開闢額外的輔助緩衝和紋理對象;

而最大最大的問題就是速度! 像素在顯示之前的處理動作是沒有經過加速的,也就是說不管你有沒有把他編譯到顯示列表,像素的轉換傳輸等動作每次都照做不誤,它不同於紋理對象中的像素,多數OpenGL實現沒有對它開闢專屬的顯存地區(這種說法有待考證,但實際測試中效率確實很差,編程指南中有特定篇幅介紹了如何提高像素繪製的效率,但即使犧牲一切資源來保證效率,實測效果仍然很難讓人滿意)。

所以,雖然 glDrawPixels似乎是三種方法中最簡單有效, 可實際運行起來卻是三種方法中最慢的!所以如果你要繪製大量點陣字,又想保證幀數的話,寧願去考慮紋理貼圖,也不要在這個函數上花太多心思。

● glBitmap,位元影像,如果你想在你的3D引擎裡添加一個控制台,這個是唯一的選擇,96個可列印字元做成位元影像映射到索引為0x20~0x7F的顯示列表,供隨時調用。就算直接用glBitmap也來的及,對幀數的影響也不算大,  三種方法中它的速度最能讓人滿意, 且能通過設定光柵顏色靈活改變位元影像字型的顏色。想象一下,如果你的控制台裡的warning error 普通的log message和user command分別使用了不同的顏色顯示,而為實現這個既酷又實用的效果,所付出的代價僅僅是在設定光柵前加個glColor這麼簡單而已。

缺點:

只能在光柵上繪製,若要縮放旋轉之類的變換,需要額外的處理工序,但由於其本身的速度優勢,這些工序一般不會對幀數有太大的影響;

另外由於位元影像只有黑白單色,無法表示灰階,鋸齒問題嚴重,如果只顯示英文字型還好,一旦要顯示中文,文字效果很差,實在是褻瀆中華文化!當然如果你知道怎麼在OpenGL裡實現一個和ClearType類似的技術,那另當別論。

 

以往對於全屏字型渲染,glBitmap一直是我心中的痛,難以割捨它的高速,又無法忍受它的效果, 直到前一段在讀編程指南時,無意間發現了一種利用glBitmap顯示反鋸齒字型的技巧。當時反覆讀了幾次,貌似明白了上面的意思,拿到機器上試了試, 果然天才,
很好地解決了鋸齒的問題,相見恨晚,感歎讀書太不認真,怎麼早沒發現!!  下面簡單描述一下這個方法:

對於一副256灰階映像,每個像素使用了一個位元組表示0~255個灰階,而位元影像只有一位0或1,乍一看不太可能,但位元影像可以靈活設定顏色的特點,成了突破口。既然位元影像在設定光柵前可以使用glColor為光柵指定"當前光柵顏色",不僅如此,我們還可以指定顏色的alpha值,從而繪製明暗相間的彩色位元影像,瞭解了?

把一個反鋸齒的灰階字型映像分為多幅位元影像,假設分為4張位元影像,第一張:使灰階1~63的相應點置1,其他點置0;第二張:64~127的置1,其他置0...以此類推, 灰階每上升64的點都集中到同一張位元影像上。然後,開啟混合,使用4次glBitmap調用繪製出來,每次繪製前將光柵顏色設定成與映像對應階段的灰階,像下面這樣: 

GLfloat curColor[4] = { r, g, b, a*0.25f}; //假設使用中色彩為 (r,g,b,a)for (int i=0; i<4; ++i) {glColor4fv(curColor);glRasterPosiv(curPos);glBitmap(w,h, 0,0, 0,0, bitmap[i]);//當前alpha增幅0.25, 4次增至1.0curColor[3] += a*0.25f;}

就相當於讓一張256灰階的位元影像降低到5灰階。這麼做的效果如何呢?

是我在glut這種超慢架構下的測試的:

中間的是用glDrawPixels在開啟freetype2的autohinting選項下渲染的256灰階字型, 上下兩張都是使用glBitmap繪製的,沒有開啟autohintng,上面的是3副位元影像(4灰階)/字,下面的是4副位元影像/字。glDrawPixels是使用了顯示列表繪製全屏1003個漢字的,已經累成14FPS了,而glBitmap是沒用顯示列表的,同樣1003字一屏,在glut下也能達到50FPS以上! 近乎完美!

(視窗解析度是960x600)

 同時,由於每個像素變成了4個bit表示(4張圖每張1bit),使儲存字模所需的空間降至原來的一半。

 

  1. 字型檔和編碼映射

除了glDrawPixels,每一種方法都有應用它的理由,但不管你用哪一種,要克服的最大困難除了渲染速度,就是字型檔問題了! 讀取字型檔建議使用FreeType2這個開源目, 它支援當今幾乎所有流行格式的字型檔,我們可以選擇它來作為字型匯入的工具,當然也可以把它link到你的程式中,即時的載入ttf字型並按需產生字模映像。解決字型檔的讀取問題,FreeType2絕對是上上之選,就這麼簡單~

當然, 如果你只想支援普通的96個可列印字元,除了glDrawPixels,其他兩種方式隨便用——想要效果就用glBindTexture、想要簡單方便就glBitmap,然後關掉瀏覽器、合上參考書,最多半個小時你的字型問題就有著落了! 可如果你想要支援中文??龐大的字型檔體積是你不得不考慮的另一個問題, 何為龐大?讓我們簡單地算下:

GB2312編碼包含7445個字元,其中漢字6000多個,GBK編碼下僅漢字就有20902個,最新國家標準GB18030-2005,總共76546個字元, 而目前的Unicode字元集,已經增至超過10萬個字元,雖然現在還沒有哪個unicode字型檔能支援到這麼多字元(難道真的有?),但至少20000個還是有的! 而這些字元都是分散在編碼空間中的,就是說編碼是不連續的,不能使用連續的顯示清單索引作簡單的映射(即使連續,這麼龐大的數目,就算顯示列表沒有上限,它所佔據的顯存空間也相當可觀),因此不得不為‘字元編碼’到‘字模索引/清單索引’建立尋找表。

最猛的做法是,在記憶體平鋪整張表,字模全部存入記憶體,一步索引到字模,產生顯示列表,下次再繪製字模時只需索引到顯示列表而不必去取字模。這樣做好像也沒什麼問題,沒什麼問題?如果真的沒問題就不會是最猛的了——對於GB2312和GBK這種"小型"多位元組編碼就需要盡1MB的空間,對於unicode最少最少需要近4MB的空間,而在這個大表裡,八成以上的內容是普通人這輩子都用不上的,而每重新整理一幀,你的每個要顯示的字元都要重複查表一次,在這樣大的空間中頻繁查表,產生頁交換的可能非常的大,速度不慢才怪,絕對不比你每次調用freetype即時轉換灰階來的快,而且還很浪費。

我建議的方法是利用std::map!當然如果你有自己的紅/黑樹狀結構類和allocator也可以自己做一個map,效率上可能更勝一籌。map的作用是把字模資訊映射到字元編碼,動態載入我們僅有可能用到的那幾千個字模資訊,這樣既節省了空間(省點是點),又比較高效。另外,這裡不必專門為map設定空間限制,map在到達一定大小後(大約7000個節點)或每過一段時間後將尋找表clear掉就可以了,除非你要在程式裡顯示《說文解字》全篇,否則要讓map增大到5000節點都是個相當有難度的工作。

 

  1. 定製自己的字型檔

哎……這也是被逼無奈,如果你夢想著自己的圖行引擎能有全功能的中文支援(顯示、輸入),你必須一再考慮速度的問題!因為中文實在是太多了……而且萬把字元一會要查表一會要轉換映像一會又要排布文字,各個環節都不像西文那樣方便直接, 都需要額外的繁瑣的計算!如果你還要些特效,你一定會比我更吝嗇速度。

實踐證明,使用了定製點陣字型檔的方式後,不使用顯示列表而是即時從記憶體取得字模再逐個glBitmap,其效率幾乎可以和使用了顯示列表的內嵌Freetype2的字型系統媲美。至於怎麼建立自己的字型檔嘛,我的意見是:怎麼方便怎麼建,讀著方便,用這方便就OK了,因為像這樣的位元影像資料組建檔案後資料是很“稀疏”的,很容易壓縮和解壓,所以空間上不必太擔心(我自己做的24×24點陣字型檔,連帶額外資料只有4MB多一點)。

其他的就沒什麼可說的了,要注意的只有三點:你需要一個有序的code-index表,為什麼要有序?因為代碼域很長而實際的可顯示碼點很稀少,在一個有序的靜態表中二分尋找是不二之選;你還需要為每個字模資料建立一個字模資訊記錄,記錄啥?寬width、高height、列步進長度advance、行位元組數pitch、字模資料指標等; 還有就是字模資料,如果你想更塊一些,讓每行像素的位元組數擴充到4的倍數,浪費些空間可以再換些速度。

 

到目前為止我們基本完成了下面的要求:

1. 速度快,永遠不能放棄對它的追求!

2. 省記憶體,CPU記憶體要省,GPU記憶體更要一省再省!

3. 美觀,字是拿來看的,辛勤勞動不能僅因一個難看而被淪為劣質產品。

4. 簡單,方法要簡單通用!這個好像差點事.....

5. 支援海量中文,在新一輪的‘文字改革’到來之前,這永遠是個艱巨的任務! 而我們做到……一半了!!  不容易啊!!

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.