"Ningún juego dura tanto hasta que te lo terminas como el que te programas tú mismo,
ninguno te absorbe tanto y ninguno te produce tanta satisfacción cuando lo has acabado"
Programacion de videojuegos
Inicio Curso de programación de juegos 9. Listas, Texto y FPS - Texto
Jueves 14 de Noviembre del 2019

Menu principal
Colaborar (con PayPal)

Para continuar con el trabajo de esta Web y poder pagar el hosting, viene bien la ayuda que sea. Gracias a todos.

Importe: 

Ultimas descargas
19.Jan

Clase que permite dibujar texto en OpenGL con mucha facilidad.Usa FreeType2.Para ver que hace y c...


9. Listas, Texto y FPS - Texto Imprimir Correo electrónico
Videojuegos - Curso de Programación de juegos
Escrito por Vicengetorix   
Índice de Artículos
9. Listas, Texto y FPS
Texto
Velocidad y FPS
Todas las páginas

Con lo que hemos aprendido, ya podemos empezar con el texto en pantalla que, como habreis adivinado, vamos a implementar con "display lists".
En este apartado veremos una programacion muy de Windows, aunque el texto se puede implementar de otras formas. La nuestra es la mas rapida y comoda por ahora.

Un pequeño anticipo de lo que vamos a hacer para poder poner texto en pantalla.
Vamos a definir una nueva funcion para crear las "display lists" para las letras, una por simbolo del codigo ASCII.

Como ya deberiais saber, el codigo ASCII es simplemente la asociacion de un numero a cada letra, y tambien a cada simbolo que se usa en escritura. Hasta el codigo 32 los numeros corresponden a codigos no imprimibles que nosotros no vamos a usar. El codigo 32 es el primero que vamos a crear, es el espacio en blanco. Si fueramos ingleses nos bastaria con generar del 32 al 128, el codigo ASCII original con mayusculas, minusculas y poco mas como parentesis, corchetes, mayorque, ... . Como somos castellano hablantes (de otro idioma tambien habria que hacerlo) tendremos que generar del 32 al 255, el codigo ASCII completo, en nuestro caso incluyendo ñ, Ñ, ü, Ü,  á, Á, é, É, ... .


Dicho esto, lo que haremos sera generar un "display  list" por cada caracter de codigo ASCII del 32 al 255, de forma secuencial y en el mismo orden. Asi, al pintar  las listas con el indice correspondiente al codigo ASCII menos 32, de cada letra, de izquierda a derecha en pantalla, aparecera la palabra que queramos. Los codigos de cada letra en una frase que vamos a escribir en pantalla, los almacenaremos en una matriz (no una de OpenGL, si no un "array" de C) de tipo char, lo mismo que usamos en C la funcion "printf".

Empezamos con el codigo.

Definimos de forma global una variable de tipo GLuint para almacenar el identificativo base, el primero de nuestra lista de 223 (255 menos 32), a partir del que se crearan todos de manera consecutiva.

1
2
3
4
5
6
// Variable de tipo unsigned int para guardar 
// el identificativo base de las "display list"
// que generara para almacenar las letras.
// A partir de este, los siguientes, consecutivamente,
// seran usados en estricto orden.
GLuint ListaFuente=0;

Creamos la funcion GeneraFuente() donde crearemos las "display list" a partir de una fuente del sistema.
Creamos dos variables de tipo HFONT, propio de windows para almacenar la fuente que vamos a crear y la fuente antigua y restaurarla despues.

1
2
3
4
5
6
// Variable para guardar el identificativo
// de una fuente de Windows
HFONT fuente=NULL;
// Otro para guardar el que esta activo ahora
// y al terminar volver a dejar el que estaba.
HFONT fuente2=NULL;

Creamos la fuente windows como nos apetezca. En este ejemplo uso los parametro que me da la gana pero se pueden cambiar al gusto.
Uso la funcion CreateFont. Los comentarios del codigo son bastante explicitos.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Creo la fuente que voy a convertir en listas de OpenGL.
fuente = CreateFont(
25, // Altura del caracter
0, // Ancho del caracter. 0 es proporcionado a la altura.
0, // Angulo de escape
0, // Angulo respecto a X
FW_BOLD, // Grosor del caracter. 0 es por defecto.
// Valor de 0 a 1000.
FALSE, // Italica
FALSE, // Subrallada
FALSE, // Tachada
ANSI_CHARSET, // Conjunto de caracteres
OUT_TT_PRECIS, // Tipo de fuente preferida
CLIP_DEFAULT_PRECIS, // Precision de recorte
ANTIALIASED_QUALITY, // Calidad de salida
FF_DONTCARE|DEFAULT_PITCH, // Familia y ¿pitch?
L"Arial"); // Fuente del sistema a usar

