Resurse bonus

Redare text în OpenGL

OpenGL este o bibliotecă ce pune la dispoziție funcționalități de nivel scăzut și nu conține capabilități de nivel ridicat pentru redarea unui text pe ecran. Pentru a realiza acest lucru, dezvoltatorul trebuie să implementeze un sistem de redare a textului ce utilizează functionalitățile low-level ale OpenGL.

Cea mai des utilizată metoda este aceea ca fiecare caracter ce se dorește a fi redat să aibă asociată o textură. Astfel redarea propriu-zisă a unui caracter pe ecran presupune desenarea unui poligon de tip quad ce va avea mapat textura corespunzătoare caracterului ce se dorește a fi afișat.

O primă variantă ar fi ca toate caracterele să se regăsească în cadrul unei singure texturi mari denumită “bitmap font” și la redare să se selecteze din bitmap font pentru mapare coordonatele specifice porțiunii unde se află caracterul dorit.

 Figura 1. Bitmap font ce conține toate caracterele și simbolurile

 Figura 2. Redare text “Hello World” folosind quad-uri și caracterele extrase din bitmap font

Cea de-a doua variantă (și cea care este folosită și pentru implementare în continuarea laboratorului) este ca pentru fiecare caracter/simbol să se creeze câte o textură individuală ce va avea dimensiunea (lătime/înălțime) caracterului/simbolului. Această metodă permite o mai bună flexibilitate în manipularea fiecărui caracter/simbol în parte pentru poziționarea/scalarea acestuia.

Pentru a obtine imaginea (bitmapul) pentru fiecare caracter/simbol necesar dintr-un font dat, a fost folosită biblioteca FreeType.

Utilizare FreeType

FreeType este o bibliotecă open-source care permite încărcarea de fonturi și redarea acestora în bitmapuri inclusiv la nivelul fiecărui caracter/simbol individual.

FreeType poate fi descarcat de la (FreeType).

Detalii despre modul de lucru cu FreeType, crearea și gestiunea bitmapurilor create pot fi găsite la ( Tutoriale FreeType).

În cazul nostru, pentru primele 128 de caractere ASCII ale font-ului folosit, folosind FreeType vom crea câte o textură astfel:

  • Încărcare font
// Then initialize and load the FreeType library
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, font.c_str(), 0, &face))
	std::cout << "ERROR::FREETYPE: Failed to load font" << std::endl;
// Set size to load glyphs as
FT_Set_Pixel_Sizes(face, 0, fontSize);
  • Creare texturi pentru fiecare caracter/simbol
// for the first 128 ASCII characters, 
// pre-load/compile their characters and store them
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
	...
 
	// Store character information (texture id, size, etc) for later use
	...
}

Astfel pentru fiecare dintre primele 128 de caractere ASCII din font se crează câte o textură. Se folosește pentru textura creată doar primul canal de culoare (GL_RED) unde se va memora valoarea de culoare din bitmap-ul generat pentru fiecare caracter ca o imagine greyscale pe 8 biți.

Shadere

Pentru redarea fiecărui caracter se folosesc următoarele shadere:

  • Vertex shader
layout(location = 0) in vec4 vertex; // <x,y,u,v>
out vec2 TexCoords;
 
uniform mat4 projection;
 
void main()
{
	gl_Position = projection * vec4(vertex.xy, 0.0, 1.0);
	TexCoords = vertex.zw;
}

Pe coordonatele .xy se transmit coordonatele x,y din cadrul ferestrei de afișare care reprezintă poziția unde va fi afișat caracterul.

Coordonata z va fi zero deoarece afișarea se va face direct pe fața cubului corespunzător volumului vizual canonic în planul Z = 0.

Astfel acestea vor fi transformate in vertex shader doar cu matricea de proiecție.

Se va folosi o proiecție ortografică ce va avea near = far = 0 și pentru a considera colțul stânga sus ca fiind 0,0 va avea bottom inversat cu top astfel:

glm::value_ptr(glm::ortho(0.0f, static_cast<GLfloat>(width), static_cast<GLfloat>(height), 0.0f))

unde width și height sunt rezoluția ferestrei de afișare

Pe coordonatele .zw vor veni coordonatele de textură care vor fi trimise mai departe în TexCoords

  • Fragment shader
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;
}

