c++ - letras - ¿Cómo dibujar texto usando solo métodos OpenGL?
opengl text (6)
Teoría
Por qué es difícil
Los formatos de fuentes populares como TrueType y OpenType son formatos de contorno vectorial: usan curvas de Bezier para definir el límite de la letra.
La transformación de esos formatos en matrices de píxeles (rasterización) es demasiado específica y está fuera del alcance de OpenGL, especialmente porque OpenGl no tiene primitivas no rectas (por ejemplo, vea ¿Por qué no hay círculo o primitiva de elipse en OpenGL? )
El enfoque más sencillo es utilizar las primeras fuentes de ráster en la CPU y luego asignar la matriz de píxeles a OpenGL como textura.
OpenGL entonces sabe cómo manejar arreglos de píxeles a través de texturas muy bien.
Atlas de textura
Podríamos trazar caracteres en cada cuadro y volver a crear las texturas, pero eso no es muy eficiente, especialmente si los caracteres tienen un tamaño fijo.
El enfoque más eficiente es rastrear todos los caracteres que planea usar y meterlos en una sola textura.
Y luego transfiéralo a la GPU una vez, y utilícelo texturas con coordenadas uv personalizadas para elegir el personaje correcto.
Este enfoque se llama https://en.wikipedia.org/wiki/Texture_atlas y se puede utilizar no solo para texturas sino también para otras texturas usadas repetidamente, como mosaicos en un juego 2D o íconos de interfaz de usuario web.
La imagen de Wikipedia de la textura completa, que en sí misma está tomada de freetype-gl, ilustra esto bien:
Sospecho que la optimización de la colocación de caracteres para el problema de textura más pequeño es un problema NP-hard, consulte: ¿Qué algoritmo se puede utilizar para empaquetar rectángulos de diferentes tamaños en el rectángulo más pequeño posible de una manera bastante óptima?
La misma técnica se usa en el desarrollo web para transmitir varias imágenes pequeñas (como iconos) a la vez, pero allí se llama "CSS Sprites": https://css-tricks.com/css-sprites/ y se utilizan para ocultar el latencia de la red en lugar de la comunicación CPU / GPU.
Métodos ráster sin CPU
También existen métodos que no usan el ráster de la CPU para texturas.
El raspado de la CPU es simple porque usa la GPU lo menos posible, pero también comenzamos a pensar si sería posible usar aún más la eficiencia de la GPU.
Este video de FOSDEM 2014 https://youtu.be/LZis03DXWjE?t=886 explica otras técnicas existentes:
- tesselación: convierta la fuente a triángulos diminutos. La GPU es realmente buena para dibujar triángulos. Desventajas:
- genera un grupo de triángulos
- Cálculo de CPU O (n log n) de los triángulos
- calcular curvas en sombreadores. Un documento de 2005 de Blinn-Loop puso este método en el mapa. Desventaja: complejo. Ver: Dibujo de bezier cúbico independiente de resolución en GPU (Blinn / Loop)
- implementaciones directas de hardware como OpenVG https://en.wikipedia.org/wiki/OpenVG . Desventaja: no muy implementada por alguna razón. Ver:
Fuentes dentro de la geometría 3D con perspectiva
Renderizar fuentes dentro de la geometría 3D con perspectiva (en comparación con un HUD ortogonal) es mucho más complicado, porque la perspectiva podría hacer que una parte del personaje esté mucho más cerca de la pantalla y más grande que la otra, haciendo una discretización uniforme de la CPU (por ejemplo, raster tesselación) se ven mal en la parte más cercana. Este es en realidad un tema de investigación activo:
- ¿Cuál es el estado del arte para la representación de texto en OpenGL a partir de la versión 4.1?
- http://www.valvesoftware.com/publications/2007/SIGGRAPH2007_AlphaTestedMagnification.pdf
Los campos de distancia son una de las técnicas populares ahora.
Implementaciones
Los ejemplos que siguen fueron todos probados en Ubuntu 15.10.
Debido a que este es un problema complejo como se discutió anteriormente, la mayoría de los ejemplos son grandes, y explotarían el límite de 30k de esta respuesta, así que solo clone los repositorios Git respectivos para compilar.
Sin embargo, todos son de código abierto, por lo que solo puede RTFS.
Soluciones FreeType
FreeType parece a la biblioteca de rasterización de fuentes de código abierto dominante, por lo que nos permitiría usar fuentes TrueType y OpenType, por lo que es la solución más elegante.
https://github.com/rougier/freetype-gl
Fue un conjunto de ejemplos OpenGL y freetype, pero está evolucionando más o menos en una biblioteca que lo hace y expone una API decente.
En cualquier caso, ya debería ser posible integrarlo en su proyecto copiando y pegando algún código fuente.
Proporciona tanto atlas de textura como técnicas de campo de distancia listas para usar.
Demos en: https://github.com/rougier/freetype-gl/tree/master/demos
No tiene un paquete Debian, y es un dolor compilar en Ubuntu 15.10: https://github.com/rougier/freetype-gl/issues/82#issuecomment-216025527 (problemas de empaquetado, algunos upstream), pero mejoró a partir de 16.10.
No tiene un buen método de instalación: https://github.com/rougier/freetype-gl/issues/115
Genera hermosos resultados como esta demostración:
libdgx https://github.com/libgdx/libgdx/tree/1.9.2/extensions/gdx-freetype
Ejemplos / tutoriales:
- un tutorial de NEHE: http://nehe.gamedev.net/tutorial/freetype_fonts_in_opengl/24001/
- http://learnopengl.com/#!In-Practice/Text-Rendering menciona, pero no pude encontrar el código fuente ejecutable
- ASÍ preguntas:
Otros rasterizadores de fuentes
Esos parecen menos buenos que FreeType, pero pueden ser más livianos:
- https://github.com/nothings/stb/blob/master/stb_truetype.h
- http://www.angelcode.com/products/bmfont/
Ejemplos de tutoriales de OpenGL 4 de Anton 26 "Fuentes de mapa de bits"
- tutorial: http://antongerdelan.net/opengl/ )
- fuente: https://github.com/capnramses/antons_opengl_tutorials_book/blob/9a117a649ae4d21d68d2b75af5232021f5957aac/26_bitmap_fonts/main.cpp
La fuente fue creada por el autor de forma manual y almacenada en un solo archivo .png
. Las letras se almacenan en forma de matriz dentro de la imagen.
Este método, por supuesto, no es muy general, y tendría dificultades con la internacionalización.
Construir con:
make -f Makefile.linux64
Vista previa de salida:
opengl-tutorial capítulo 11 "Fuentes 2D"
- tutorial: http://www.opengl-tutorial.org/intermediate-tutorials/tutorial-11-2d-text/
- fuente: https://github.com/opengl-tutorials/ogl/blob/71cad106cefef671907ba7791b28b19fa2cc034d/tutorial11_2d_fonts/tutorial11.cpp
Las texturas se generan a partir de archivos DDS .
El tutorial explica cómo se crearon los archivos DDS, utilizando CBFG y Paint.Net .
Vista previa de salida:
Por alguna razón, Suzanne me falta, pero el contador de tiempo funciona bien: https://github.com/opengl-tutorials/ogl/issues/15
FreeGLUT
GLUT tiene glutStrokeCharacter
y FreeGLUT es de código abierto ... https://github.com/dcnieho/FreeGLUT/blob/FG_3_0_0/src/fg_font.c#L255
OpenGLText
https://github.com/tlorach/OpenGLText
Ráster TrueType. Por empleado de NVIDIA. Tiene como objetivo la reutilización. No lo he intentado todavía
Muestra de ARM Mali GLES SDK
http://malideveloper.arm.com/resources/sample-code/simple-text-rendering/ parece codificar todos los caracteres en un PNG, y cortarlos desde allí.
SDL_ttf
Fuente: https://github.com/cirosantilli/cpp-cheat/blob/d36527fe4977bb9ef4b885b1ec92bd0cd3444a98/sdl/ttf.c
Vive en un árbol separado para SDL, y se integra fácilmente.
Sin embargo, no proporciona una implementación de atlas de texturas, por lo que el rendimiento será limitado: ¿cómo renderizar fuentes y texto con SDL2 de manera eficiente?
Temas relacionados
No tengo la opción de usar, pero los métodos OpenGL (es glxxx()
métodos glxxx()
). Necesito dibujar texto usando solo métodos gl. Después de leer el libro rojo, entiendo que solo es posible a través del método glBitmap()
. Si esta es la única forma posible, entonces ¿alguien me puede ayudar con la información de la matriz de píxeles para todos los personajes? ¿Hay alguna otra forma de dibujar texto?
Cargue una imagen con caracteres como textura y dibuje la parte de esa textura según el carácter que desee. Puede crear esa textura usando un programa de pintura, codificarlo o usar un componente de ventana para dibujar una imagen y recuperar esa imagen para obtener una copia exacta de las fuentes del sistema.
No es necesario utilizar Glut o cualquier otra extensión, solo la operatividad básica de OpenGL. Hace el trabajo, sin mencionar que ha sido hecho así durante décadas por programadores profesionales en juegos exitosos y otras aplicaciones.
Creo que la mejor solución para dibujar texto en OpenGL son las fuentes de textura. Trabajo con ellas durante mucho tiempo. Son flexibles, rápidos y bonitos (con algunas excepciones posteriores). Utilizo un programa especial para convertir archivos de fuentes (.ttf, por ejemplo) en texturas, que se guardan en archivos de algún formato interno de "fuente" (he desarrollado formatos y programas basados en http://content.gpwiki.org/index.php/OpenGL:Tutorials:Font_System aunque mi versión fue bastante lejos de la original de Unicode, y así sucesivamente). Al iniciar la aplicación principal, las fuentes se cargan desde este formato "interno". Mire el enlace de arriba para más información.
Con ese enfoque, la aplicación principal no usa ninguna biblioteca especial como FreeType, lo que tampoco me conviene. El texto se dibuja utilizando las funciones estándar de OpenGL.
Dibujar texto en OpenGL no es una tarea directa. Probablemente debería echar un vistazo a las bibliotecas para hacer esto (ya sea mediante el uso de una biblioteca o como una implementación de ejemplo).
Algunos buenos puntos de partida podrían ser GLFont , OpenGL Font Survey y NeHe Tutorial para Bitmap Fonts (Windows) .
Tenga en cuenta que los mapas de bits no son la única forma de lograr texto en OpenGL como se menciona en la encuesta de fuente.
Este artículo describe cómo renderizar texto en OpenGL usando varias técnicas.
Con solo usar OpenGL, hay varias formas:
- usando glBitmap
- usando texturas
- usando listas de visualización
Usa glutStrokeCharacter(GLUT_STROKE_ROMAN, myCharString)
.
Un ejemplo: un STAR WARS SCROLLER.
#include <windows.h>
#include <string.h>
#include <GL/glut.h>
#include <iostream.h>
#include <fstream.h>
GLfloat UpwardsScrollVelocity = -10.0;
float view=20.0;
char quote[6][80];
int numberOfQuotes=0,i;
//*********************************************
//* glutIdleFunc(timeTick); *
//*********************************************
void timeTick(void)
{
if (UpwardsScrollVelocity< -600)
view-=0.000011;
if(view < 0) {view=20; UpwardsScrollVelocity = -10.0;}
// exit(0);
UpwardsScrollVelocity -= 0.015;
glutPostRedisplay();
}
//*********************************************
//* printToConsoleWindow() *
//*********************************************
void printToConsoleWindow()
{
int l,lenghOfQuote, i;
for( l=0;l<numberOfQuotes;l++)
{
lenghOfQuote = (int)strlen(quote[l]);
for (i = 0; i < lenghOfQuote; i++)
{
//cout<<quote[l][i];
}
//out<<endl;
}
}
//*********************************************
//* RenderToDisplay() *
//*********************************************
void RenderToDisplay()
{
int l,lenghOfQuote, i;
glTranslatef(0.0, -100, UpwardsScrollVelocity);
glRotatef(-20, 1.0, 0.0, 0.0);
glScalef(0.1, 0.1, 0.1);
for( l=0;l<numberOfQuotes;l++)
{
lenghOfQuote = (int)strlen(quote[l]);
glPushMatrix();
glTranslatef(-(lenghOfQuote*37), -(l*200), 0.0);
for (i = 0; i < lenghOfQuote; i++)
{
glColor3f((UpwardsScrollVelocity/10)+300+(l*10),(UpwardsScrollVelocity/10)+300+(l*10),0.0);
glutStrokeCharacter(GLUT_STROKE_ROMAN, quote[l][i]);
}
glPopMatrix();
}
}
//*********************************************
//* glutDisplayFunc(myDisplayFunction); *
//*********************************************
void myDisplayFunction(void)
{
glClear(GL_COLOR_BUFFER_BIT);
glLoadIdentity();
gluLookAt(0.0, 30.0, 100.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0);
RenderToDisplay();
glutSwapBuffers();
}
//*********************************************
//* glutReshapeFunc(reshape); *
//*********************************************
void reshape(int w, int h)
{
glViewport(0, 0, w, h);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
gluPerspective(60, 1.0, 1.0, 3200);
glMatrixMode(GL_MODELVIEW);
}
//*********************************************
//* int main() *
//*********************************************
int main()
{
strcpy(quote[0],"Luke, I am your father!.");
strcpy(quote[1],"Obi-Wan has taught you well. ");
strcpy(quote[2],"The force is strong with this one. ");
strcpy(quote[3],"Alert all commands. Calculate every possible destination along their last known trajectory. ");
strcpy(quote[4],"The force is with you, young Skywalker, but you are not a Jedi yet.");
numberOfQuotes=5;
glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
glutInitWindowSize(800, 400);
glutCreateWindow("StarWars scroller");
glClearColor(0.0, 0.0, 0.0, 1.0);
glLineWidth(3);
glutDisplayFunc(myDisplayFunction);
glutReshapeFunc(reshape);
glutIdleFunc(timeTick);
glutMainLoop();
return 0;
}