舊文 OpenGL ES 文字渲染方式有幾種? 一文中分別介紹了 OpenGL 利用 Canvas 和 FreeType 繪製文字的方法。 不管採用哪一種方式進行渲染,本質上原理都是紋理貼圖:將帶有文字的圖像上傳到紋理,而後進行貼圖。git
利用 Canvas 繪製中文字體和繪製其餘字體在操做方式上沒有區別,可是使用 FreeType 繪製中文字體,在編碼方式、加載方式以及字體屬性上面會有一些坑要踩,這裏本人已經踩過,將在本文中分享給各位讀者大人。github
關於 FreeType 前文已經進行了詳細的介紹,它是一個基於 C 語言實現的用於文字渲染的跨平臺開源庫,它小巧、高效、高度可定製,主要用於加載字體並將其渲染到位圖,支持多種字體的相關操做。緩存
TrueType 字體不採用像素或其餘不可縮放的方式來定義,而是一些經過數學公式(曲線的組合)。這些字形,相似於矢量圖像,能夠根據你須要的字體大小來生成像素圖像。微信
FreeType 官網地址:markdown
https://www.freetype.org/
複製代碼
關於 FreeType 開源庫多個平臺的編譯方法,一樣請參考舊文 OpenGL ES 文字渲染方式有幾種? ,這裏再也不重複講述。函數
使用 FreeType 渲染中文和英文字符在流程上基本一致,都是根據字符的編碼值來加載位圖,而後上傳紋理。oop
與 ASCII 碼不一樣的是,中文字符采用 2 字節的 Unicode 編碼,因此加載字體以前,首先須要設置編碼類型:post
FT_Select_Charmap(face, ft_encoding_unicode);
複製代碼
另外,中文字符串須要採用寬字符 wchar_t 。字體
FreeType 加載中文字符位圖須要,先根據 Unicode 編碼值查詢位圖的索引,而後根據索引獲取到 FreeType 的 Glyph 對象,最後再將 FT_Glyph 轉換爲 FT_BitmapGlyph 獲取到字體的位圖。ui
加載中文字符串對應位圖的代碼以下;
void TextRenderSample::LoadFacesByUnicode(const wchar_t* text, int size) {
// FreeType
FT_Library ft;
// All functions return a value different than 0 whenever an error occurred
if (FT_Init_FreeType(&ft))
LOGCATE("TextRenderSample::LoadFacesByUnicode FREETYPE: Could not init FreeType Library");
// Load font as face
FT_Face face;
std::string path(DEFAULT_OGL_ASSETS_DIR);
if (FT_New_Face(ft, (path + "/fonts/msyh.ttc").c_str(), 0, &face))
LOGCATE("TextRenderSample::LoadFacesByUnicode FREETYPE: Failed to load font");
// Set size to load glyphs as
FT_Set_Pixel_Sizes(face, 96, 96);
FT_Select_Charmap(face, ft_encoding_unicode);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
for (int i = 0; i < size; ++i) {
//int index = FT_Get_Char_Index(face,unicodeArr[i]);
if (FT_Load_Glyph(face, FT_Get_Char_Index(face, text[i]), FT_LOAD_DEFAULT))
{
LOGCATE("TextRenderSample::LoadFacesByUnicode FREETYTPE: Failed to load Glyph");
continue;
}
FT_Glyph glyph;
FT_Get_Glyph(face->glyph, &glyph );
//Convert the glyph to a bitmap.
FT_Glyph_To_Bitmap(&glyph, ft_render_mode_normal, 0, 1 );
FT_BitmapGlyph bitmap_glyph = (FT_BitmapGlyph)glyph;
FT_Bitmap& bitmap = bitmap_glyph->bitmap;
// Generate texture
GLuint texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D(
GL_TEXTURE_2D,
0,
GL_LUMINANCE,
bitmap.width,
bitmap.rows,
0,
GL_LUMINANCE,
GL_UNSIGNED_BYTE,
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),
static_cast<GLuint>((glyph->advance.x / MAX_SHORT_VALUE) << 6)
};
m_Characters.insert(std::pair<GLint, Character>(text[i], character));
}
glBindTexture(GL_TEXTURE_2D, 0);
// Destroy FreeType once we're finished
FT_Done_Face(face);
FT_Done_FreeType(ft);
}
複製代碼
代碼中 glyph->advance.x / MAX_SHORT_VALUE 至關於向右移 16 位,是從 FreeType 官方文檔中得出來的結論。
值得反覆強調的地方,針對 OpenGL ES 灰度圖要使用的紋理格式是 GL_LUMINANCE 而不是 GL_RED 。
OpenGL 紋理對應的圖像默認要求 4 字節對齊,這裏須要設置爲 1 ,確保寬度不是 4 倍數的位圖(灰度圖)可以正常渲染。
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
複製代碼
渲染文字使用的 shader :
//vertex shader
#version 300 es
layout(location = 0) in vec4 a_position;// <vec2 pos, vec2 tex>
uniform mat4 u_MVPMatrix;
out vec2 v_texCoord;
void main()
{
gl_Position = u_MVPMatrix * vec4(a_position.xy, 0.0, 1.0);;
v_texCoord = a_position.zw;
}
//fragment shader
#version 300 es
precision mediump float;
in vec2 v_texCoord;
layout(location = 0) out vec4 outColor;
uniform sampler2D s_textTexture;
uniform vec3 u_textColor;
void main()
{
vec4 color = vec4(1.0, 1.0, 1.0, texture(s_textTexture, v_texCoord).r);
outColor = vec4(u_textColor, 1.0) * color;
}
複製代碼
片斷着色器有兩個 uniform 變量:一個是單顏色通道的字形位圖紋理,另外一個是文字的顏色,咱們能夠同調整它來改變最終輸出的字體顏色。
開啓混合,去掉文字背景。
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
複製代碼
生成一個 VAO 和一個 VBO ,用於管理的存儲頂點、紋理座標數據,GL_DYNAMIC_DRAW 表示咱們後面要使用 glBufferSubData 不斷刷新 VBO 的緩存。
glGenVertexArrays(1, &m_VaoId);
glGenBuffers(1, &m_VboId);
glBindVertexArray(m_VaoId);
glBindBuffer(GL_ARRAY_BUFFER, m_VboId);
glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, nullptr, GL_DYNAMIC_DRAW);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 4 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, GL_NONE);
glBindVertexArray(GL_NONE);
複製代碼
每一個 2D 方塊須要 6 個頂點,每一個頂點又是由一個 4 維向量(一個紋理座標和一個頂點座標)組成,所以咱們將VBO 的內存分配爲 6*4 個 float 的大小。
渲染中文字體的函數以下,其中傳入 viewport 主要是針對屏幕座標進行歸一化:
vvoid TextRenderSample::RenderText(const wchar_t *text, int textLen, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color, glm::vec2 viewport) {
// 激活合適的渲染狀態
glUseProgram(m_ProgramObj);
glUniform3f(glGetUniformLocation(m_ProgramObj, "u_textColor"), color.x, color.y, color.z);
glBindVertexArray(m_VaoId);
GO_CHECK_GL_ERROR();
x *= viewport.x;
y *= viewport.y;
for (int i = 0; i < textLen; ++i)
{
Character ch = m_Characters[text[i]];
GLfloat xpos = x + ch.bearing.x * scale;
GLfloat ypos = y - (ch.size.y - ch.bearing.y) * scale;
xpos /= viewport.x;
ypos /= viewport.y;
GLfloat w = ch.size.x * scale;
GLfloat h = ch.size.y * scale;
w /= viewport.x;
h /= viewport.y;
LOGCATE("TextRenderSample::RenderText [xpos,ypos,w,h]=[%f, %f, %f, %f]", xpos, ypos, w, h);
// 當前字符的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 }
};
// 在方塊上繪製字形紋理
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, ch.textureID);
glUniform1i(m_SamplerLoc, 0);
GO_CHECK_GL_ERROR();
// 更新當前字符的VBO
glBindBuffer(GL_ARRAY_BUFFER, m_VboId);
glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices);
GO_CHECK_GL_ERROR();
glBindBuffer(GL_ARRAY_BUFFER, 0);
// 繪製方塊
glDrawArrays(GL_TRIANGLES, 0, 6);
GO_CHECK_GL_ERROR();
// 更新位置到下一個字形的原點,注意單位是1/64像素
x += (ch.advance >> 6) * scale; //(2^6 = 64)
}
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
}
複製代碼
加載和渲染中文字體:
static const wchar_t BYTE_FLOW[] = L"OpenES 渲染中文字體";
// 加載中文字體
LoadFacesByUnicode(BYTE_FLOW, sizeof(BYTE_FLOW)/sizeof(BYTE_FLOW[0]) - 1);
// (x,y)爲屏幕座標系的位置,即原點位於屏幕中心,x(-1.0,1.0), y(-1.0,1.0)
// 渲染中文字體
RenderText(BYTE_FLOW, sizeof(BYTE_FLOW)/sizeof(BYTE_FLOW[0]) - 1, -0.9f, -0.2f, 1.0f, glm::vec3(0.7, 0.4f, 0.2f), viewport);
複製代碼
完整實現代碼見項目:
https://github.com/githubhaohao/NDK_OpenGLES_3_0
複製代碼
中文字體渲染效果:
完整實現代碼見項目: github.com/githubhaoha…
有疑問或技術交流能夠添加個人我的微信:Byte-Flow