Reputation: 73
I'm currently trying to improve my text rendering. The basic way where you render every character separate works, but now I want to do everything in one draw call by rendering a texture atlas. The texture atlas is almost done. It does render the text as quad, but I cannot figure out how to solve the alpha problem. The values R, G and B are all zero.
I have also tried GL_ALPHA
, GL_RGBA
and GL_LUMINANCE
.
Note: I'm working on a Raspberry Pi and using OpenGL ES 2.0.
Image of all font characters:
Shaders:
precision mediump float;
attribute vec4 vertex;
varying vec2 textCoord;
void main()
{
gl_Position = vec4(vertex.xy, 0.0, 1.0);
textCoord = vertex.zw;
}
precision lowp float;
varying vec2 textCoord;
uniform sampler2D text;
uniform vec3 textColor;
void main()
{
lowp vec4 sampled = vec4(1.0, 1.0, texture2D(text, textCoord).ba);
gl_FragColor = vec4(textColor, 1.0) * sampled;
}
Setup Texture Atlas:
glGenBuffers(1, &vbo);
if (FT_Init_FreeType(&m_FT)) {
std::cout << "ERROR: Could not init the FreeType Library" << std::endl;
}
if (FT_New_Face(m_FT, "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 0, &m_Face)) {
std::cout << "ERROR: This font failed to load." << std::endl;
}
FT_Set_Pixel_Sizes(m_Face, 0, height);
unsigned int roww = 0;
unsigned int rowh = 0;
memset(c, 0, sizeof c); // Set all values to 0
// Find minimum size for a texture holding all visible ASCII characters
for (int i = 0; i < 128; i++) {
if (FT_Load_Char(m_Face, i, FT_LOAD_RENDER)) {
fprintf(stderr, "Loading character %c failed!\n", i);
continue;
}
if (roww + m_Face->glyph->bitmap.width + 1 >= SCREEN_WIDTH) {
w = std::max(w, roww);
h += rowh;
roww = 0;
rowh = 0;
}
roww += m_Face->glyph->bitmap.width + 1;
rowh = std::max(rowh, m_Face->glyph->bitmap.rows);
}
w = std::max(w, roww);
h += rowh;
// Create a texture that will be used to hold all ASCII glyphs
glActiveTexture(GL_TEXTURE0);
glGenTextures(1, &tex);
glBindTexture(GL_TEXTURE_2D, tex);
GetShader()->Use();
GetShader()->SetInt("text", tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
// We require 1 byte alignment when uploading texture data
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
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);
// Paste all glyph bitmaps into the texture, remembering the offset
int ox = 0;
int oy = 0;
rowh = 0;
for (int i = 0; i < 128; i++) {
if (FT_Load_Char(m_Face, i, FT_LOAD_RENDER)) {
fprintf(stderr, "Loading character %c failed!\n", i);
continue;
}
if (ox + m_Face->glyph->bitmap.width + 1 >= SCREEN_WIDTH) {
oy += rowh;
rowh = 0;
ox = 0;
}
glTexSubImage2D(GL_TEXTURE_2D, 0, ox, oy, m_Face->glyph->bitmap.width, m_Face->glyph->bitmap.rows, GL_RGBA, GL_UNSIGNED_BYTE, m_Face->glyph->bitmap.buffer);
c[i].ax = m_Face->glyph->advance.x >> 6;
c[i].ay = m_Face->glyph->advance.y >> 6;
c[i].bw = m_Face->glyph->bitmap.width;
c[i].bh = m_Face->glyph->bitmap.rows;
c[i].bl = m_Face->glyph->bitmap_left;
c[i].bt = m_Face->glyph->bitmap_top;
c[i].tx = ox / (float)w;
c[i].ty = oy / (float)h;
rowh = std::max(rowh, m_Face->glyph->bitmap.rows);
ox += m_Face->glyph->bitmap.width + 1;
}
fprintf(stderr, "Generated a %d x %d (%d kb) texture atlas\n", w, h, w * h / 1024);
GetShader()->Stop();
glBindTexture(GL_TEXTURE_2D, 0);
Draw function:
// Set uniforms
a_Shader->SetVec3("textColor", m_v3Color);
glActiveTexture(GL_TEXTURE0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, 0);
point coords[6 * 128];
int dc = 0;
const uint8_t *p;
float sx = 2.0 / SCREEN_WIDTH;
float sy = 2.0 / SCREEN_HEIGHT;
// float x = (m_v2Position.x - (SCREEN_WIDTH / 2)) * sx;
//float y = (m_v2Position.y - (SCREEN_HEIGHT / 2)) * sy;
float x = -1.f;
float y = 0.f;
// Loop through all characters
for (int p = 0; p < 128; p++) {
// Calculate the vertex and texture coordinates
float x2 = x + c[p].bl * sx;
float y2 = -y - c[p].bt * sy;
float w = c[p].bw * sx;
float h = c[p].bh * sy;
// Advance the cursor to the start of the next character
x += c[p].ax * sx;
y += c[p].ay * sy;
// Skip glyphs that have no pixels
if (!w || !h)
continue;
coords[dc++] = (point) {
x2, -y2, c[p].tx, c[p].ty
};
coords[dc++] = (point) {
x2 + w, -y2, c[p].tx + c[p].bw / w, c[p].ty
};
coords[dc++] = (point) {
x2, -y2 - h, c[p].tx, c[p].ty + c[p].bh / h
};
coords[dc++] = (point) {
x2 + w, -y2, c[p].tx + c[p].bw / w, c[p].ty
};
coords[dc++] = (point) {
x2, -y2 - h, c[p].tx, c[p].ty + c[p].bh / h
};
coords[dc++] = (point) {
x2 + w, -y2 - h, c[p].tx + c[p].bw / w, c[p].ty + c[p].bh / h
};
}
// Render glyph texture over quad
glBindTexture(GL_TEXTURE_2D, tex);
// Update content of VBO memory
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(coords), coords, GL_DYNAMIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
// Render quad
glDrawArrays(GL_TRIANGLES, 0, dc);
glDisableVertexAttribArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
glCheckError();
Upvotes: 0
Views: 2570
Reputation: 210878
The buffer which is provided by m_Face->glyph->bitmap.buffer
is a buffer with one single color channel. Since OpenGL ES is used, the source format of the texture has to be GL_LUMINANCE
.
Specify a two-dimensional texture image with a single (red) color channel. The alignment for a row of the texture has to be set 1 (see glPixelStorei
). Note default is 4, which doesn't match a tightly packed texture with a size of 1 byte per pixel:
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, w, h, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, 0);
Note, since the texture is non power of 2, mip mapmapping can't be used, so the texture minifying function has to be either GL_NEAREST
or GL_LINEAR
and the wrap mode has to be GL_CLAMP_TO_EDGE
:
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);
Load the glyphs to the texture:
glTexSubImage2D(
GL_TEXTURE_2D, 0, ox, oy,
m_Face->glyph->bitmap.width, m_Face->glyph->bitmap.rows,
GL_LUMINANCE, GL_UNSIGNED_BYTE, m_Face->glyph->bitmap.buffer);
Since the internal texture format is GL_LUMINANCE
, the samples have to be read form the red, green or blue color channel:
lowp float sampled = texture2D(text, textCoord).r;
gl_FragColor = vec4(textColor, 1.0) * sampled;
Upvotes: 1