Despues asigno esta nueva fuente al "device context" o dispositivo de contexto de nuestra ventana OpenGL.

1
2
3
// Selecciono la fuente que acabo de crear como la actual
// en el contexto de dispositivo de nuestra ventana.
fuente2 = (HFONT)SelectObject(DevContex, fuente);

Ahora genero los identificativos de lista de OpenGL para los 223 caracteres y guardo el primero en la variable que habiamos definido para ello.

1
2
3
4
// Creo 223 "display lists" para guardar las letras.
// Todos los caracteres ASCII desde el espacio en
// blanco (codigo 32).
ListaFuente = glGenLists(255-32);

LLegamos al momento critico, usamos la funcion wglUseFontBitmaps, propia de la implementacion de OpenGL para windows y creamos todas las listas de un tiron. Esta funcion esta hecha para eso.

1
2
3
4
// Usamos esta funcion de OpenGL especifica de Windows
// para crear 223 listas a partir del identificativo de ListaFuente
// y a partir del 32 con la fuente del contexto de dispositivo.
wglUseFontBitmaps(DevContex, 32, 255-32, ListaFuente);

Una vez creadas las listas, solo queda restablecer la fuente antigua y borrar la que hemos creamos y ya no necesitamos porque ya estan las listas creadas.

1
2
3
4
// Una vez creadas las listas restablezco la fuente anterior.
SelectObject(DevContex, fuente2);
// Borro la fuente que cree.
DeleteObject(fuente);

Ya estaria la funcion GeneraFuente() pero voy a poner otra version mas simple, en la que no se puede modificar la fuente, solo se usa la que te da el sistema por defecto.

1
2
3
4
5
6
7
8
9
10
11
12
// Selecciono la fuente que me da el sistema.
SelectObject (DevContex, GetStockObject (SYSTEM_FONT));
 
// Creo 223 "display lists" para guardar las letras.
// Todos los caracteres ASCII desde el espacio en
// blanco (codigo 32).
ListaFuente = glGenLists(255-32);
 
// Usamos esta funcion de OpenGL especifica de Windows
// para crear 223 listas a partir del identificativo de ListaFuente
// y a partir del 32 con la fuente del contexto de dispositivo.
wglUseFontBitmaps(DevContex, 32, 255-32, ListaFuente);

Como veis, mas corto y menos versatil.Cada uno elige.

Pequeña aclaracion de que hay en las "display lists" que se crean.
Para hacer los caracteres de texto internamente, OpenGL crea un bitmap por cada letra y para pintar cada uno, usa la funcion glBitmap(...), que pone el bitmap en pantalla independientemente de la proyeccion. Es una funcion 2D. El bitmap lo pone en la posicion de pantalla que toca, a partir del ultimo pixel que pinto. Esta posicion se puede modificar con la funcion glRasterPos, asi se puede poner el bitmap donde haga falta.

Creamos ahora la funcion Escribe(...) que sera de uso parecido a "printf" pero con la opcion de posicionar lo que escribimos en pantalla. La funcion sera con 3 parametros fijos y otros de numero variable.

La definicion sera asi:

