¿Cómo mostrar texto Unicode en OpenGL?
Pregunta
¿Existe una buena manera de mostrar texto Unicode en opengl en Windows?Por ejemplo, cuando tienes que lidiar con diferentes idiomas.El enfoque más común como
#define FONTLISTRANGE 128
GLuint list;
list = glGenLists(FONTLISTRANGE);
wglUseFontBitmapsW(hDC, 0, FONTLISTRANGE, list);
simplemente no sirve porque no puedes crear suficientes listas para todos los caracteres Unicode.
Solución
También puedes agrupar los personajes por idioma.Cargue cada tabla de idiomas según sea necesario y, cuando necesite cambiar de idioma, descargue la tabla de idiomas anterior y cargue la nueva.
Otros consejos
También deberías consultar el biblioteca FTGL.
FTGL es una biblioteca C ++ de código abierto cruzada gratuita que usa Freetype2 para simplificar las fuentes de representación en aplicaciones OpenGL.FTGL admite mapas de bits, pixmaps, mapas de textura, contornos, malla de polígono y modos de renderizado de polígono extruido.
Este proyecto estuvo inactivo por un tiempo, pero recientemente ha vuelto a estar en desarrollo.No he actualizado mi proyecto para usar la última versión, pero deberías comprobarlo.
Permite utilizar cualquier fuente True Type a través del Tipo libre biblioteca de fuentes.
recomiendo leer esto Tutorial de fuentes OpenGL.Es para el lenguaje de programación D, pero es una buena introducción a varios temas relacionados con la implementación de un sistema de almacenamiento en caché de glifos para representar texto con OpenGL.El tutorial cubre el cumplimiento de Unicode, el antialiasing y las técnicas de kerning.
D es bastante comprensible para cualquiera que conozca C++ y la mayor parte del artículo trata sobre técnicas generales, no sobre el lenguaje de implementación.
Recomiendo FTGL como ya se recomendó anteriormente, sin embargo, yo mismo implementé un renderizador de tipo libre/OpenGL y pensé que el código podría resultarle útil si desea reinventar esta rueda usted mismo.Sin embargo, realmente recomendaría FTGL, es mucho menos complicado de usar.:)
* glTextRender class by Semi Essessi
*
* FreeType2 empowered text renderer
*
*/
#include "glTextRender.h"
#include "jEngine.h"
#include "glSystem.h"
#include "jMath.h"
#include "jProfiler.h"
#include "log.h"
#include <windows.h>
FT_Library glTextRender::ftLib = 0;
//TODO::maybe fix this so it use wchar_t for the filename
glTextRender::glTextRender(jEngine* j, const char* fontName, int size = 12)
{
#ifdef _DEBUG
jProfiler profiler = jProfiler(L"glTextRender::glTextRender");
#endif
char fontName2[1024];
memset(fontName2,0,sizeof(char)*1024);
sprintf(fontName2,"fonts\\%s",fontName);
if(!ftLib)
{
#ifdef _DEBUG
wchar_t fn[128];
mbstowcs(fn,fontName,strlen(fontName)+1);
LogWriteLine(L"\x25CB\x25CB\x25CF Font: %s was requested before FreeType was initialised", fn);
#endif
return;
}
// constructor code for glTextRender
e=j;
gl = j->gl;
red=green=blue=alpha=1.0f;
face = 0;
// remember that for some weird reason below font size 7 everything gets scrambled up
height = max(6,(int)floorf((float)size*((float)gl->getHeight())*0.001666667f));
aHeight = ((float)height)/((float)gl->getHeight());
setPosition(0.0f,0.0f);
// look in base fonts dir
if(FT_New_Face(ftLib, fontName2, 0, &face ))
{
// if we dont have it look in windows fonts dir
char buf[1024];
GetWindowsDirectoryA(buf,1024);
strcat(buf, "\\fonts\\");
strcat(buf, fontName);
if(FT_New_Face(ftLib, buf, 0, &face ))
{
//TODO::check in mod fonts directory
#ifdef _DEBUG
wchar_t fn[128];
mbstowcs(fn,fontName,strlen(fontName)+1);
LogWriteLine(L"\x25CB\x25CB\x25CF Request for font: %s has failed", fn);
#endif
face = 0;
return;
}
}
// FreeType uses 64x size and 72dpi for default
// doubling size for ms
FT_Set_Char_Size(face, mulPow2(height,7), mulPow2(height,7), 96, 96);
// set up cache table and then generate the first 256 chars and the console prompt character
for(int i=0;i<65536;i++)
{
cached[i]=false;
width[i]=0.0f;
}
for(unsigned short i = 0; i < 256; i++) getChar((wchar_t)i);
getChar(CHAR_PROMPT);
#ifdef _DEBUG
wchar_t fn[128];
mbstowcs(fn,fontName,strlen(fontName)+1);
LogWriteLine(L"\x25CB\x25CB\x25CF Font: %s loaded OK", fn);
#endif
}
glTextRender::~glTextRender()
{
// destructor code for glTextRender
for(int i=0;i<65536;i++)
{
if(cached[i])
{
glDeleteLists(listID[i],1);
glDeleteTextures(1,&(texID[i]));
}
}
// TODO:: work out stupid freetype crashz0rs
try
{
static int foo = 0;
if(face && foo < 1)
{
foo++;
FT_Done_Face(face);
face = 0;
}
}
catch(...)
{
face = 0;
}
}
// return true if init works, or if already initialised
bool glTextRender::initFreeType()
{
if(!ftLib)
{
if(!FT_Init_FreeType(&ftLib)) return true;
else return false;
} else return true;
}
void glTextRender::shutdownFreeType()
{
if(ftLib)
{
FT_Done_FreeType(ftLib);
ftLib = 0;
}
}
void glTextRender::print(const wchar_t* str)
{
// store old stuff to set start position
glPushAttrib(GL_TRANSFORM_BIT);
// get viewport size
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluOrtho2D(viewport[0],viewport[2],viewport[1],viewport[3]);
glPopAttrib();
float color[4];
glGetFloatv(GL_CURRENT_COLOR, color);
glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
glEnable(GL_TEXTURE_2D);
//glDisable(GL_DEPTH_TEST);
// set blending for AA
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glTranslatef(xPos,yPos,0.0f);
glColor4f(red,green,blue,alpha);
// call display lists to render text
glListBase(0u);
for(unsigned int i=0;i<wcslen(str);i++) glCallList(getChar(str[i]));
// restore old states
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
glPopAttrib();
glColor4fv(color);
glPushAttrib(GL_TRANSFORM_BIT);
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glPopAttrib();
}
void glTextRender::printf(const wchar_t* str, ...)
{
if(!str) return;
wchar_t* buf = 0;
va_list parg;
va_start(parg, str);
// allocate buffer
int len = (_vscwprintf(str, parg)+1);
buf = new wchar_t[len];
if(!buf) return;
vswprintf(buf, str, parg);
va_end(parg);
print(buf);
delete[] buf;
}
GLuint glTextRender::getChar(const wchar_t c)
{
int i = (int)c;
if(cached[i]) return listID[i];
// load glyph and get bitmap
if(FT_Load_Glyph(face, FT_Get_Char_Index(face, i), FT_LOAD_DEFAULT )) return 0;
FT_Glyph glyph;
if(FT_Get_Glyph(face->glyph, &glyph)) return 0;
FT_Glyph_To_Bitmap(&glyph, FT_RENDER_MODE_NORMAL, 0, 1);
FT_BitmapGlyph bitmapGlyph = (FT_BitmapGlyph)glyph;
FT_Bitmap& bitmap = bitmapGlyph->bitmap;
int w = roundPow2(bitmap.width);
int h = roundPow2(bitmap.rows);
// convert to texture in memory
GLubyte* texture = new GLubyte[2*w*h];
for(int j=0;j<h;j++)
{
bool cond = j>=bitmap.rows;
for(int k=0;k<w;k++)
{
texture[2*(k+j*w)] = 0xFFu;
texture[2*(k+j*w)+1] = ((k>=bitmap.width)||cond) ? 0x0u : bitmap.buffer[k+bitmap.width*j];
}
}
// store char width and adjust max height
// note .5f
float ih = 1.0f/((float)gl->getHeight());
width[i] = ((float)divPow2(face->glyph->advance.x, 7))*ih;
aHeight = max(aHeight,(.5f*(float)bitmap.rows)*ih);
glPushAttrib(GL_LIST_BIT | GL_CURRENT_BIT | GL_ENABLE_BIT | GL_TRANSFORM_BIT);
// create gl texture
glGenTextures(1, &(texID[i]));
glEnable(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, texID[i]);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, w, h, 0, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, texture);
glPopAttrib();
delete[] texture;
// create display list
listID[i] = glGenLists(1);
glNewList(listID[i], GL_COMPILE);
glBindTexture(GL_TEXTURE_2D, texID[i]);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
// adjust position to account for texture padding
glTranslatef(.5f*(float)bitmapGlyph->left, 0.0f, 0.0f);
glTranslatef(0.0f, .5f*(float)(bitmapGlyph->top-bitmap.rows), 0.0f);
// work out texcoords
float tx=((float)bitmap.width)/((float)w);
float ty=((float)bitmap.rows)/((float)h);
// render
// note .5f
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(0.0f, .5f*(float)bitmap.rows);
glTexCoord2f(0.0f, ty);
glVertex2f(0.0f, 0.0f);
glTexCoord2f(tx, ty);
glVertex2f(.5f*(float)bitmap.width, 0.0f);
glTexCoord2f(tx, 0.0f);
glVertex2f(.5f*(float)bitmap.width, .5f*(float)bitmap.rows);
glEnd();
glPopMatrix();
// move position for the next character
// note extra div 2
glTranslatef((float)divPow2(face->glyph->advance.x, 7), 0.0f, 0.0f);
glEndList();
// char is succesfully cached for next time
cached[i] = true;
return listID[i];
}
void glTextRender::setPosition(float x, float y)
{
float fac = ((float)gl->getHeight());
xPos = fac*x+FONT_BORDER_PIXELS; yPos = fac*(1-y)-(float)height-FONT_BORDER_PIXELS;
}
float glTextRender::getAdjustedWidth(const wchar_t* str)
{
float w = 0.0f;
for(unsigned int i=0;i<wcslen(str);i++)
{
if(cached[str[i]]) w+=width[str[i]];
else
{
getChar(str[i]);
w+=width[str[i]];
}
}
return w;
}
Es posible que tengas que generar tu propio "caché de glifos" en la memoria de texturas a medida que avanzas, potencialmente con algún tipo de política LRU para evitar destruir toda la memoria de texturas.No es tan fácil como el método actual, pero puede ser la única forma dada la cantidad de caracteres Unicode.
Deberías considerar el uso de una biblioteca de renderizado Unicode (por ejemplo. Pango) para renderizar el material en un mapa de bits y colocar ese mapa de bits en la pantalla o en una textura.
Representar texto Unicode no es sencillo.Por lo tanto, no puede simplemente cargar glifos rectangulares de 64K y usarlos.
Los personajes pueden superponerse.Por ejemplo, en este emoticón:
( ͡° ͜ʖ ͡°)
Algunos puntos de código acumulan acentos en el carácter anterior.Considere este extracto de este publicación notable:
... él come, ̕h̵i s un̨ho͞ly radiańcé desestro ҉ying todos los enjuague, html etiquetas lea͠ki̧n͘g fr̶ǫm ̡yo ͟ur ojo ̸s̸ ̛lik͏e liq uid dolor, la canción de la experiencia re̸gular que extenderá las voces de las voces de las voces Sp aquí puedo verlo, ¿puedes ver? Mi cara ᵒh Dios no noo̼o o nθ deja de un*̶͑̾̾ ̅ͫ͏̙̤g͇̫͛͆̾ͫ̑͆l͖͉̗̩̳̟̍ͫͥͨe̠̅s ͎a̧͈͖r̽̾̈́͒͑e n ot rè̑ͧ̌aͨl̘̝̙̃ͤ͂̾̆ za̡͊͠͝lgό isͮ̂҉̯͈͕̹̘̱ to͇̹̺ͅɲ̴ȳ̳ th̘ë͖́̉ ͠p̯͍̭o̚ n̐y̡ h̸̡̪̯ͨ͊̽̅̾̎ȩ̬̩̾͛ͪ̈́̀́͘ ̶̧̨̱̹̭̯ͧ̾ͬc̷̙̲̝͖ͭ̏ͥͮ͟oͮ͏̮̪̝͍m̲̖͊̒ͪͩͬ̚̚͜ȇ̴̟̟͙̞ͩ͌͝s̨̥̫͎̭ͯ̿̔̀ͅs̨̥̫͎̭ͯ̿̔̀ͅ
Si realmente desea representar Unicode correctamente, también debería poder representar este correctamente.
ACTUALIZAR:Miré este motor Pango, y es el caso del plátano, el gorila y toda la jungla.Primero, depende de Glib porque usó GObjects, segundo, no puede renderizarse directamente en un búfer de bytes.Tiene backends Cario y FreeType, por lo que debes usar uno de ellos para renderizar el texto y eventualmente exportarlo a mapas de bits.Eso no se ve bien hasta ahora.
Además de eso, si deseas almacenar el resultado en una textura, usa pango_layout_get_pixel_extents
después de configurar el texto para obtener los tamaños de los rectángulos para representar el texto.El rectángulo de tinta es el rectángulo que contiene el texto completo, su posición superior izquierda es la posición relativa a la parte superior izquierda del rectángulo lógico.(La línea inferior del rectángulo lógico es la línea de base).Espero que esto ayude.
Queso GLC es excelente para esto, lo he usado para representar caracteres chinos y cirílicos en 3D.
http://quesoglc.sourceforge.net/
El ejemplo de texto Unicode que viene debería ayudarle a empezar.