How many ways to render text in OpenGL ES? This article introduces OpenGL using Canvas and FreeType drawing text method respectively. Either way you render, the principle is essentially texture mapping: upload an image with text to the texture and then map it.

There is no difference between drawing A Chinese font on Canvas and drawing any other font. However, when drawing a Chinese font on FreeType, there are some problems in encoding, loading and font properties, which I have already done here and will share with you in this article.

FreeType is a small, efficient, and highly customizable library for loading fonts and rendering them into place. It supports a wide variety of font operations.

TrueType fonts are not defined in pixels or other non-scalable ways, but rather by mathematical formulas (combinations of curves). These glyphs, similar to vector images, can be used to generate pixel images based on the font size you need.

FreeType website address:

https://www.freetype.org/
Copy the code

How many ways do you render text in OpenGL ES? “Will not be repeated here.

Rendering Chinese and English characters using FreeType is basically the same process, loading bitmaps based on the encoded values of the characters and then uploading textures.

Unlike ASCII, Chinese characters use the 2-byte Unicode encoding, so before loading the font, you need to set the encoding type:

FT_Select_Charmap(face, ft_encoding_unicode);
Copy the code

In addition, Chinese strings need to use the wide character wchar_t.

FreeType To load a Chinese character bitmap, query the index of the bitmap based on the Unicode encoding value, obtain the FreeType Glyph object based on the index, and then convert FT_Glyph to FT_BitmapGlyph to obtain the font bitmap.

The code for loading the bitmap corresponding to the Chinese string is as follows;

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);
}
Copy the code

Glyph ->advance. X/MAX_SHORT_VALUE = 16 bits to the right.

It’s worth repeating that the texture format to use for OpenGL ES grayscale images is GL_LUMINANCE rather than GL_RED.

OpenGL textures require 4 bytes of alignment by default, so set this to 1 to ensure that bitmaps (grayscale) that are not four multiples of width will render properly.

glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
Copy the code

Render text using 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; }Copy the code

The fragment shader has two uniform variables: one is the font bitmap texture with a single color channel, and the other is the text color, which we can adjust to change the final output font color.

Turn on blending and remove the text background.

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);  
Copy the code

Generate a VAO and a VBO to manage the storage of vertex and texture coordinate data. GL_DYNAMIC_DRAW indicates that we will use glBufferSubData to refresh the VBO cache.


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);
Copy the code

Each 2D square requires 6 vertices, each of which in turn consists of a 4-dimensional vector (a texture coordinate and a vertex coordinate), so we allocate VBO memory to the size of 6*4 floats.

The function for rendering a Chinese font is as follows, where the viewport is passed to normalize the screen coordinates:

vvoid TextRenderSample::RenderText(const wchar_t *text, int textLen, GLfloat x, GLfloat y, GLfloat scale,
								  glm::vec3 color, glm::vec2 viewport) {
	// Activate the appropriate render state
	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 of the current 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}};// Draw a glyph texture on the square
		glActiveTexture(GL_TEXTURE0);
		glBindTexture(GL_TEXTURE_2D, ch.textureID);
		glUniform1i(m_SamplerLoc, 0);
		GO_CHECK_GL_ERROR();
		// Update the VBO of the current character
		glBindBuffer(GL_ARRAY_BUFFER, m_VboId);
		glBufferSubData(GL_ARRAY_BUFFER, 0.sizeof(vertices), vertices);
		GO_CHECK_GL_ERROR();
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		// Draw the block
		glDrawArrays(GL_TRIANGLES, 0.6);
		GO_CHECK_GL_ERROR();
		// Update the position to the origin of the next glyph, noting that the unit is 1/64 pixel
		x += (ch.advance >> 6) * scale; / / (2 ^ 6 = 64)
	}
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);
}

Copy the code

Loading and rendering Chinese fonts:

static const wchar_t BYTE_FLOW[] = L"OpenES Render Chinese font";

// Load the Chinese font
LoadFacesByUnicode(BYTE_FLOW, sizeof(BYTE_FLOW)/sizeof(BYTE_FLOW[0]) - 1);

/ / (x, y) for the position of the screen coordinate system, namely the origin is located in the center of the screen, x (1.0, 1.0), y (1.0, 1.0)
// Render the Chinese font
RenderText(BYTE_FLOW, sizeof(BYTE_FLOW)/sizeof(BYTE_FLOW[0]) - 1.0.9 f.0.2 f.1.0 f, glm::vec3(0.7.0.4 f.0.2 f), viewport);
Copy the code

See the project for the full implementation code:

https://github.com/githubhaohao/NDK_OpenGLES_3_0
Copy the code

Chinese font rendering effect:

For the full implementation code, see the project: github.com/githubhaoha…

Technical communication

For questions or technical exchanges, you can add my personal wechat: Bytes-Flow