文本繪制
本文主要射擊Freetype的入門理解和在OpenGL中實現文字的渲染。
freetype
freetype的官網,本文大部分內容參考https://www.freetype.org/freetype2/docs/tutorial/step1.html#section-2
library
FreeType中的library其類型是FT_Library,定義如下:
typedef struct FT_LibraryRec_ *FT_Library;
所以可以簡單的理解為一個FT_LibraryRec_的對象,雖然FreeType用c寫的,但這個地方不妨礙我們使用對象來理解它。因為_LibraryRec_我並沒有看到源代碼,具體包含哪些內容我並不清楚。但根據其用法,可以推測其應該是一些字體上下文的內容,比如緩存、內存管理等。
FT_Error = FT_Init_Freetype(&library);
face
一個face可以理解為字體的描述或者說字形的集合,比如“Times New Roman Regular"表示正常的新羅馬字體,而"Times New Roman Italic"表示新羅馬字體的斜體表示。一個字體文件中可以嵌入多個face,我們可以通過下面的API加載特定的face:
FT_Error FT_New_Face(FT_Library library,const char* filepathname,FT_Long face_index,FT_Face *aface);
其中face_index就表示要加載字體文件中的哪個face,一般情況下,0總是可用的。如果face_index設置為-1,則可以通過face->num_faces獲取字體文件中有多少個face。
error = FT_New_Face(library,"./arial.ttf",0,&face);
字體大小
error = FT_Set_Char_Size( face, /* handle to face object */ 0, /* char_width in 1/64th of points */ 16*64, /* char_height in 1/64th of points */ 300, /* horizontal device resolution */ 300 ); /* vertical device resolution */
在這里有兩個概念需要注意:pt和dpi。pt是point的縮寫,可以立即為一個點,一個點的大小使用物理距離來描述的,通常是1/72英寸。dpi的意思是dot per inch,表示每英寸有多少個點,這個點表示的是顯示設備最小輸出單元,可以理解為我們常說的像素,主流顯示設備的標准值是72dpi和96dpi,這個可以在顯示器配置中查到。所以在這個方法中通過同時指定pt大小和設備的dpi,根據換算關系可以計算字符的像素大小。
下面的代碼直接設置字形的像素大小。注意這兩個方法中,width的值設置為0,表示width與height一樣,height設置為0同理。
error = FT_Set_Pixel_Sizes( face, /* handle to face object */ 0, /* pixel_width */ 16 ); /* pixel_height */
glyph
字形,其實就是某個字符具體的形狀描述。字形的描述有兩種,一種是位圖,一種是矢量圖。位圖又叫點陣圖,簡單理解就是圖像是由一個一個像素點組成,每個像素點可以有自己的顏色;矢量圖則記錄的是一個一個的對象,這些對象是一種形狀,形狀由數學公式來描述的,簡單的理解就是矢量圖記錄的是圖像的畫法。這兩種描述最大的區別就是,縮放的時候矢量圖不會失真。
使用FreeType的時候,我們不需要關心這些底層的實現,直接使用FT_Load_Glyph即可加載。
FT_UInt FT_Get_Char_Index( FT_Face face, FT_ULong charcode ); FT_Error FT_Load_Glyph( FT_Face face, FT_UInt glyph_index, //The index of the glyph in the font file. FT_Int32 load_flags ); //A flag indicating what to load for this glyph
FT_Error FT_Render_Glyph( face->glyph, /* glyph slot */
render_mode ); /* render mode */
字形的數據結構:
typedef struct FT_GlyphSlotRec_ { FT_Library library; FT_Face face; FT_GlyphSlot next; FT_UInt reserved; /* retained for binary compatibility */ FT_Generic generic; FT_Glyph_Metrics metrics; FT_Fixed linearHoriAdvance; FT_Fixed linearVertAdvance; FT_Vector advance; FT_Glyph_Format format; FT_Bitmap bitmap; FT_Int bitmap_left; FT_Int bitmap_top; FT_Outline outline; FT_UInt num_subglyphs; FT_SubGlyph subglyphs; void* control_data; long control_len; FT_Pos lsb_delta; FT_Pos rsb_delta; void* other; FT_Slot_Internal internal; } FT_GlyphSlotRec;
在字形數據結構中,幾個比較重要的數據是bitmap,bitmap_left,bitmap_top。其中bitmap其實可以理解為字形的位圖數據,定義如下:
typedef struct FT_Bitmap_ { unsigned int rows; unsigned int width; int pitch; unsigned char* buffer; unsigned short num_grays; unsigned char pixel_mode; unsigned char palette_mode; void* palette; } FT_Bitmap;
其中buffer像素數據,rows,width表示圖像的寬和高,我們在OpenGL中繪制時主要用到的三個數據。
除了bitmap,還有幾個重要的數據,這寫數據主要影響多個文本之間的布局,因為不是每個字形的大小都是一樣的,比如bitmap_left,bitmap_right等。讓我們以下圖為例說明一下FreeType對於字形的描述:
每一個字形都放在一個水平的基准線(Baseline)上(即上圖中水平箭頭指示的那條線)。一些字形恰好位於基准線上(如’X’),而另一些則會稍微越過基准線以下(如’g’或’p’)(譯注:即這些帶有下伸部的字母,可以見這里)。這些度量值精確定義了擺放字形所需的每個字形距離基准線的偏移量,每個字形的大小,以及需要預留多少空間來渲染下一個字形。下面這個表列出了我們需要的所有屬性。
在OpenGL中繪制文字
FreeType 官網有些例子給我們參考,點擊這里查看。其中一個簡單的例子是在終端中以字符的形式輸出字形,這個例子可以幫助我們理解bitmap中的數據形式。
獲取到字形的bitmap后,在OpenGL繪制文字的思路就很簡單了:根據字形數據生成二維紋理,然后把這個紋理繪制到一個四方形中,以下是繪制的主要代碼,完整代碼見:https://github.com/xin-lover/opengl-learn/tree/master/chapter-16-glfw/chapter-font。
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //禁用字節對齊限制
unsigned int textureID;
glGenTextures(1,&textureID);
glBindTexture(GL_TEXTURE_2D,textureID);
glTexImage2D(GL_TEXTURE_2D,0,format,width,height,0,format,GL_UNSIGNED_BYTE,data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T,GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,GL_LINEAR);
在這里需要注意字節對齊的問題,OpenGL默認要求紋理4字節對齊,即紋理的大小是4的倍數,這通常不會有什么問題,因為絕大多數的紋理大小都是4的倍數並/或每個橡樹4字節大小,但現在我們一個像素是一個字節(GL_RED),它可以是任意的寬度,所以需要需求這個限制,將對齊參數設置為1,不然的話可能會造成段錯誤。
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);//禁用字節對齊限制
效果:
demo參考網址:https://learnopengl-cn.github.io/06%20In%20Practice/02%20Text%20Rendering/。