先聲明: 本篇只處理渲染出ASCII碼的部分,渲染ASCII以外的字母還需要處理解析unicode的部分
這裡我直接建議讀Learn OpenGL - Text Rendering這篇,如果英文不好的讀者,這篇是其他網友好心翻譯的簡中版本。
假如你跟我一樣,以為要在銀幕上畫一個字很簡單嘛?錯!我又把我自己搞死了 O_>O
渲染一個字(以英文字母來說),除了大小,需要注意幾件事
Bearing
)Advance
)上方的文章使用了freetype
這個libary,但這裡使用的是之前介紹過的stb
系列,stb_truetype
stb_truetype
的使用如同之前stb_image
的好處,stb_truetype
一樣是single-header file。使用方法與stb_image
大致相同,標頭檔裡面其實就有example了,請見line 272。
或是我有在github找到另一個範例,有額外用stb_image_write
把要輸出的文字輸出到圖片上。
然後我主要參照了上面的範例與raylib
載入字形的方法(其實百分之九十都是抄raylib
),只把ASCII的字形資料載入至記憶體裡,這個會是字形與各個字的結構。
typedef struct CharInfo {
int unicode;
unsigned char* data;
Rect rec;
int xoffset, yoffset;
int xadvance;
} CharInfo;
typedef struct Font {
Texture texture;
int base_height;
CharInfo chars[ASCII_CHAR_COUNT];
} Font;
下面直接貼上範例的內容
/* prepare font */
stbtt_fontinfo info;
if (!stbtt_InitFont(&info, fontBuffer, 0))
{
printf("failed\n");
}
int b_w = 512; /* bitmap width */
int b_h = 128; /* bitmap height */
int l_h = 64; /* line height */
/* create a bitmap for the phrase */
unsigned char* bitmap = calloc(b_w * b_h, sizeof(unsigned char));
/* calculate font scaling */
float scale = stbtt_ScaleForPixelHeight(&info, l_h);
其實到這裡,stb_truetype
就已經把所有字形(包括其他國家的文字)的資料全部讀完了,剩下的就是依據我們設置的字高之類的找出這個字的邊界、高、寬、間隙等等...,然後範例中,透過for loop,一遍又一遍,重新迭代這些要輸出的文字。
再來就是重點了,我設計會把這些資料,都集成一張圖片,也就是Learn OpenGL文章裡面提到的bitmap fonts,當然這個就會是我們的圖集(Texture Atlas),之後要在遊戲內輸出文本的時候,就會透過批次渲染畫出來,按理來說就會更高效,不需要重疊代找到資料該字的資料,所以批次渲染那邊需要對不同Texture又說是圖集進行處理(可能是明天那篇)。
目前已經完成了載入字體的部分,Project裡面,使用了arial.ttf
,如果是Windows,可以在C:/Windows/Fonts
裡面找到,OS的預設字體都放這裡。
既然要把所有要輸出的字,擠到一張圖裡面,這裡就會需要2D bin packing的演算法,這個不是單獨指一個特定的演算法,而是一個研究主題,目的在於如何在有限空間內,放入最多不同大小的箱子。
很好的是,stb_truetype
裡面就有內建一套這樣的處理,獨立出來的標頭檔是stb_rect_pack.h
,stb的作者使用的是skyline bin packing。
也就是說,利用stb_truetype
算出每個字放在小箱子裡的大小,然後透過stb_rect_pack
安排這些箱子要放大箱子的哪一處,下面是如何使用stb_rect_pack
,可以看到每個箱子的間隙也要計算。
stbrp_context* context = (stbrp_context*)malloc(sizeof(stbrp_context));
stbrp_node* nodes = (stbrp_node*)malloc(sizeof(ASCII_CHAR_COUNT * sizeof(stbrp_node)));
stbrp_init_target(context, font_atlas.w, font_atlas.h, nodes, ASCII_CHAR_COUNT);
stbrp_rect* rects = (stbrp_rect*)malloc(ASCII_CHAR_COUNT * sizeof(stbrp_rect));
for (int i = 0; i < ASCII_CHAR_COUNT; i++) {
rects[i].id = i;
rects[i].w = font->chars[i].rec.w + 2 * padding;
rects[i].h = font->chars[i].rec.h + 2 * padding;
}
stbrp_pack_rects(context, rects, ASCII_CHAR_COUNT);
老實說,這塊目前沒太多研究,也是直接參照
raylib
的作法。
之後就是把圖片的byte資料直接給opengl,產出貼圖,就有一張Font Atlas可以用了。
然後還有一段,圖片轉成貼圖的地方,有用到一個叫做Texture Swizzle,原因是true type轉成圖片的資料只有兩個通道,有就是灰階的,但如果要轉成Font Bitmap的話至少需要三通道 RGB,但我們需要Alpha通道,故轉成RGBA。