Fragment shader-ul primește ca uniforme id-ul texturii unde a fost încărcat bitmap-ul monocolor al caracterului și culoarea cu care se dorește a fi afișat textul. Astfel, deoarece am folosit doar canalul roșu pentru textură (GL_RED) se face eșantionarea din textură doar de pe acesta (texture(text, TexCoords).r) și se stochează în culoarea finală pe canalul de alpha (componenta a 4-a). În acest fel, considerând quad-ul pe care se afisează ca fiind transparent (in bitmap acolo unde nu există pixeli pentru caracter, va fi intors 0 iar un alpha de 0 înseamnă perfect transparent) textul va fi afișat iar restul conținutului quad-ului pe care a fost mapată textură va fi ignorat permițând astfel combinarea naturală cu ce deja a fost afișat anterior în frame buffer. Ca ultim pas, se înmulțește culoarea obținută cu culoarea textColor pentru a desena textul cu o culoare dorită.

Pentru a putea fi folosit acest mecanism de transparență trebuie activat și folosit mecanismul de blending din OpenGL astfel:

glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

Redarea textului

Pentru redarea unei linii de text se va folosi functia

void RenderText(std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)

unde:

  • x,y: coordonatele (x,y) din fereastra de afișare de unde va fi afișată linia de text ( 0,0 corespunde colțului stânga sus al ecranului)
  • scale: factor de scalare pentru text (dimensiunea font-ului ales la încărcare va fi înmulțită cu acest factor)
  • color: culoarea textului

Întreaga funcție:

void TextRenderer::RenderText(std::string text, GLfloat x, GLfloat y, GLfloat scale, glm::vec3 color)
{
	// Activate corresponding render state	
	//this->TextShader.Use();
	if (this->TextShader)
	{
		glUseProgram(this->TextShader->program);
		CheckOpenGLError();
	}
 
	int loc_text_color = glGetUniformLocation(this->TextShader->program, "textColor");
	glUniform3f(loc_text_color, color.r, color.g, color.b);
	//this->TextShader.SetVector3f("textColor", color);
	glActiveTexture(GL_TEXTURE0);
	glBindVertexArray(this->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 + (this->Characters['H'].Bearing.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, 1.0 },
			{ xpos + w, ypos,       1.0, 0.0 },
			{ xpos,     ypos,       0.0, 0.0 },
 
			{ xpos,     ypos + h,   0.0, 1.0 },
			{ xpos + w, ypos + h,   1.0, 1.0 },
			{ xpos + w, ypos,       1.0, 0.0 }
		};
		// Render glyph texture over quad
		glBindTexture(GL_TEXTURE_2D, ch.TextureID);
		// Update content of VBO memory
		glBindBuffer(GL_ARRAY_BUFFER, this->VBO);
		glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData
 
		glBindBuffer(GL_ARRAY_BUFFER, 0);
		// Render quad
		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
		glDrawArrays(GL_TRIANGLES, 0, 6);
		glDisable(GL_BLEND);
		// Now advance cursors for next glyph
		x += (ch.Advance >> 6) * scale; // Bitshift by 6 to get value in pixels (1/64th times 2^6 = 64)
	}
	glBindVertexArray(0);
	glBindTexture(GL_TEXTURE_2D, 0);
}

Astfel se procesează fiecare caracter din care este alcătuit quad-ul după cum urmează:

  • Pentru fiecare caracter se avansează pe ecran pe linia orizontală (variabila x) la poziția de început de unde urmează a fi desenat (xpos, ypos)
  • Se crează quad-ul de dimensiunea lățimea și înălțimea texturii asociate caracterului
  • Quad-ul va fi desenat folosind două triunghiuri: 6 vertecși (câte 3 pentru fiecare triunghi) fiecare cu 4 valori:
    • x,y: coodonatele vârfului (după cum s-a explicat mai sus z va fi zero deoarece se desenează folosind o proiecție ortografică în planul Z = 0 al volumului vizual canonic)
    • z,w: coordonatele de textură asociate vârfului
  • Se desenează quad-ul format din cele două triunghiuri cu textura corespunzătoare caracterului (glBindTexture(GL_TEXTURE_2D, ch.TextureID)) și cu mecanismul de blending activat pentru a nu desena decât pixelii caracterului și a ignora restul quad-ului care este transparent

Utilizare

  1. Descărcați framework-ul de laborator ce are implementat redarea textului
  2. Rulați exemplul de redare a textului: clasa Laborator_BonusTextRenderer
  3. Dacă doriți să vedeți în detaliu implementarea redării textului examinați:
    • Clasa TextRenderer din /TextRenderer
    • Shaderele VertexShaderText.glsl și FragmentShaderText.glsl din /TextRenderer/Shaders
egc/laboratoare/bonusrendertext.txt · Last modified: 2019/12/03 07:56 by alexandru.gradinaru
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0