OpenGL에서 유니코드 텍스트를 표시하는 방법은 무엇입니까?
문제
Windows에서 OpenGL에 유니코드 텍스트를 표시하는 좋은 방법이 있습니까?예를 들어, 다양한 언어를 다루어야 하는 경우입니다.다음과 같은 가장 일반적인 접근 방식은
#define FONTLISTRANGE 128
GLuint list;
list = glGenLists(FONTLISTRANGE);
wglUseFontBitmapsW(hDC, 0, FONTLISTRANGE, list);
모든 유니코드 문자에 대해 충분한 목록을 만들 수 없기 때문에 그렇게 하지 않습니다.
해결책
언어별로 문자를 그룹화할 수도 있습니다.필요에 따라 각 언어표를 로드하고, 언어를 전환해야 할 경우 이전 언어표를 언로드하고 새 언어표를 로드합니다.
다른 팁
당신은 또한 확인해야합니다 FTGL 라이브러리.
FTGL은 Freetype2를 사용하여 OpenGL 응용 프로그램의 렌더링 글꼴을 단순화하는 무료 크로스 플랫폼 오픈 소스 C ++ 라이브러리입니다.FTGL은 비트 맵, 픽스 맵, 텍스처 맵, 윤곽선, 다각형 메쉬 및 압출 다각형 렌더링 모드를 지원합니다.
이 프로젝트는 한동안 휴면 상태였지만 최근에 다시 개발 중입니다.최신 버전을 사용하도록 프로젝트를 업데이트하지 않았지만 확인해 보시기 바랍니다.
이를 통해 트루타입 글꼴을 사용할 수 있습니다. 프리타입 글꼴 라이브러리.
나는 이것을 읽는 것이 좋습니다 OpenGL 글꼴 튜토리얼.이는 D 프로그래밍 언어를 위한 것이지만 OpenGL로 텍스트를 렌더링하기 위한 글리프 캐싱 시스템 구현과 관련된 다양한 문제에 대한 좋은 소개입니다.이 튜토리얼에서는 유니코드 준수, 앤티앨리어싱 및 커닝 기술을 다룹니다.
D는 C++를 아는 사람이라면 누구나 쉽게 이해할 수 있으며 대부분의 기사는 구현 언어가 아닌 일반적인 기술에 관한 것입니다.
위에서 이미 권장한 대로 FTGL을 추천하고 싶습니다. 그러나 저는 freetype/OpenGL 렌더러를 직접 구현했으며 이 휠을 직접 재창조하려는 경우 코드가 편리할 것이라고 생각했습니다.하지만 저는 FTGL을 추천합니다. 사용하기가 훨씬 덜 번거롭기 때문입니다.:)
* 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;
}
모든 텍스처 메모리가 파괴되는 것을 방지하기 위해 잠재적으로 일종의 LRU 정책을 사용하여 텍스처 메모리에 자신만의 "글리프 캐시"를 생성해야 할 수도 있습니다.현재 방법만큼 쉽지는 않지만 유니코드 문자 수를 고려하면 유일한 방법일 수 있습니다.
유니코드 렌더링 라이브러리 사용을 고려해야 합니다(예: 팡고) 항목을 비트맵으로 렌더링하고 해당 비트맵을 화면이나 텍스처에 배치합니다.
유니코드 텍스트를 렌더링하는 것은 간단하지 않습니다.따라서 단순히 64K 직사각형 글리프를 로드하여 사용할 수는 없습니다.
문자가 겹칠 수 있습니다.예를 들어 이 스마일리에서:
( ͡° ͜ʖ ͡°)
일부 코드 포인트는 이전 문자에 악센트를 쌓습니다.이 발췌문을 고려하십시오. 주목할만한 게시물:
... 그는 모든 균형을 이루는 radiakat, html tags leajki̧ki ̧kin͘g fr̶ǫm ̡yo ͟ ur eoy͢s̸ ̛ litik e liq uid paressing의 노래의 노래의 목소리의 노래에 대한 목소리의 노래에 대한 노래의 노래를 불러 일으킨다. SP 여기서 나는 당신이 그것을 볼 수 있습니다. 내 얼굴 ᵒH 신이 아니오 no a no a nθ를 멈추십시오.
정말로 유니코드를 올바르게 렌더링하려면 이 코드도 올바르게 렌더링할 수 있어야 합니다.
업데이트:이 팡고 엔진을 보니 바나나, 고릴라, 정글 전체의 경우입니다.첫째, GObject를 사용했기 때문에 Glib에 의존하고, 둘째, 바이트 버퍼로 직접 렌더링할 수 없습니다.Cario 및 FreeType 백엔드가 있으므로 텍스트를 렌더링하고 결국 비트맵으로 내보내려면 둘 중 하나를 사용해야 합니다.지금까지는 별로 좋아 보이지 않습니다.
그 외에도 결과를 텍스처에 저장하려면 다음을 사용하십시오. pango_layout_get_pixel_extents
텍스트를 렌더링할 직사각형의 크기를 가져오도록 텍스트를 설정한 후.잉크 직사각형은 전체 텍스트를 포함하는 직사각형이며, 왼쪽 상단 위치는 논리적 직사각형의 왼쪽 상단을 기준으로 한 위치입니다.(논리적 직사각형의 맨 아래 줄이 기준선입니다).도움이 되었기를 바랍니다.
Queso GLC는 이에 적합합니다. 저는 이를 사용하여 중국어와 키릴 문자를 3D로 렌더링했습니다.
http://quesoglc.sourceforge.net/
함께 제공되는 유니코드 텍스트 샘플을 사용하면 시작할 수 있습니다.