Dibujar Texto en OpenGL

Dibujar texto en una aplicación gráfica es una tarea común, sin embargo OpenGL no cuenta con funciones para esta tarea, cuando deseemos mostrar texto tenemos varias opciones, las más comúnmente utilizadas como: usar una imagen generada a partir de una fuente determinada, o cargar una fuente TrueType, generar la imagen y mostrarla como una textura.

Nos apoyaremos en la librería STB para la generación de la textura a partir de una fuente TrueType, esta librería nos servirá también para abrir y guardar imágenes en diversos formatos como: JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC, además nos servirá para realizar otras tareas relacionadas con los gráficos 3D.

Para utilizar la librería debemos definir los siguientes identificadores antes de incluir los correspondientes archivos de encabezado (*.h).

#define STB_TRUETYPE_IMPLEMENTATION
#define STB_RECT_PACK_IMPLEMENTATION
#define STB_IMAGE_WRITE_IMPLEMENTATION

#include "stb\stb_rect_pack.h"
#include "stb\stb_truetype.h"
#include "stb\stb_image_write.h"

Lo siguiente que haremos será leer el archivo de fuente .ttf, lo almacenamos en un buffer, ahora podemos usar STB para generar la imagen a partir del archivo cargado, podemos indicar que caracteres deseamos generar, usaremos las funciones stbtt_PackBegin para indicar el buffer donde guardaremos la imagen, stbtt_PackSetOversampling para mejorar la calidad visual mediante oversampling, stbtt_PackFontRange genera los caracteres indicados y almacena la meta información de cada uno en la variable chardata, stbtt_PackEnd finaliza el proceso.

stbtt_pack_context pc;

stbtt_PackBegin(&pc, temp_bitmap, BITMAP_W, BITMAP_H, 0, 1, NULL);
stbtt_PackSetOversampling(&pc, 2, 2);
stbtt_PackFontRange(&pc, ttf_buffer, 0, font_size, 32, 95, chardata + 32);
stbtt_PackEnd(&pc);

stbi_write_png("font.png", BITMAP_W, BITMAP_H, 1, temp_bitmap, 0);

La última línea de código nos es necesaria, la usamos para guardar la imagen creada por STB en un archivo en formato PNG, de este modo tenemos una mejor idea de lo que hemos hecho.

font
Esta imagen contiene los 95 caracteres ASCII más usados comúnmente, empezando por el  número 32 hasta el 127, el siguiente paso es crear una textura 2D a partir de esta imagen, para más información puedes ver: Texturas en OpenGL Moderno.

glGenTextures(1, &font_tex);
glBindTexture(GL_TEXTURE_2D, font_tex);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, BITMAP_W, BITMAP_H, 0, GL_ALPHA, GL_UNSIGNED_BYTE, temp_bitmap);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

Dibujar Texto 2D


Cuando deseamos mostrar un texto, primero debemos localizar la coordenada UV donde se encuentra cada carácter que compone el texto que deseamos mostrar, creamos un cuadrado y sobre el mostramos la porción de la textura indicada.
En este punto recurrimos a la función stbtt_GetPackedQuad que nos permite obtener las coordenadas UV para un determinado carácter y las coordenadas requeridas para construir el cuadro que contendrá la textura, indicamos también una posición inicial (X, Y) donde deseemos ubicar el texto a mostrar.

std::string::const_iterator c;
for (c = text.begin(); c != text.end(); c++)
{
    stbtt_aligned_quad q;
    stbtt_GetPackedQuad(chardata, BITMAP_W, BITMAP_H, *c, &x, &y, &q, 1);

    const GLfloat vertex[] = {
        q.s0, q.t0, q.x0, q.y0,
        q.s1, q.t0, q.x1, q.y0,
        q.s1, q.t1, q.x1, q.y1,

        q.s0, q.t0, q.x0, q.y0,
        q.s1, q.t1, q.x1, q.y1,
        q.s0, q.t1, q.x0, q.y1
    };

    glBindBuffer(GL_ARRAY_BUFFER, buffer);
    glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertex), vertex);
    glVertexAttribPointer(0, 4, GL_FLOAT, GL_FALSE, 0, NULL);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glEnableVertexAttribArray(0);

    glDrawArrays(GL_TRIANGLES, 0, 6);
}

Nuestro vertex shader recibe un buffer que contiene las posiciones (x, y) y las coordenadas (u, v), ambas están contenidas en un vec4.

in vec4 vertex;
uniform mat4 projection;
out vec2 uv;

void main()
{
     gl_Position = projection * vec4(vertex.zw, 0.0, 1.0);
     uv = vertex.xy; 
}

La matriz de proyección la generamos usando una vista ortográfica, esto porque deseamos mostrar el texto en 2D, la misma está generada de tal modo que la coordenada (0, 0) se ubica en la parte superior izquierda de la pantalla, lo que facilita el posicionamiento del texto, glm::mat4 ortho_proj = glm::ortho(0.0f, width, height, 0.0f);.

En el fragment shader aplicamos la textura y el color del texto.

in vec2 uv;
out vec4 color;

uniform sampler2D font;
uniform vec3 font_color;

void main()
{
    vec4 sampled = vec4(1.0, 1.0, 1.0, texture(font, uv).a);
    color = vec4(font_color, 1.0) * sampled;
}

Para poder apreciar correctamente el texto necesitamos activar las transparencias, glEnable(GL_BLEND); para activarla y glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); para cambiar a la correspondiente función de blending.

Para facilitar su uso todo está encapsulado en una clase llamada OpenGLText en su constructor debemos indicar el archivo .ttf a cargar y el tamaño de texto, debemos indicar también el tamaño de la ventana, necesitamos el ancho y el alto para crear la proyección ortográfica, para mostrar el texto solo usamos el método draw(x, y, “texto”) indicando la posición y el texto a mostrar, cambiamos el color usando setColor(r, g, b).

void onstart() override {
        // Inicializar Fuente TrueType
        text.loadFont("C:/test/segoeui.ttf", 48);
        text.setSize(1280, 768);    
}

Carga la fuente segoeui.ttf y establece el tamaño de la ventana.

void draw_text(double time) {
        std::wostringstream str;
        str.precision(2);
        str.setf(std::ios::fixed);
        str << "Time: " << time << " ms.";

        text.setColor(0.15f, 0.3f, 0.7f);
        text.draw(10, 60, L"Tutor de Programación - Render Text 2D");

        text.setColor(0.3f, 0.3f, 0.3f);
        text.draw(10, 120, str.str());
}

Muestra el texto sobre la escena 3D creada en el tutorial #3, indicamos el color del texto, utilizamos wstring para poder mostrar un rango mas amplio de caracteres.

Render Text OpenGL Moderno
GitHub: Dibujar Texto OpenGL Moderno

Comentarios

Temas relacionados

Entradas populares de este blog

tkinter Grid

tkinter Canvas

Controles y Contenedores JavaFX 8 - I

Conectar SQL Server con Java