1
2
3
4
// Los dos primeros parametros son la posicion
// y el resto es igual que la funcion prinf.
void Escribe(int x, int y, char *cadena,...)
{

Definimos un "array" de letras.

1
2
// Array para guardar texto.
char texto[256];

Defino una variable especifica para tratar los argumentos variables.

1
2
// Tipo de datos para argumentos variables.
va_list ap;

Compruebo que le paso una cadena a escribir, si no lo dejo.

1
2
// Si no mando nada a escribir me voy.
if (cadena == NULL) return

De estas tres lineas, la primera y la ultima son macros especificas para el tratamiento de argumentos variables.
Al final lo que hace este bloque de codigo el tratar la cadena que pasamos a la funcion y los argumentos variables posteriores exactamente igual que la funcion "printf" y dejar el resultado en el array "texto" que habiamos creado al principio de la funcion.

1
2
3
4
5
6
7
// Con las siguientes 3 lineas proceso la
// entrada tipo "printf" de mi funcion,
// los argumentos variables y su encaje en
// el texto general en base a las "%" que encuentre.
va_start(ap, cadena);
vsprintf(texto, cadena, ap);
va_end(ap);

Despues compruebo si la iluminacion de OpenGL esta habilitada y me quedo en una variable de tipo bool con el estado. Hago lo mismo con las Texturas 2D y la profundidad.
Luego deshabilito la iluminacion, las texturas y el test de profundidad para que se vean las letras correctamente (en el caso de la profundidad, si no la deshabilito puede no verse el texto segun el hardware de video).

1
2
3
4
5
6
7
8
9
10
11
// Compruebo si esta habilitada la iluminacion,
// texturas y profundidad para dejarlas luego igual.
bool luz = glIsEnabled(GL_LIGHTING);
bool tex = glIsEnabled(GL_TEXTURE_2D);
bool pro = glIsEnabled(GL_DEPTH_TEST);
// Ahora deshabilito la iluminacion.
glDisable(GL_LIGHTING);
// Deshabilito Texturas.
glDisable(GL_TEXTURE_2D);
// Deshabilito profundidad.
glDisable(GL_DEPTH_TEST);

Salvo los estados de las matrices de proyeccion y modelo, y pongo la proyeccion ortogonal con las medidas de nuestra ventana.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Este bloque es para guardar las matrices de 
// OpenGL y dejarlas igual al final.
// Selecciono matriz de projeccion.
glMatrixMode(GL_PROJECTION);
// La guardo en la pila.
glPushMatrix();
// Cargo la matriz identidad.
glLoadIdentity();
// Pongo proyeccion ortogonal.
glOrtho(0,rect.right,rect.bottom,0,-1,1);
// Selecciono matriz de modelo.
glMatrixMode(GL_MODELVIEW);
// La guardo en la pila.
glPushMatrix();
// Cargo la matriz identidad.
glLoadIdentity();

Pongo el color que quiero para lo que voy a escribir. Si quisieramos hacerlo variable, podriamos no ponerlo y usar glColor antes de usar la funcion Escribe.

1
2
// El color con el que voy a pintar.
glColor3f(1,1,1);

Le paso a OpenGL la posicion donde voy a poner el texto a escribir de pantalla con la funcion glRasterPos que ya vimos de pasada antes. Gracias a que hemos puesto la proyeccion ortogonal, la "posicion raster" coincidira con los pixels de nuestra ventana empezando en la esquina superior izquierda y la posicion que le pasemos sera donde pinte la esquina inferior izquierda del primer caracter del texto.

1
2
3
4
// Le indico donde va a empezar a pintar de la
// pantalla en base a los parametros de nuestra
// funcion.
glRasterPos2i(x,y);

Ahora va una funcion que sirve para guardar las variables de estado de OpenGL relativas a las "display lists" que se pueden alterar al pintar luego el texto.

1
2
// Esta funcion guarda los valores de lista. 
glPushAttrib(GL_LIST_BIT);

Establecemos el indicador base a partir del cual habiamos creado las "display lists" y le restamos 32 (el codigo del espacio en blanco, la primera "display list" que habiamos creado.

1
2
3
4
5
// Establecemos el identificativo base de las listas
// a usar (-32 porque el caracter de la fuente con valor
// ASCII mas bajo es el espacio cuyo valor es 32, y de ahi
// para arriba.
glListBase(ListaFuente-32);

Esta es la funcion clave, la que pinta el texto en pantalla.
La funcion glCallLists el la que pinta varias listas consecutivas a partir del identificativo que se le paso en la funcion anterior, glListBase (por ejemplo, si glListBase lo ponemos a 100 y pintamos 3 listas, pintara las listas cuyos identificativos sean 100, 101 y 102).                glCallLists recibe tres parametros, el numero de listas a pintar (numero de letras incluidos espacios), el tipo de los indices (GL_UNSIGNED_BYTE) y el ultimo sera el puntero al array con los indices de las listas a pintar (un array de caracteres) ; se calcula sumando el indice de cada caracter (codigo ASCII de cada caracter) mas el glListBase (por eso le hemos restado 32, porque no hemos creado listas para los primeros 32 indices).

1
2
3
4
5
6
7
// Ejecuto tantas listas como la longitud del texto a 
// escribir, los indices son del tipo unsigned byte y
// los indices de las listas estan en el puntero que se
// pasa como tercer parametro(los codigos ASCII del texto).
// A estos indices se les suma la base establecida en la
// funcion anterior.
glCallLists(strlen(texto), GL_UNSIGNED_BYTE, texto);

Nota: con la funcion strlen calculamos la longitud en caracteres de la cadena.

Restablezco los valores para las "display lists".

1
2
// Restablezco los valores de lista.
glPopAttrib();

Dejo color blanco, que es el que voy a usar como base para ver bien todo.

1
2
// Restablezco como color el blanco, nuestro color base.
glColor3f(1,1,1);

Y restablezco tambien la proyeccion y matriz de modelo anterior.

1
2
3
4
5
6
7
8
// Restablezco la matrix de modelo anterior.
glPopMatrix();
// Cambio a la matriz de projeccion.
glMatrixMode(GL_PROJECTION);
// Restablezco la matrix de proyeccion anterior.
glPopMatrix();
// Dejo como activa la matriz de modelo.
glMatrixMode(GL_MODELVIEW);

Dejo tambien la iluminacion, las texturas y el test de profundidad como estaban, habilitados o no.

1
2
3
4
5
// Si antes estaba habilidata la iluminacion,
// Texturas o profundidad las dejo igual.
if(luz) glEnable(GL_LIGHTING);
if(tex) glEnable(GL_TEXTURE_2D);
if(pro) glEnable(GL_DEPTH_TEST);

Con esto tenemos ya nuestra propia funcion "printf" para OpenGL, que podemos poner en cualquier parte de nuestra funcion Pinta() sin problemas porque deja todo como estaba tras escribir el texto.

Para terminar y ver algo escrito hay que incluir la funcion GeneraFuente() en la funcion IniciaGL().

1
2
// Genero la fuente con la que voy a escribir.
GeneraFuente();

pero siempre antes de crear las texturas. Si usas la funcion wglUseFontBitmaps despues de una funcion glTexParameter, por alguna razon que desconozco, la primera falla y no se generan las listas para el texto (con esto intento ahorraros un par de dias de desesperacion al ver que no sale y no sabes porque).

Para finalizar solo queda escribir lo que queramos en la funcion Pinta() con nuestra flamante funcion Escribe(...), en cualquier lugar, despues de borrar la pantalla anterior con glClear, claro.

1
2
// Escribo texto en pantalla.
Escribe(20,360,"Hola Mundo OpenGL" );

Separamos un poco del borde izquierdo (20) y lo ponemos abajo de nuestra ventana (360), o donde nos de la gana.

Al fin podemos escribir en OpenGL:


El codigo completo es este: usw9b.cpp




¡Sólo los usuarios registrados pueden escribir comentarios!
+/- Comentarios
Buscar
Mario  - Preguntilla   |186.104.20.xxx |11-07-2010 21:06:24
Hola. Me gustaría saber que habría que agregarle a la función Escribe() para
poder cambiar la fuente de esta cada vez que aparezca.

Gracias de antemano
Vicengetorix   |194.179.126.xxx |12-07-2010 11:55:23
Habría que generar los display list de la otra fuente, tal y como hemos hecho
con la primera, y a la hora de estribir, antes, habría que cambiar la base de
las listas (glListBase) para que apunte a la segunda fuente.
Para elegir entre
fuentes se podría hacer añadiendo un parámetro a la función.
jromerorasero  - Duda al inicializar la lista de fuentes   |87.220.201.xxx |05-03-2010 22:25:18
Buenas tardes.
Tengo una dudilla, y es que al llamar a GeneraFuente() desde
IniciaGL() yo la puse al final de la función, junto a GeneraLista(). Si hacía
esto no obtenía ningún resultado en la pantalla, ¿puedes explicarme porqué
hay que llamarla justo ahí?
Un saludo y gracias.
Vicengetorix   |194.179.126.xxx |12-03-2010 02:17:29
Ver el foro (es por no responder 2 veces)
FabianSN  - Que mala suerte!!!   |85.53.197.xxx |23-11-2011 19:18:38
Venía bien con el curso, hasta que empecé a probar los ejemplos y ahora me
encuentro con otro problema, los fps no me aumentan a más de 5.
Si, como se
lee, no más de 5 fps.
La unica forma de que aumenten es arrastrando la ventana
desde su barra de título bien abajo, que solo se vea el mensaje de FPS para que
aumente a 30.
Probé tambien el rompe ladrillos en 2D y no me anda a más de 5
fps.
Tengo una placa NVIDIA GeForce FX 5200 de 128 Mb y en mi Pc tengo 1Gb de
RAM, será que es una plataforma muy lenta? Con muchos juegos no tengo
problemas.
He probado cambiando algunas cositas y toquetee un poco el código
para que me marque el máximo FPS en algún momento de la ejecucion y he notado
gracias a eso que aumentan los FPS mientras la ventana esta parcialmente fuera
del área visible, es más, si minimizo y máximizo la ventana me muestra un
pico de FPS máximo de 999 y si oculto casi toda la ventana, dejando solo
v...
Vicengetorix   |85.53.218.xxx |21-07-2009 12:02:07
Pienso que tienes un problema con los drivers de la tarjeta. La tarjeta es mas
que suficiente para mover los programas.
Seguramente los juegos que has probado
estan hechos con DirectX.
Yo he testeado los programas en dos ATI (X550 y
mobility X600) y una Intel (no de las modernas) y funcionan con 200 FPS o mas.

Prueba el "OpenGL Extensions Viewer" para ver la capacidad de tu
tarjeta y bajate el ultimo driver.
Comentame como va.

Suerte.
FabianSN  - Son Contento     |85.53.197.xxx |23-11-2011 19:18:06
Bueno, te hice caso y me fui a buscar algún driver más actualizado.

Cambió
muchísimo, ahora estoy a 75/80 fps, creo que voy a probar cuando pueda, el
programa que me mencionas para ver la capacidad de mi tarjeta/sistema.

En
realidad los juegos comerciales no me daban problemas pero otros desarrollos en
C++ hecho por usuarios particulares como vos, esos si me daban problemas de
velocidad, aún cuando usaban DirectX.

En fin, creo que todo pasa por la
placa video/perfomance PC, pero por lo menos me haz solucionado un problema de
años en mi PC.

Ya antes había intentado incursionar en la creación de
juegos de video y los fps siempre fueron un obstaculo, ya 80 fps para mi es
suficiente para hacer muchas cosas, no obstante voy a seguir investigando si
puede aumentar aún más.

Muchas gracias maestro, me ha sido de gran ayuda.
FabianSN  - FPS   |85.53.197.xxx |23-11-2011 19:15:44
Continúo este problemita que mencione antes aquí, para no armar otro hilo en
otro lado, ya que es un problema de FPS, de lo cual habla este
articulo.

Probé el soft \"OpenGL Extensions Viewer 3.0\" e
hice un test de velocidades y me informa que hasta OpenGL 1.5 la velocidad ronda
entre 180/200 FPS,pero cuando hace el test para OpenGL 2.0 y 2.1 la velocidad
cae entre 50/70 FPS.

¿Tendrá que ver con la libreria de OpenGL que estoy
usando la cual es la última version?
¿Hay forma de que los programas los
compile para que usen opengl 1.5?
O bien, ¿hay forma de decirle que se ejecute
con opengl 1.5?

Saludos y gracias, pero no termino de entender bien entre la
libreria opengl con sus versiones, el driver nuevo que baje de NVIDIA y que
testea este soft que mencione antes.
Vicengetorix   |85.53.215.xxx |24-07-2009 11:35:18
Nos estamos adelantando al curso.
El tema de las versiones tiene que ver con las
extensiones.
De momento todo lo que estamos haciendo es con la version 1.1 de
OpenGL, aunque nuestra tarjeta grafica acepte mas.
En dos capitulos veremos
estas cosas.
La libreria que usas es la que el driver de tu tarjeta instala mas
la que viene con Windows (que es 1.1). Para usar versiones superiores o
extensiones, debes preparar tu programa a mano o con alguna libreria como
GLEE.
Por ahora no te preocupes del tema si el PC ya te va suficientemente
rapido (no solo de tarjeta grafica vive OpenGL, tambien procesador central,
memoria, ...).
Pedro  - Hola   |189.145.138.xxx |16-06-2009 22:51:51
Hola, exelentes tutoriales espero pronto subas los demas gracias.

3.26 Copyright (C) 2008 Compojoom.com / Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved."

 


Banner
Spanish Chinese (Simplified) English French German Japanese Portuguese Russian