版本記錄
版本號(hào) | 時(shí)間 |
---|---|
V1.0 | 2018.01.20 |
前言
OpenGL 圖形庫項(xiàng)目中一直也沒用過纬乍,最近也想學(xué)著使用這個(gè)圖形庫笔时,感覺還是很有意思,也就自然想著好好的總結(jié)一下斤寂,希望對(duì)大家能有所幫助。下面內(nèi)容來自歡迎來到OpenGL的世界揪惦。
1. OpenGL 圖形庫使用(一) —— 概念基礎(chǔ)
2. OpenGL 圖形庫使用(二) —— 渲染模式遍搞、對(duì)象、擴(kuò)展和狀態(tài)機(jī)
3. OpenGL 圖形庫使用(三) —— 著色器丹擎、數(shù)據(jù)類型與輸入輸出
4. OpenGL 圖形庫使用(四) —— Uniform及更多屬性
5. OpenGL 圖形庫使用(五) —— 紋理
6. OpenGL 圖形庫使用(六) —— 變換
7. OpenGL 圖形庫的使用(七)—— 坐標(biāo)系統(tǒng)之五種不同的坐標(biāo)系統(tǒng)(一)
8. OpenGL 圖形庫的使用(八)—— 坐標(biāo)系統(tǒng)之3D效果(二)
9. OpenGL 圖形庫的使用(九)—— 攝像機(jī)(一)
10. OpenGL 圖形庫的使用(十)—— 攝像機(jī)(二)
11. OpenGL 圖形庫的使用(十一)—— 光照之顏色
12. OpenGL 圖形庫的使用(十二)—— 光照之基礎(chǔ)光照
13. OpenGL 圖形庫的使用(十三)—— 光照之材質(zhì)
14. OpenGL 圖形庫的使用(十四)—— 光照之光照貼圖
15. OpenGL 圖形庫的使用(十五)—— 光照之投光物
16. OpenGL 圖形庫的使用(十六)—— 光照之多光源
17. OpenGL 圖形庫的使用(十七)—— 光照之復(fù)習(xí)總結(jié)
18. OpenGL 圖形庫的使用(十八)—— 模型加載之Assimp
19. OpenGL 圖形庫的使用(十九)—— 模型加載之網(wǎng)格
20. OpenGL 圖形庫的使用(二十)—— 模型加載之模型
21. OpenGL 圖形庫的使用(二十一)—— 高級(jí)OpenGL之深度測試
22. OpenGL 圖形庫的使用(二十二)—— 高級(jí)OpenGL之模板測試Stencil testing
23. OpenGL 圖形庫的使用(二十三)—— 高級(jí)OpenGL之混合Blending
24. OpenGL 圖形庫的使用(二十四)—— 高級(jí)OpenGL之面剔除Face culling
25. OpenGL 圖形庫的使用(二十五)—— 高級(jí)OpenGL之幀緩沖Framebuffers
26. OpenGL 圖形庫的使用(二十六)—— 高級(jí)OpenGL之立方體貼圖Cubemaps
27. OpenGL 圖形庫的使用(二十七)—— 高級(jí)OpenGL之高級(jí)數(shù)據(jù)Advanced Data
28. OpenGL 圖形庫的使用(二十八)—— 高級(jí)OpenGL之高級(jí)GLSL Advanced GLSL
29. OpenGL 圖形庫的使用(二十九)—— 高級(jí)OpenGL之幾何著色器Geometry Shader
30. OpenGL 圖形庫的使用(三十)—— 高級(jí)OpenGL之實(shí)例化Instancing
31. OpenGL 圖形庫的使用(三十一)—— 高級(jí)OpenGL之抗鋸齒Anti Aliasing
32. OpenGL 圖形庫的使用(三十二)—— 高級(jí)光照之高級(jí)光照Advanced Lighting
33. OpenGL 圖形庫的使用(三十三)—— 高級(jí)光照之Gamma校正Gamma Correction
34. OpenGL 圖形庫的使用(三十四)—— 高級(jí)光照之陰影 - 陰影映射Shadow Mapping
35. OpenGL 圖形庫的使用(三十五)—— 高級(jí)光照之陰影 - 點(diǎn)陰影Point Shadows
36. OpenGL 圖形庫的使用(三十六)—— 高級(jí)光照之法線貼圖Normal Mapping
37. OpenGL 圖形庫的使用(三十七)—— 高級(jí)光照之視差貼圖Parallax Mapping
38. OpenGL 圖形庫的使用(三十八)—— 高級(jí)光照之HDR
39. OpenGL 圖形庫的使用(三十九)—— 高級(jí)光照之泛光
40. OpenGL 圖形庫的使用(四十)—— 高級(jí)光照之延遲著色法Deferred Shading
41. OpenGL 圖形庫的使用(四十一)—— 高級(jí)光照之SSAO
42. OpenGL 圖形庫的使用(四十二)—— PBR之理論Theory
43. OpenGL 圖形庫的使用(四十三)—— PBR之光照Lighting
44. OpenGL 圖形庫的使用(四十四)—— PBR之幾篇沒有翻譯的英文原稿
45. OpenGL 圖形庫的使用(四十五)—— 實(shí)戰(zhàn)之調(diào)試Debugging
文本渲染
當(dāng)你在圖形計(jì)算領(lǐng)域冒險(xiǎn)到了一定階段以后你可能會(huì)想使用OpenGL來繪制文本尾抑。然而,可能與你想象的并不一樣蒂培,使用像OpenGL這樣的底層庫來把文本渲染到屏幕上并不是一件簡單的事情再愈。如果你只需要繪制128種不同的字符(Character),那么事情可能會(huì)簡單一些护戳。但是如果你要繪制的字符有著不同的寬翎冲、高和邊距,事情馬上就復(fù)雜了媳荒。根據(jù)你使用語言的不同抗悍,你可能會(huì)需要多于128個(gè)字符。再者钳枕,如果你要繪制音樂符缴渊、數(shù)學(xué)符號(hào)這些特殊的符號(hào);或者渲染豎排文本呢鱼炒?一旦你把文本這些復(fù)雜的情況考慮進(jìn)來衔沼,你就不會(huì)奇怪為什么OpenGL這樣的底層API沒有包含文本處理了。
由于OpenGL本身并沒有包含任何的文本處理能力昔瞧,我們必須自己定義一套全新的系統(tǒng)讓OpenGL繪制文本到屏幕上指蚁。由于文本字符沒有圖元,我們必須要有點(diǎn)創(chuàng)造力才行自晰。需要使用的一些技術(shù)可以是:通過GL_LINES來繪制字形凝化,創(chuàng)建文本的3D網(wǎng)格(Mesh),或在3D環(huán)境中將字符紋理渲染到2D四邊形上酬荞。
開發(fā)者最常用的一種方式是將字符紋理繪制到四邊形上搓劫。繪制這些紋理四邊形本身其實(shí)并不是很復(fù)雜,然而檢索要繪制文本的紋理卻變成了一項(xiàng)有挑戰(zhàn)性的工作混巧。本教程將探索多種文本渲染的實(shí)現(xiàn)方法枪向,并且使用FreeType庫實(shí)現(xiàn)一個(gè)更加高級(jí)但更靈活的渲染文本技術(shù)。
經(jīng)典文本渲染:位圖字體
早期的時(shí)候牲剃,渲染文本是通過選擇一個(gè)需要的字體(Font)(或者自己創(chuàng)建一個(gè))遣疯,并提取這個(gè)字體中所有相關(guān)的字符,將它們放到一個(gè)單獨(dú)的大紋理中來實(shí)現(xiàn)的。這樣一張紋理叫做位圖字體(Bitmap Font)
缠犀,它在紋理的預(yù)定義區(qū)域中包含了我們想要使用的所有字符数苫。字體的這些字符被稱為字形(Glyph)
。每個(gè)字形都關(guān)聯(lián)著一個(gè)特定的紋理坐標(biāo)區(qū)域辨液。當(dāng)你想要渲染一個(gè)字符的時(shí)候虐急,你只需要通過渲染這一塊特定的位圖字體區(qū)域到2D四邊形上即可。
你可以看到滔迈,我們?nèi)∫粡埼粓D字體止吁,(通過仔細(xì)選擇紋理坐標(biāo))從紋理中采樣對(duì)應(yīng)的字形,并渲染它們到多個(gè)2D四邊形上燎悍,最終渲染出“OpenGL”文本敬惦。通過啟用混合,讓背景保持透明谈山,最終就能渲染一個(gè)字符串到屏幕上俄删。這個(gè)位圖字體是通過Codehead的位圖字體生成器生成的。
使用這種方式繪制文本有許多優(yōu)勢也有很多缺點(diǎn)奏路。首先畴椰,它相對(duì)來說很容易實(shí)現(xiàn),并且因?yàn)槲粓D字體已經(jīng)預(yù)光柵化了鸽粉,它的效率也很高斜脂。然而,這種方式不夠靈活触机。當(dāng)你想要使用不同的字體時(shí)帚戳,你需要重新編譯一套全新的位圖字體,而且你的程序會(huì)被限制在一個(gè)固定的分辨率威兜。如果你對(duì)這些文本進(jìn)行縮放的話你會(huì)看到文本的像素邊緣销斟。此外庐椒,這種方式通常會(huì)局限于非常小的字符集椒舵,如果你想讓它來支持Extended或者Unicode字符的話就很不現(xiàn)實(shí)了。
這種繪制文本的方式曾經(jīng)得益于它的高速和可移植性而非常流行约谈,然而現(xiàn)在已經(jīng)出現(xiàn)更加靈活的方式了笔宿。其中一個(gè)是我們即將討論的使用FreeType
庫來加載TrueType
字體的方式。
現(xiàn)代文本渲染:FreeType
FreeType是一個(gè)能夠用于加載字體并將他們渲染到位圖以及提供多種字體相關(guān)的操作的軟件開發(fā)庫棱诱。它是一個(gè)非常受歡迎的跨平臺(tái)字體庫泼橘,它被用于Mac OS X、Java迈勋、PlayStation主機(jī)炬灭、Linux、Android等平臺(tái)靡菇。FreeType的真正吸引力在于它能夠加載TrueType字體重归。
TrueType字體不是用像素或其他不可縮放的方式來定義的米愿,它是通過數(shù)學(xué)公式(曲線的組合)來定義的。類似于矢量圖像鼻吮,這些光柵化后的字體圖像可以根據(jù)需要的字體高度來生成育苟。通過使用TrueType字體,你可以輕易渲染不同大小的字形而不造成任何質(zhì)量損失椎木。
FreeType可以在他們的官方網(wǎng)站中下載到违柏。你可以選擇自己用源碼編譯這個(gè)庫,如果支持你的平臺(tái)的話香椎,你也可以使用他們預(yù)編譯好的庫漱竖。請(qǐng)確認(rèn)你將freetype.lib添加到你項(xiàng)目的鏈接庫中,并且確認(rèn)編譯器知道頭文件的位置畜伐。
然后請(qǐng)確認(rèn)包含合適的頭文件:
#include <ft2build.h>
#include FT_FREETYPE_H
由于FreeType的開發(fā)方式(至少在我寫這篇文章的時(shí)候)闲孤,你不能將它們的頭文件放到一個(gè)新的目錄下。它們應(yīng)該保存在你include目錄的根目錄下烤礁。通過使用像
#include <FreeType/ft2build.h>
這樣的方式導(dǎo)入FreeType可能會(huì)出現(xiàn)一些頭文件沖突的問題讼积。
FreeType所做的事就是加載TrueType字體并為每一個(gè)字形生成位圖以及計(jì)算幾個(gè)度量值(Metric)。我們可以提取出它生成的位圖作為字形的紋理脚仔,并使用這些度量值定位字符的字形勤众。
要加載一個(gè)字體,我們只需要初始化FreeType庫鲤脏,并且將這個(gè)字體加載為一個(gè)FreeType稱之為面(Face)的東西们颜。這里為我們加載一個(gè)從Windows/Fonts目錄中拷貝來的TrueType
字體文件arial.ttf
。
FT_Library ft;
if (FT_Init_FreeType(&ft))
std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
FT_Face face;
if (FT_New_Face(ft, "fonts/arial.ttf", 0, &face))
std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
這些FreeType函數(shù)在出現(xiàn)錯(cuò)誤時(shí)將返回一個(gè)非零的整數(shù)值猎醇。
當(dāng)面加載完成之后窥突,我們需要定義字體大小,這表示著我們要從字體面中生成多大的字形:
FT_Set_Pixel_Sizes(face, 0, 48);
此函數(shù)設(shè)置了字體面的寬度和高度硫嘶,將寬度值設(shè)為0表示我們要從字體面通過給定的高度中動(dòng)態(tài)計(jì)算出字形的寬度阻问。
一個(gè)FreeType面中包含了一個(gè)字形的集合。我們可以調(diào)用FT_Load_Char
函數(shù)來將其中一個(gè)字形設(shè)置為激活字形沦疾。這里我們選擇加載字符字形’X’:
if (FT_Load_Char(face, 'X', FT_LOAD_RENDER))
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
通過將FT_LOAD_RENDER
設(shè)為加載標(biāo)記之一称近,我們告訴FreeType去創(chuàng)建一個(gè)8位的灰度位圖,我們可以通過face->glyph->bitmap
來訪問這個(gè)位圖哮塞。
使用FreeType加載的每個(gè)字形沒有相同的大信俑选(不像位圖字體那樣)。使用FreeType生成的位圖的大小恰好能包含這個(gè)字符可見區(qū)域忆畅。例如生成用于表示’.’的位圖的大小要比表示’X’的小得多衡未。因此,F(xiàn)reeType同樣也加載了一些度量值來指定每個(gè)字符的大小和位置。下面這張圖展示了FreeType對(duì)每一個(gè)字符字形計(jì)算的所有度量值缓醋。
每一個(gè)字形都放在一個(gè)水平的基準(zhǔn)線(Baseline)
上(即上圖中水平箭頭指示的那條線)剔交。一些字形恰好位于基準(zhǔn)線上(如’X’),而另一些則會(huì)稍微越過基準(zhǔn)線以下(如’g’或’p’)(譯注:即這些帶有下伸部的字母改衩,可以見這里)岖常。這些度量值精確定義了擺放字形所需的每個(gè)字形距離基準(zhǔn)線的偏移量,每個(gè)字形的大小葫督,以及需要預(yù)留多少空間來渲染下一個(gè)字形竭鞍。下面這個(gè)表列出了我們需要的所有屬性。
在需要渲染字符時(shí)橄镜,我們可以加載一個(gè)字符字形偎快,獲取它的度量值,并生成一個(gè)紋理洽胶,但每一幀都這樣做會(huì)非常沒有效率晒夹。我們應(yīng)將這些生成的數(shù)據(jù)儲(chǔ)存在程序的某一個(gè)地方,在需要渲染字符的時(shí)候再去調(diào)用姊氓。我們會(huì)定義一個(gè)非常方便的結(jié)構(gòu)體丐怯,并將這些結(jié)構(gòu)體存儲(chǔ)在一個(gè)map中。
struct Character {
GLuint TextureID; // 字形紋理的ID
glm::ivec2 Size; // 字形大小
glm::ivec2 Bearing; // 從基準(zhǔn)線到字形左部/頂部的偏移值
GLuint Advance; // 原點(diǎn)距下一個(gè)字形原點(diǎn)的距離
};
std::map<GLchar, Character> Characters;
對(duì)于這個(gè)教程來說翔横,本著讓一切簡單的目的读跷,我們只生成ASCII字符集的前128個(gè)字符。對(duì)每一個(gè)字符禾唁,我們生成一個(gè)紋理并保存相關(guān)數(shù)據(jù)至Character
結(jié)構(gòu)體中效览,之后再添加至Characters這個(gè)映射表中。這樣子荡短,渲染一個(gè)字符所需的所有數(shù)據(jù)就都被儲(chǔ)存下來備用了丐枉。
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); //禁用字節(jié)對(duì)齊限制
for (GLubyte c = 0; c < 128; c++)
{
// 加載字符的字形
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
continue;
}
// 生成紋理
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
// 設(shè)置紋理選項(xiàng)
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);
// 儲(chǔ)存字符供之后使用
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
Characters.insert(std::pair<GLchar, Character>(c, character));
}
在這個(gè)for循環(huán)中我們遍歷了ASCII集中的全部128個(gè)字符,并獲取它們對(duì)應(yīng)的字符字形掘托。對(duì)每一個(gè)字符瘦锹,我們生成了一個(gè)紋理,設(shè)置了它的選項(xiàng)烫映,并儲(chǔ)存了它的度量值沼本。有趣的是我們這里將紋理的internalFormat
和format
設(shè)置為GL_RED
噩峦。通過字形生成的位圖是一個(gè)8位灰度圖锭沟,它的每一個(gè)顏色都由一個(gè)字節(jié)來表示。因此我們需要將位圖緩沖的每一字節(jié)都作為紋理的顏色值识补。這是通過創(chuàng)建一個(gè)特殊的紋理實(shí)現(xiàn)的族淮,這個(gè)紋理的每一字節(jié)都對(duì)應(yīng)著紋理顏色的紅色分量(顏色向量的第一個(gè)字節(jié))。如果我們使用一個(gè)字節(jié)來表示紋理的顏色,我們需要注意OpenGL的一個(gè)限制:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
OpenGL要求所有的紋理都是4字節(jié)對(duì)齊的祝辣,即紋理的大小永遠(yuǎn)是4字節(jié)的倍數(shù)贴妻。通常這并不會(huì)出現(xiàn)什么問題,因?yàn)榇蟛糠旨y理的寬度都為4的倍數(shù)并/或每像素使用4個(gè)字節(jié)蝙斜,但是現(xiàn)在我們每個(gè)像素只用了一個(gè)字節(jié)名惩,它可以是任意的寬度。通過將紋理解壓對(duì)齊參數(shù)設(shè)為1孕荠,這樣才能確保不會(huì)有對(duì)齊問題(它可能會(huì)造成段錯(cuò)誤)娩鹉。
當(dāng)你處理完字形后不要忘記清理FreeType的資源。
FT_Done_Face(face);
FT_Done_FreeType(ft);
1. 著色器
我們將使用下面的頂點(diǎn)著色器來渲染字形:
#version 330 core
layout (location = 0) in vec4 vertex; // <vec2 pos, vec2 tex>
out vec2 TexCoords;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
TexCoords = vertex.zw;
}
我們將位置和紋理紋理坐標(biāo)的數(shù)據(jù)合起來存在一個(gè)vec4
中稚伍。這個(gè)頂點(diǎn)著色器將位置坐標(biāo)與一個(gè)投影矩陣相乘弯予,并將紋理坐標(biāo)傳遞給片段著色器:
#version 330 core
in vec2 TexCoords;
out vec4 color;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
vec4 sampled = vec4(1.0, 1.0, 1.0, texture(text, TexCoords).r);
color = vec4(textColor, 1.0) * sampled;
}
片段著色器有兩個(gè)uniform
變量:一個(gè)是單顏色通道的字形位圖紋理,另一個(gè)是顏色uniform个曙,它可以用來調(diào)整文本的最終顏色锈嫩。我們首先從位圖紋理中采樣顏色值,由于紋理數(shù)據(jù)中僅存儲(chǔ)著紅色分量垦搬,我們就采樣紋理的r分量來作為取樣的alpha值呼寸。通過變換顏色的alpha值,最終的顏色在字形背景顏色上會(huì)是透明的猴贰,而在真正的字符像素上是不透明的等舔。我們也將RGB顏色與textColor這個(gè)uniform相乘,來變換文本顏色糟趾。
當(dāng)然我們需要啟用混合才能讓這一切行之有效:
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
對(duì)于投影矩陣慌植,我們將使用一個(gè)正射投影矩陣(Orthographic Projection Matrix)
。對(duì)于文本渲染我們(通常)都不需要透視义郑,使用正射投影同樣允許我們在屏幕坐標(biāo)系中設(shè)定所有的頂點(diǎn)坐標(biāo)蝶柿,如果我們使用如下方式配置:
glm::mat4 projection = glm::ortho(0.0f, 800.0f, 0.0f, 600.0f);
我們設(shè)置投影矩陣的底部參數(shù)為0.0f,并將頂部參數(shù)設(shè)置為窗口的高度非驮。這樣做的結(jié)果是我們指定了y坐標(biāo)的范圍為屏幕底部(0.0f)至屏幕頂部(600.0f)交汤。這意味著現(xiàn)在點(diǎn)(0.0, 0.0)對(duì)應(yīng)左下角(譯注:而不再是窗口正中間)。
最后要做的事是創(chuàng)建一個(gè)VBO
和VAO
用來渲染四邊形〗袤希現(xiàn)在我們在初始化VBO時(shí)分配足夠的內(nèi)存芙扎,這樣我們可以在渲染字符的時(shí)候再來更新VBO的內(nèi)存。
GLuint VAO, VBO;
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
每個(gè)2D四邊形需要6個(gè)頂點(diǎn)填大,每個(gè)頂點(diǎn)又是由一個(gè)4float向量(譯注:一個(gè)紋理坐標(biāo)和一個(gè)頂點(diǎn)坐標(biāo))組成戒洼,因此我們將VBO的內(nèi)存分配為6 * 4個(gè)float的大小。由于我們會(huì)在繪制字符時(shí)經(jīng)常更新VBO的內(nèi)存允华,所以我們將內(nèi)存類型設(shè)置為GL_DYNAMIC_DRAW
圈浇。
2. 渲染一行文本
要渲染一個(gè)字符寥掐,我們從之前創(chuàng)建的Characters
映射表中取出對(duì)應(yīng)的Character結(jié)構(gòu)體,并根據(jù)字符的度量值來計(jì)算四邊形的維度磷蜀。根據(jù)四邊形的維度我們就能動(dòng)態(tài)計(jì)算出6個(gè)描述四邊形的頂點(diǎn)召耘,并使用glBufferSubData
函數(shù)更新VBO所管理內(nèi)存的內(nèi)容。
我們創(chuàng)建一個(gè)叫做RenderText
的函數(shù)渲染一個(gè)字符串:
void RenderText(Shader &s, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)
{
// 激活對(duì)應(yīng)的渲染狀態(tài)
s.Use();
glUniform3f(glGetUniformLocation(s.Program, "textColor"), color.x, color.y, color.z);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(VAO);
// 遍歷文本中所有的字符
std::string::const_iterator c;
for (c = text.begin(); c != text.end(); c++)
{
Character ch = Characters[*c];
GLfloat xpos = x + ch.Bearing.x * scale;
GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale;
GLfloat w = ch.Size.x * scale;
GLfloat h = ch.Size.y * scale;
// 對(duì)每個(gè)字符更新VBO
GLfloat vertices[6][4] = {
{ xpos, ypos + h, 0.0, 0.0 },
{ xpos, ypos, 0.0, 1.0 },
{ xpos + w, ypos, 1.0, 1.0 },
{ xpos, ypos + h, 0.0, 0.0 },
{ xpos + w, ypos, 1.0, 1.0 },
{ xpos + w, ypos + h, 1.0, 0.0 }
};
// 在四邊形上繪制字形紋理
glBindTexture(GL_TEXTURE_2D, ch.textureID);
// 更新VBO內(nèi)存的內(nèi)容
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 繪制四邊形
glDrawArrays(GL_TRIANGLES, 0, 6);
// 更新位置到下一個(gè)字形的原點(diǎn)褐隆,注意單位是1/64像素
x += (ch.Advance >> 6) * scale; // 位偏移6個(gè)單位來獲取單位為像素的值 (2^6 = 64)
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
這個(gè)函數(shù)的內(nèi)容應(yīng)該非常明顯了:我們首先計(jì)算出四邊形的原點(diǎn)坐標(biāo)(為xpos
和ypos
)和它的大形鬯(為w和h),并生成6個(gè)頂點(diǎn)形成這個(gè)2D四邊形庶弃;注意我們將每個(gè)度量值都使用scale進(jìn)行縮放轨蛤。接下來我們更新了VBO的內(nèi)容、并渲染了這個(gè)四邊形虫埂。
其中這行代碼需要加倍留意:
GLfloat ypos = y - (ch.Size.y - ch.Bearing.y);
一些字符(如’p’或’q’)需要被渲染到基準(zhǔn)線以下祥山,因此字形四邊形也應(yīng)該被擺放在RenderText的y值以下。ypos的偏移量可以從字形的度量值中得出:
要計(jì)算這段距離掉伏,即偏移量缝呕,我們需要找出字形在基準(zhǔn)線之下延展出去的距離。在上圖中這段距離用紅色箭頭標(biāo)出斧散。從度量值中可以看到供常,我們可以通過用字形的高度減去bearingY
來計(jì)算這段向量的長度。對(duì)于那些正好位于基準(zhǔn)線上的字符(如’X’)鸡捐,這個(gè)值正好是0.0栈暇。而對(duì)于那些超出基準(zhǔn)線的字符(如’g’或’j’),這個(gè)值則是正的箍镜。
如果你每件事都做對(duì)了源祈,那么你現(xiàn)在已經(jīng)可以使用下面的語句成功渲染字符串了:
RenderText(shader, "This is sample text", 25.0f, 25.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f));
RenderText(shader, "(C) LearnOpenGL.com", 540.0f, 570.0f, 0.5f, glm::vec3(0.3, 0.7f, 0.9f));
渲染效果看上去像這樣:
你可以從這里獲取這個(gè)例子的源代碼。
// Std. Includes
#include <iostream>
#include <map>
#include <string>
// GLEW
#define GLEW_STATIC
#include <GL/glew.h>
// GLFW
#include <GLFW/glfw3.h>
// GLM
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
// FreeType
#include <ft2build.h>
#include FT_FREETYPE_H
// GL includes
#include "Shader.h"
// Properties
const GLuint WIDTH = 800, HEIGHT = 600;
/// Holds all state information relevant to a character as loaded using FreeType
struct Character {
GLuint TextureID; // ID handle of the glyph texture
glm::ivec2 Size; // Size of glyph
glm::ivec2 Bearing; // Offset from baseline to left/top of glyph
GLuint Advance; // Horizontal offset to advance to next glyph
};
std::map<GLchar, Character> Characters;
GLuint VAO, VBO;
void RenderText(Shader &shader, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color);
// The MAIN function, from here we start our application and run the Game loop
int main()
{
// Init GLFW
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "LearnOpenGL", nullptr, nullptr); // Windowed
glfwMakeContextCurrent(window);
// Initialize GLEW to setup the OpenGL Function pointers
glewExperimental = GL_TRUE;
glewInit();
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
// Set OpenGL options
glEnable(GL_CULL_FACE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Compile and setup the shader
Shader shader("shaders/text.vs", "shaders/text.frag");
glm::mat4 projection = glm::ortho(0.0f, static_cast<GLfloat>(WIDTH), 0.0f, static_cast<GLfloat>(HEIGHT));
shader.Use();
glUniformMatrix4fv(glGetUniformLocation(shader.Program, "projection"), 1, GL_FALSE, glm::value_ptr(projection));
// FreeType
FT_Library ft;
// All functions return a value different than 0 whenever an error occurred
if (FT_Init_FreeType(&ft))
std::cout << "ERROR::FREETYPE: Could not init FreeType Library" << std::endl;
// Load font as face
FT_Face face;
if (FT_New_Face(ft, "fonts/arial.ttf", 0, &face))
std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
// Set size to load glyphs as
FT_Set_Pixel_Sizes(face, 0, 48);
// Disable byte-alignment restriction
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
// Load first 128 characters of ASCII set
for (GLubyte c = 0; c < 128; c++)
{
// Load character glyph
if (FT_Load_Char(face, c, FT_LOAD_RENDER))
{
std::cout << "ERROR::FREETYTPE: Failed to load Glyph" << std::endl;
continue;
}
// Generate texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_RED,
face->glyph->bitmap.width,
face->glyph->bitmap.rows,
0,
GL_RED,
GL_UNSIGNED_BYTE,
face->glyph->bitmap.buffer
);
// Set texture options
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);
// Now store character for later use
Character character = {
texture,
glm::ivec2(face->glyph->bitmap.width, face->glyph->bitmap.rows),
glm::ivec2(face->glyph->bitmap_left, face->glyph->bitmap_top),
face->glyph->advance.x
};
Characters.insert(std::pair<GLchar, Character>(c, character));
}
glBindTexture(GL_TEXTURE_2D, 0);
// Destroy FreeType once we're finished
FT_Done_Face(face);
FT_Done_FreeType(ft);
// Configure VAO/VBO for texture quads
glGenVertexArrays(1, &VAO);
glGenBuffers(1, &VBO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
// Game loop
while (!glfwWindowShouldClose(window))
{
// Check and call events
glfwPollEvents();
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
RenderText(shader, "This is sample text", 25.0f, 25.0f, 1.0f, glm::vec3(0.5, 0.8f, 0.2f));
RenderText(shader, "(C) LearnOpenGL.com", 540.0f, 570.0f, 0.5f, glm::vec3(0.3, 0.7f, 0.9f));
// Swap the buffers
glfwSwapBuffers(window);
}
glfwTerminate();
return 0;
}
void RenderText(Shader &shader, std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)
{
// Activate corresponding render state
shader.Use();
glUniform3f(glGetUniformLocation(shader.Program, "textColor"), color.x, color.y, color.z);
glActiveTexture(GL_TEXTURE0);
glBindVertexArray(VAO);
// Iterate through all characters
std::string::const_iterator c;
for (c = text.begin(); c != text.end(); c++)
{
Character ch = Characters[*c];
GLfloat xpos = x + ch.Bearing.x * scale;
GLfloat ypos = y - (ch.Size.y - ch.Bearing.y) * scale;
GLfloat w = ch.Size.x * scale;
GLfloat h = ch.Size.y * scale;
// Update VBO for each character
GLfloat vertices[6][4] = {
{ xpos, ypos + h, 0.0, 0.0 },
{ xpos, ypos, 0.0, 1.0 },
{ xpos + w, ypos, 1.0, 1.0 },
{ xpos, ypos + h, 0.0, 0.0 },
{ xpos + w, ypos, 1.0, 1.0 },
{ xpos + w, ypos + h, 1.0, 0.0 }
};
// Render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, ch.TextureID);
// Update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Render quad
glDrawArrays(GL_TRIANGLES, 0, 6);
// Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
x += (ch.Advance >> 6) * scale; // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels))
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
為了讓你更好理解我們是怎么計(jì)算四邊形頂點(diǎn)的色迂,我們可以關(guān)閉混合來看看真正渲染出來的四邊形是什么樣子的:
可以看到香缺,大部分的四邊形都位于一條(想象中的)基準(zhǔn)線上,而對(duì)應(yīng)’p’或’(‘字形的四邊形則稍微向下偏移了一些歇僧。
更進(jìn)一步
本教程演示了如何使用FreeType庫繪制TrueType文本图张。這種方式靈活、可縮放并支持多種字符編碼诈悍。然而祸轮,由于我們對(duì)每一個(gè)字形都生成并渲染了紋理,你的應(yīng)用程序可能并不需要這么強(qiáng)大的功能侥钳。性能更好的位圖字體也許是更可取的适袜,因?yàn)閷?duì)所有的字形我們只需要一個(gè)紋理。當(dāng)然慕趴,最好的方式是結(jié)合這兩種方式痪蝇,動(dòng)態(tài)生成包含所有字符字形的位圖字體紋理鄙陡,并用FreeType加載冕房。這為渲染器節(jié)省了大量紋理切換的開銷躏啰,并且根據(jù)字形的排列緊密程度也可以節(jié)省很多的性能開銷。
另一個(gè)使用FreeType字體的問題是字形紋理是儲(chǔ)存為一個(gè)固定的字體大小的耙册,因此直接對(duì)其放大就會(huì)出現(xiàn)鋸齒邊緣给僵。此外,對(duì)字形進(jìn)行旋轉(zhuǎn)還會(huì)使它們看上去變得模糊详拙。這個(gè)問題可以通過儲(chǔ)存每個(gè)像素距最近的字形輪廓的距離帝际,而不是光柵化的像素顏色,來緩解饶辙。這項(xiàng)技術(shù)被稱為有向距離場(Signed Distance Fields)
蹲诀,Valve在幾年前發(fā)表過一篇了論文,討論了他們通過這項(xiàng)技術(shù)來獲得非常棒的3D渲染效果弃揽。
后記
本篇已結(jié)束脯爪,后面更精彩~~~~